mirror of
https://github.com/wpilibsuite/allwpilib
synced 2026-06-24 01:31:46 +00:00
[glass] Use JSON files for storage instead of imgui ini
Storage is now nested. Separate "roots" can be configured which save to separate files. In particular, this is used to save wpigui and ImGui window position to a -window.json file. ImGui's ini (for window position) is mapped to JSON. You can optionally specify a directory to load from on the command line. If one isn't provided, it uses the global system directory. Any changes made are automatically saved here. Workspace | Open: select directory, the current layout is replaced with that workspace, and future auto-saves also switch to that location. The main window size/location is not changed, only the contents. Workspace | Save As: select directory, the current layout is saved there, and future auto-saves also switch to that location. Workspace | Reset: window locations are preserved, but all other settings are reset to default (including e.g. removing plot windows). This will also end up clearing the current save file. as with load, the main window size/location is not changed. Workspace | Save As Global: "save as" to the global system location Notably, the main window size/location is only loaded at startup, but is auto-saved as part of the current workspace.
This commit is contained in:
@@ -7,13 +7,21 @@
|
||||
#include <algorithm>
|
||||
#include <cinttypes>
|
||||
#include <cstdio>
|
||||
#include <filesystem>
|
||||
|
||||
#include <fmt/format.h>
|
||||
#include <imgui.h>
|
||||
#include <imgui_internal.h>
|
||||
#include <imgui_stdlib.h>
|
||||
#include <wpi/StringExtras.h>
|
||||
#include <wpi/fs.h>
|
||||
#include <wpi/json.h>
|
||||
#include <wpi/json_serializer.h>
|
||||
#include <wpi/raw_istream.h>
|
||||
#include <wpi/raw_ostream.h>
|
||||
#include <wpi/timestamp.h>
|
||||
#include <wpigui.h>
|
||||
#include <wpigui_internal.h>
|
||||
|
||||
#include "glass/ContextInternal.h"
|
||||
|
||||
@@ -21,172 +29,292 @@ using namespace glass;
|
||||
|
||||
Context* glass::gContext;
|
||||
|
||||
static bool ConvertInt(Storage::Value* value) {
|
||||
value->type = Storage::Value::kInt;
|
||||
if (auto val = wpi::parse_integer<int>(value->stringVal, 10)) {
|
||||
value->intVal = val.value();
|
||||
return true;
|
||||
static void WorkspaceResetImpl() {
|
||||
// call reset functions
|
||||
for (auto&& reset : gContext->workspaceReset) {
|
||||
if (reset) {
|
||||
reset();
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
||||
// clear storage
|
||||
for (auto&& root : gContext->storageRoots) {
|
||||
root.second->Clear();
|
||||
}
|
||||
|
||||
// ImGui reset
|
||||
ImGui::ClearIniSettings();
|
||||
}
|
||||
|
||||
static bool ConvertInt64(Storage::Value* value) {
|
||||
value->type = Storage::Value::kInt64;
|
||||
if (auto val = wpi::parse_integer<int64_t>(value->stringVal, 10)) {
|
||||
value->int64Val = val.value();
|
||||
return true;
|
||||
static void WorkspaceInit() {
|
||||
for (auto&& init : gContext->workspaceInit) {
|
||||
if (init) {
|
||||
init();
|
||||
}
|
||||
}
|
||||
|
||||
for (auto&& root : gContext->storageRoots) {
|
||||
root.getValue()->Apply();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool ConvertBool(Storage::Value* value) {
|
||||
value->type = Storage::Value::kBool;
|
||||
if (auto val = wpi::parse_integer<int>(value->stringVal, 10)) {
|
||||
value->intVal = (val.value() != 0);
|
||||
return true;
|
||||
static bool JsonToWindow(const wpi::json& jfile, const char* filename) {
|
||||
if (!jfile.is_object()) {
|
||||
ImGui::LogText("%s top level is not object", filename);
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
|
||||
// loop over JSON and generate ini format
|
||||
std::string iniStr;
|
||||
wpi::raw_string_ostream ini{iniStr};
|
||||
|
||||
for (auto&& jsection : jfile.items()) {
|
||||
if (!jsection.value().is_object()) {
|
||||
ImGui::LogText("%s section %s is not object", filename,
|
||||
jsection.key().c_str());
|
||||
return false;
|
||||
}
|
||||
for (auto&& jsubsection : jsection.value().items()) {
|
||||
if (!jsubsection.value().is_object()) {
|
||||
ImGui::LogText("%s section %s subsection %s is not object", filename,
|
||||
jsection.key().c_str(), jsubsection.key().c_str());
|
||||
return false;
|
||||
}
|
||||
ini << '[' << jsection.key() << "][" << jsubsection.key() << "]\n";
|
||||
for (auto&& jkv : jsubsection.value().items()) {
|
||||
try {
|
||||
auto& value = jkv.value().get_ref<const std::string&>();
|
||||
ini << jkv.key() << '=' << value << "\n";
|
||||
} catch (wpi::json::exception&) {
|
||||
ImGui::LogText("%s section %s subsection %s value %s is not string",
|
||||
filename, jsection.key().c_str(),
|
||||
jsubsection.key().c_str(), jkv.key().c_str());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
ini << '\n';
|
||||
}
|
||||
}
|
||||
ini.flush();
|
||||
|
||||
ImGui::LoadIniSettingsFromMemory(iniStr.data(), iniStr.size());
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool ConvertFloat(Storage::Value* value) {
|
||||
value->type = Storage::Value::kFloat;
|
||||
if (auto val = wpi::parse_float<float>(value->stringVal)) {
|
||||
value->floatVal = val.value();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool ConvertDouble(Storage::Value* value) {
|
||||
value->type = Storage::Value::kDouble;
|
||||
if (auto val = wpi::parse_float<double>(value->stringVal)) {
|
||||
value->doubleVal = val.value();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static void* GlassStorageReadOpen(ImGuiContext*, ImGuiSettingsHandler* handler,
|
||||
const char* name) {
|
||||
auto ctx = static_cast<Context*>(handler->UserData);
|
||||
auto& storage = ctx->storage[name];
|
||||
if (!storage) {
|
||||
storage = std::make_unique<Storage>();
|
||||
}
|
||||
return storage.get();
|
||||
}
|
||||
|
||||
static void GlassStorageReadLine(ImGuiContext*, ImGuiSettingsHandler*,
|
||||
void* entry, const char* line) {
|
||||
auto storage = static_cast<Storage*>(entry);
|
||||
auto [key, val] = wpi::split(line, '=');
|
||||
auto& keys = storage->GetKeys();
|
||||
auto& values = storage->GetValues();
|
||||
auto it = std::find(keys.begin(), keys.end(), key);
|
||||
if (it == keys.end()) {
|
||||
keys.emplace_back(key);
|
||||
values.emplace_back(std::make_unique<Storage::Value>(val));
|
||||
static bool LoadWindowStorageImpl(const std::string& filename) {
|
||||
std::error_code ec;
|
||||
wpi::raw_fd_istream is{filename, ec};
|
||||
if (ec) {
|
||||
ImGui::LogText("error opening %s: %s", filename.c_str(),
|
||||
ec.message().c_str());
|
||||
return false;
|
||||
} else {
|
||||
auto& value = *values[it - keys.begin()];
|
||||
value.stringVal = val;
|
||||
switch (value.type) {
|
||||
case Storage::Value::kInt:
|
||||
ConvertInt(&value);
|
||||
break;
|
||||
case Storage::Value::kInt64:
|
||||
ConvertInt64(&value);
|
||||
break;
|
||||
case Storage::Value::kBool:
|
||||
ConvertBool(&value);
|
||||
break;
|
||||
case Storage::Value::kFloat:
|
||||
ConvertFloat(&value);
|
||||
break;
|
||||
case Storage::Value::kDouble:
|
||||
ConvertDouble(&value);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
try {
|
||||
return JsonToWindow(wpi::json::parse(is), filename.c_str());
|
||||
} catch (wpi::json::parse_error& e) {
|
||||
ImGui::LogText("Error loading %s: %s", filename.c_str(), e.what());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void GlassStorageWriteAll(ImGuiContext*, ImGuiSettingsHandler* handler,
|
||||
ImGuiTextBuffer* out_buf) {
|
||||
auto ctx = static_cast<Context*>(handler->UserData);
|
||||
|
||||
// sort for output
|
||||
std::vector<wpi::StringMapConstIterator<std::unique_ptr<Storage>>> sorted;
|
||||
for (auto it = ctx->storage.begin(); it != ctx->storage.end(); ++it) {
|
||||
sorted.emplace_back(it);
|
||||
static bool LoadStorageRootImpl(Context* ctx, const std::string& filename,
|
||||
std::string_view rootName) {
|
||||
std::error_code ec;
|
||||
wpi::raw_fd_istream is{filename, ec};
|
||||
if (ec) {
|
||||
ImGui::LogText("error opening %s: %s", filename.c_str(),
|
||||
ec.message().c_str());
|
||||
return false;
|
||||
} else {
|
||||
auto& storage = ctx->storageRoots[rootName];
|
||||
bool createdStorage = false;
|
||||
if (!storage) {
|
||||
storage = std::make_unique<Storage>();
|
||||
createdStorage = true;
|
||||
}
|
||||
try {
|
||||
storage->FromJson(wpi::json::parse(is), filename.c_str());
|
||||
} catch (wpi::json::parse_error& e) {
|
||||
ImGui::LogText("Error loading %s: %s", filename.c_str(), e.what());
|
||||
if (createdStorage) {
|
||||
ctx->storageRoots.erase(rootName);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
std::sort(sorted.begin(), sorted.end(), [](const auto& a, const auto& b) {
|
||||
return a->getKey() < b->getKey();
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
for (auto&& entryIt : sorted) {
|
||||
auto& entry = *entryIt;
|
||||
out_buf->append("[GlassStorage][");
|
||||
out_buf->append(entry.first().data(),
|
||||
entry.first().data() + entry.first().size());
|
||||
out_buf->append("]\n");
|
||||
auto& keys = entry.second->GetKeys();
|
||||
auto& values = entry.second->GetValues();
|
||||
for (size_t i = 0; i < keys.size(); ++i) {
|
||||
out_buf->append(keys[i].data(), keys[i].data() + keys[i].size());
|
||||
out_buf->append("=");
|
||||
auto& value = *values[i];
|
||||
switch (value.type) {
|
||||
case Storage::Value::kInt:
|
||||
out_buf->appendf("%d\n", value.intVal);
|
||||
break;
|
||||
case Storage::Value::kInt64:
|
||||
out_buf->appendf("%" PRId64 "\n", value.int64Val);
|
||||
break;
|
||||
case Storage::Value::kBool:
|
||||
out_buf->appendf("%d\n", value.boolVal ? 1 : 0);
|
||||
break;
|
||||
case Storage::Value::kFloat:
|
||||
out_buf->appendf("%f\n", value.floatVal);
|
||||
break;
|
||||
case Storage::Value::kDouble:
|
||||
out_buf->appendf("%f\n", value.doubleVal);
|
||||
break;
|
||||
case Storage::Value::kNone:
|
||||
case Storage::Value::kString:
|
||||
out_buf->append(value.stringVal.data(),
|
||||
value.stringVal.data() + value.stringVal.size());
|
||||
out_buf->append("\n");
|
||||
break;
|
||||
static bool LoadStorageImpl(Context* ctx, std::string_view dir,
|
||||
std::string_view name) {
|
||||
WorkspaceResetImpl();
|
||||
|
||||
bool rv = true;
|
||||
for (auto&& root : ctx->storageRoots) {
|
||||
std::string filename;
|
||||
auto rootName = root.getKey();
|
||||
if (rootName.empty()) {
|
||||
filename = (fs::path{dir} / fmt::format("{}.json", name)).string();
|
||||
} else {
|
||||
filename =
|
||||
(fs::path{dir} / fmt::format("{}-{}.json", name, rootName)).string();
|
||||
}
|
||||
if (!LoadStorageRootImpl(ctx, filename, rootName)) {
|
||||
rv = false;
|
||||
}
|
||||
}
|
||||
|
||||
WorkspaceInit();
|
||||
return rv;
|
||||
}
|
||||
|
||||
static wpi::json WindowToJson() {
|
||||
size_t iniLen;
|
||||
const char* iniData = ImGui::SaveIniSettingsToMemory(&iniLen);
|
||||
std::string_view ini{iniData, iniLen};
|
||||
|
||||
// parse the ini data and build JSON
|
||||
// JSON format:
|
||||
// {
|
||||
// "Section": {
|
||||
// "Subsection": {
|
||||
// "Key": "Value" // all values are saved as strings
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
wpi::json out = wpi::json::object();
|
||||
wpi::json* curSection = nullptr;
|
||||
while (!ini.empty()) {
|
||||
std::string_view line;
|
||||
std::tie(line, ini) = wpi::split(ini, '\n');
|
||||
line = wpi::trim(line);
|
||||
if (line.empty()) {
|
||||
continue;
|
||||
}
|
||||
if (line[0] == '[') {
|
||||
// new section
|
||||
auto [section, subsection] = wpi::split(line, ']');
|
||||
section = wpi::drop_front(section); // drop '['; ']' was dropped by split
|
||||
subsection = wpi::drop_back(wpi::drop_front(subsection)); // drop []
|
||||
auto& jsection = out[section];
|
||||
if (jsection.is_null()) {
|
||||
jsection = wpi::json::object();
|
||||
}
|
||||
curSection = &jsection[subsection];
|
||||
if (curSection->is_null()) {
|
||||
*curSection = wpi::json::object();
|
||||
}
|
||||
} else {
|
||||
// value
|
||||
if (!curSection) {
|
||||
continue; // shouldn't happen, but just in case
|
||||
}
|
||||
auto [name, value] = wpi::split(line, '=');
|
||||
(*curSection)[name] = value;
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
bool SaveWindowStorageImpl(const std::string& filename) {
|
||||
std::error_code ec;
|
||||
wpi::raw_fd_ostream os{filename, ec};
|
||||
if (ec) {
|
||||
ImGui::LogText("error opening %s: %s", filename.c_str(),
|
||||
ec.message().c_str());
|
||||
return false;
|
||||
}
|
||||
WindowToJson().dump(os, 2);
|
||||
os << '\n';
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool SaveStorageRootImpl(Context* ctx, const std::string& filename,
|
||||
const Storage& storage) {
|
||||
std::error_code ec;
|
||||
wpi::raw_fd_ostream os{filename, ec};
|
||||
if (ec) {
|
||||
ImGui::LogText("error opening %s: %s", filename.c_str(),
|
||||
ec.message().c_str());
|
||||
return false;
|
||||
}
|
||||
storage.ToJson().dump(os, 2);
|
||||
os << '\n';
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool SaveStorageImpl(Context* ctx, std::string_view dir,
|
||||
std::string_view name, bool exiting) {
|
||||
fs::path dirPath{dir};
|
||||
|
||||
std::error_code ec;
|
||||
fs::create_directories(dirPath, ec);
|
||||
if (ec) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// handle erasing save files on exit if requested
|
||||
if (exiting && wpi::gui::gContext->resetOnExit) {
|
||||
fs::remove(dirPath / fmt::format("{}-window.json", name), ec);
|
||||
for (auto&& root : ctx->storageRoots) {
|
||||
auto rootName = root.getKey();
|
||||
if (rootName.empty()) {
|
||||
fs::remove(dirPath / fmt::format("{}.json", name), ec);
|
||||
} else {
|
||||
fs::remove(dirPath / fmt::format("{}-{}.json", name, rootName), ec);
|
||||
}
|
||||
}
|
||||
out_buf->append("\n");
|
||||
}
|
||||
|
||||
bool rv = SaveWindowStorageImpl(
|
||||
(dirPath / fmt::format("{}-window.json", name)).string());
|
||||
|
||||
for (auto&& root : ctx->storageRoots) {
|
||||
auto rootName = root.getKey();
|
||||
std::string filename;
|
||||
if (rootName.empty()) {
|
||||
filename = (dirPath / fmt::format("{}.json", name)).string();
|
||||
} else {
|
||||
filename = (dirPath / fmt::format("{}-{}.json", name, rootName)).string();
|
||||
}
|
||||
if (!SaveStorageRootImpl(ctx, filename, *root.getValue())) {
|
||||
rv = false;
|
||||
}
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
static void Initialize(Context* ctx) {
|
||||
wpi::gui::AddInit([=] {
|
||||
ImGuiSettingsHandler ini_handler;
|
||||
ini_handler.TypeName = "GlassStorage";
|
||||
ini_handler.TypeHash = ImHashStr("GlassStorage");
|
||||
ini_handler.ReadOpenFn = GlassStorageReadOpen;
|
||||
ini_handler.ReadLineFn = GlassStorageReadLine;
|
||||
ini_handler.WriteAllFn = GlassStorageWriteAll;
|
||||
ini_handler.UserData = ctx;
|
||||
ImGui::GetCurrentContext()->SettingsHandlers.push_back(ini_handler);
|
||||
Context::Context()
|
||||
: sourceNameStorage{storageRoots.insert({"", std::make_unique<Storage>()})
|
||||
.first->getValue()
|
||||
->GetChild("sourceNames")} {
|
||||
storageStack.emplace_back(storageRoots[""].get());
|
||||
|
||||
ctx->sources.Initialize();
|
||||
});
|
||||
// override ImGui ini saving
|
||||
wpi::gui::ConfigureCustomSaveSettings(
|
||||
[this] { LoadStorageImpl(this, storageLoadDir, storageName); },
|
||||
[this] {
|
||||
LoadWindowStorageImpl((fs::path{storageLoadDir} /
|
||||
fmt::format("{}-window.json", storageName))
|
||||
.string());
|
||||
},
|
||||
[this](bool exiting) {
|
||||
SaveStorageImpl(this, storageAutoSaveDir, storageName, exiting);
|
||||
});
|
||||
}
|
||||
|
||||
static void Shutdown(Context* ctx) {}
|
||||
Context::~Context() {
|
||||
wpi::gui::ConfigureCustomSaveSettings(nullptr, nullptr, nullptr);
|
||||
}
|
||||
|
||||
Context* glass::CreateContext() {
|
||||
Context* ctx = new Context;
|
||||
if (!gContext) {
|
||||
SetCurrentContext(ctx);
|
||||
}
|
||||
Initialize(ctx);
|
||||
return ctx;
|
||||
}
|
||||
|
||||
@@ -194,7 +322,6 @@ void glass::DestroyContext(Context* ctx) {
|
||||
if (!ctx) {
|
||||
ctx = gContext;
|
||||
}
|
||||
Shutdown(ctx);
|
||||
if (gContext == ctx) {
|
||||
SetCurrentContext(nullptr);
|
||||
}
|
||||
@@ -217,215 +344,167 @@ uint64_t glass::GetZeroTime() {
|
||||
return gContext->zeroTime;
|
||||
}
|
||||
|
||||
Storage::Value& Storage::GetValue(std::string_view key) {
|
||||
auto it = std::find(m_keys.begin(), m_keys.end(), key);
|
||||
if (it == m_keys.end()) {
|
||||
m_keys.emplace_back(key);
|
||||
m_values.emplace_back(std::make_unique<Value>());
|
||||
return *m_values.back();
|
||||
} else {
|
||||
return *m_values[it - m_keys.begin()];
|
||||
void glass::WorkspaceReset() {
|
||||
WorkspaceResetImpl();
|
||||
WorkspaceInit();
|
||||
}
|
||||
|
||||
void glass::AddWorkspaceInit(std::function<void()> init) {
|
||||
if (init) {
|
||||
gContext->workspaceInit.emplace_back(std::move(init));
|
||||
}
|
||||
}
|
||||
|
||||
#define DEFUN(CapsName, LowerName, CType) \
|
||||
CType Storage::Get##CapsName(std::string_view key, CType defaultVal) const { \
|
||||
auto it = std::find(m_keys.begin(), m_keys.end(), key); \
|
||||
if (it == m_keys.end()) \
|
||||
return defaultVal; \
|
||||
Value& value = *m_values[it - m_keys.begin()]; \
|
||||
if (value.type != Value::k##CapsName) { \
|
||||
if (!Convert##CapsName(&value)) \
|
||||
value.LowerName##Val = defaultVal; \
|
||||
} \
|
||||
return value.LowerName##Val; \
|
||||
} \
|
||||
\
|
||||
void Storage::Set##CapsName(std::string_view key, CType val) { \
|
||||
auto it = std::find(m_keys.begin(), m_keys.end(), key); \
|
||||
if (it == m_keys.end()) { \
|
||||
m_keys.emplace_back(key); \
|
||||
m_values.emplace_back(std::make_unique<Value>()); \
|
||||
m_values.back()->type = Value::k##CapsName; \
|
||||
m_values.back()->LowerName##Val = val; \
|
||||
} else { \
|
||||
Value& value = *m_values[it - m_keys.begin()]; \
|
||||
value.type = Value::k##CapsName; \
|
||||
value.LowerName##Val = val; \
|
||||
} \
|
||||
} \
|
||||
\
|
||||
CType* Storage::Get##CapsName##Ref(std::string_view key, CType defaultVal) { \
|
||||
auto it = std::find(m_keys.begin(), m_keys.end(), key); \
|
||||
if (it == m_keys.end()) { \
|
||||
m_keys.emplace_back(key); \
|
||||
m_values.emplace_back(std::make_unique<Value>()); \
|
||||
m_values.back()->type = Value::k##CapsName; \
|
||||
m_values.back()->LowerName##Val = defaultVal; \
|
||||
return &m_values.back()->LowerName##Val; \
|
||||
} else { \
|
||||
Value& value = *m_values[it - m_keys.begin()]; \
|
||||
if (value.type != Value::k##CapsName) { \
|
||||
if (!Convert##CapsName(&value)) \
|
||||
value.LowerName##Val = defaultVal; \
|
||||
} \
|
||||
return &value.LowerName##Val; \
|
||||
} \
|
||||
}
|
||||
|
||||
DEFUN(Int, int, int)
|
||||
DEFUN(Int64, int64, int64_t)
|
||||
DEFUN(Bool, bool, bool)
|
||||
DEFUN(Float, float, float)
|
||||
DEFUN(Double, double, double)
|
||||
|
||||
std::string Storage::GetString(std::string_view key,
|
||||
std::string_view defaultVal) const {
|
||||
auto it = std::find(m_keys.begin(), m_keys.end(), key);
|
||||
if (it == m_keys.end()) {
|
||||
return std::string{defaultVal};
|
||||
}
|
||||
Value& value = *m_values[it - m_keys.begin()];
|
||||
value.type = Value::kString;
|
||||
return value.stringVal;
|
||||
}
|
||||
|
||||
void Storage::SetString(std::string_view key, std::string_view val) {
|
||||
auto it = std::find(m_keys.begin(), m_keys.end(), key);
|
||||
if (it == m_keys.end()) {
|
||||
m_keys.emplace_back(key);
|
||||
m_values.emplace_back(std::make_unique<Value>(val));
|
||||
m_values.back()->type = Value::kString;
|
||||
} else {
|
||||
Value& value = *m_values[it - m_keys.begin()];
|
||||
value.type = Value::kString;
|
||||
value.stringVal = val;
|
||||
void glass::AddWorkspaceReset(std::function<void()> reset) {
|
||||
if (reset) {
|
||||
gContext->workspaceReset.emplace_back(std::move(reset));
|
||||
}
|
||||
}
|
||||
|
||||
std::string* Storage::GetStringRef(std::string_view key,
|
||||
std::string_view defaultVal) {
|
||||
auto it = std::find(m_keys.begin(), m_keys.end(), key);
|
||||
if (it == m_keys.end()) {
|
||||
m_keys.emplace_back(key);
|
||||
m_values.emplace_back(std::make_unique<Value>(defaultVal));
|
||||
m_values.back()->type = Value::kString;
|
||||
return &m_values.back()->stringVal;
|
||||
void glass::SetStorageName(std::string_view name) {
|
||||
gContext->storageName = name;
|
||||
}
|
||||
|
||||
void glass::SetStorageDir(std::string_view dir) {
|
||||
if (dir.empty()) {
|
||||
gContext->storageLoadDir = ".";
|
||||
gContext->storageAutoSaveDir = ".";
|
||||
} else {
|
||||
Value& value = *m_values[it - m_keys.begin()];
|
||||
value.type = Value::kString;
|
||||
return &value.stringVal;
|
||||
gContext->storageLoadDir = dir;
|
||||
gContext->storageAutoSaveDir = dir;
|
||||
gContext->isPlatformSaveDir = (dir == wpi::gui::GetPlatformSaveFileDir());
|
||||
}
|
||||
}
|
||||
|
||||
std::string glass::GetStorageDir() {
|
||||
return gContext->storageAutoSaveDir;
|
||||
}
|
||||
|
||||
bool glass::LoadStorage(std::string_view dir) {
|
||||
SaveStorage();
|
||||
SetStorageDir(dir);
|
||||
LoadWindowStorageImpl((fs::path{gContext->storageLoadDir} /
|
||||
fmt::format("{}-window.json", gContext->storageName))
|
||||
.string());
|
||||
return LoadStorageImpl(gContext, dir, gContext->storageName);
|
||||
}
|
||||
|
||||
bool glass::SaveStorage() {
|
||||
return SaveStorageImpl(gContext, gContext->storageAutoSaveDir,
|
||||
gContext->storageName, false);
|
||||
}
|
||||
|
||||
bool glass::SaveStorage(std::string_view dir) {
|
||||
return SaveStorageImpl(gContext, dir, gContext->storageName, false);
|
||||
}
|
||||
|
||||
Storage& glass::GetCurStorageRoot() {
|
||||
return *gContext->storageStack.front();
|
||||
}
|
||||
|
||||
Storage& glass::GetStorageRoot(std::string_view rootName) {
|
||||
auto& storage = gContext->storageRoots[rootName];
|
||||
if (!storage) {
|
||||
storage = std::make_unique<Storage>();
|
||||
}
|
||||
return *storage;
|
||||
}
|
||||
|
||||
void glass::ResetStorageStack(std::string_view rootName) {
|
||||
if (gContext->storageStack.size() != 1) {
|
||||
ImGui::LogText("resetting non-empty storage stack");
|
||||
}
|
||||
gContext->storageStack.clear();
|
||||
gContext->storageStack.emplace_back(&GetStorageRoot(rootName));
|
||||
}
|
||||
|
||||
Storage& glass::GetStorage() {
|
||||
auto& storage = gContext->storage[gContext->curId];
|
||||
if (!storage) {
|
||||
storage = std::make_unique<Storage>();
|
||||
}
|
||||
return *storage;
|
||||
return *gContext->storageStack.back();
|
||||
}
|
||||
|
||||
Storage& glass::GetStorage(std::string_view id) {
|
||||
auto& storage = gContext->storage[id];
|
||||
if (!storage) {
|
||||
storage = std::make_unique<Storage>();
|
||||
}
|
||||
return *storage;
|
||||
void glass::PushStorageStack(std::string_view label_id) {
|
||||
gContext->storageStack.emplace_back(
|
||||
&gContext->storageStack.back()->GetChild(label_id));
|
||||
}
|
||||
|
||||
static void PushIDStack(std::string_view label_id) {
|
||||
gContext->idStack.emplace_back(gContext->curId.size());
|
||||
|
||||
auto [label, id] = wpi::split(label_id, "###");
|
||||
// if no ###id, use label as id
|
||||
if (id.empty()) {
|
||||
id = label;
|
||||
}
|
||||
if (!gContext->curId.empty()) {
|
||||
gContext->curId += "###";
|
||||
}
|
||||
gContext->curId += id;
|
||||
void glass::PushStorageStack(Storage& storage) {
|
||||
gContext->storageStack.emplace_back(&storage);
|
||||
}
|
||||
|
||||
static void PopIDStack() {
|
||||
gContext->curId.resize(gContext->idStack.back());
|
||||
gContext->idStack.pop_back();
|
||||
void glass::PopStorageStack() {
|
||||
if (gContext->storageStack.size() <= 1) {
|
||||
ImGui::LogText("attempted to pop empty storage stack, mismatch push/pop?");
|
||||
return; // ignore
|
||||
}
|
||||
gContext->storageStack.pop_back();
|
||||
}
|
||||
|
||||
bool glass::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) {
|
||||
PushIDStack(name);
|
||||
PushStorageStack(name);
|
||||
return ImGui::Begin(name, p_open, flags);
|
||||
}
|
||||
|
||||
void glass::End() {
|
||||
ImGui::End();
|
||||
PopIDStack();
|
||||
PopStorageStack();
|
||||
}
|
||||
|
||||
bool glass::BeginChild(const char* str_id, const ImVec2& size, bool border,
|
||||
ImGuiWindowFlags flags) {
|
||||
PushIDStack(str_id);
|
||||
PushStorageStack(str_id);
|
||||
return ImGui::BeginChild(str_id, size, border, flags);
|
||||
}
|
||||
|
||||
void glass::EndChild() {
|
||||
ImGui::EndChild();
|
||||
PopIDStack();
|
||||
PopStorageStack();
|
||||
}
|
||||
|
||||
bool glass::CollapsingHeader(const char* label, ImGuiTreeNodeFlags flags) {
|
||||
wpi::SmallString<64> openKey;
|
||||
auto [name, id] = wpi::split(label, "###");
|
||||
// if no ###id, use name as id
|
||||
if (id.empty()) {
|
||||
id = name;
|
||||
}
|
||||
openKey = id;
|
||||
openKey += "###open";
|
||||
|
||||
bool* open = GetStorage().GetBoolRef(openKey.str());
|
||||
*open = ImGui::CollapsingHeader(
|
||||
label, flags | (*open ? ImGuiTreeNodeFlags_DefaultOpen : 0));
|
||||
return *open;
|
||||
bool& open = GetStorage().GetChild(label).GetBool(
|
||||
"open", (flags & ImGuiTreeNodeFlags_DefaultOpen) != 0);
|
||||
ImGui::SetNextItemOpen(open);
|
||||
open = ImGui::CollapsingHeader(label, flags);
|
||||
return open;
|
||||
}
|
||||
|
||||
bool glass::TreeNodeEx(const char* label, ImGuiTreeNodeFlags flags) {
|
||||
PushIDStack(label);
|
||||
bool* open = GetStorage().GetBoolRef("open");
|
||||
*open = ImGui::TreeNodeEx(
|
||||
label, flags | (*open ? ImGuiTreeNodeFlags_DefaultOpen : 0));
|
||||
if (!*open) {
|
||||
PopIDStack();
|
||||
PushStorageStack(label);
|
||||
bool& open = GetStorage().GetBool(
|
||||
"open", (flags & ImGuiTreeNodeFlags_DefaultOpen) != 0);
|
||||
ImGui::SetNextItemOpen(open);
|
||||
open = ImGui::TreeNodeEx(label, flags);
|
||||
if (!open) {
|
||||
PopStorageStack();
|
||||
}
|
||||
return *open;
|
||||
return open;
|
||||
}
|
||||
|
||||
void glass::TreePop() {
|
||||
ImGui::TreePop();
|
||||
PopIDStack();
|
||||
PopStorageStack();
|
||||
}
|
||||
|
||||
void glass::PushID(const char* str_id) {
|
||||
PushIDStack(str_id);
|
||||
PushStorageStack(str_id);
|
||||
ImGui::PushID(str_id);
|
||||
}
|
||||
|
||||
void glass::PushID(const char* str_id_begin, const char* str_id_end) {
|
||||
PushIDStack(std::string_view(str_id_begin, str_id_end - str_id_begin));
|
||||
PushStorageStack(std::string_view(str_id_begin, str_id_end - str_id_begin));
|
||||
ImGui::PushID(str_id_begin, str_id_end);
|
||||
}
|
||||
|
||||
void glass::PushID(int int_id) {
|
||||
char buf[16];
|
||||
std::snprintf(buf, sizeof(buf), "%d", int_id);
|
||||
PushIDStack(buf);
|
||||
PushStorageStack(buf);
|
||||
ImGui::PushID(int_id);
|
||||
}
|
||||
|
||||
void glass::PopID() {
|
||||
ImGui::PopID();
|
||||
PopIDStack();
|
||||
PopStorageStack();
|
||||
}
|
||||
|
||||
bool glass::PopupEditName(const char* label, std::string* name) {
|
||||
|
||||
@@ -12,13 +12,9 @@ using namespace glass;
|
||||
|
||||
wpi::sig::Signal<const char*, DataSource*> DataSource::sourceCreated;
|
||||
|
||||
DataSource::DataSource(std::string_view id) : m_id{id} {
|
||||
auto it = gContext->sources.try_emplace(m_id, this);
|
||||
auto& srcName = it.first->getValue();
|
||||
m_name = srcName.name.get();
|
||||
if (!srcName.source) {
|
||||
srcName.source = this;
|
||||
}
|
||||
DataSource::DataSource(std::string_view id)
|
||||
: m_id{id}, m_name{gContext->sourceNameStorage.GetString(m_id)} {
|
||||
gContext->sources.try_emplace(m_id, this);
|
||||
sourceCreated(m_id.c_str(), this);
|
||||
}
|
||||
|
||||
@@ -32,43 +28,7 @@ DataSource::~DataSource() {
|
||||
if (!gContext) {
|
||||
return;
|
||||
}
|
||||
auto it = gContext->sources.find(m_id);
|
||||
if (it == gContext->sources.end()) {
|
||||
return;
|
||||
}
|
||||
auto& srcName = it->getValue();
|
||||
if (srcName.source == this) {
|
||||
srcName.source = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void DataSource::SetName(std::string_view name) {
|
||||
m_name->SetName(name);
|
||||
}
|
||||
|
||||
const char* DataSource::GetName() const {
|
||||
return m_name->GetName();
|
||||
}
|
||||
|
||||
void DataSource::PushEditNameId(int index) {
|
||||
m_name->PushEditNameId(index);
|
||||
}
|
||||
|
||||
void DataSource::PushEditNameId(const char* name) {
|
||||
m_name->PushEditNameId(name);
|
||||
}
|
||||
|
||||
bool DataSource::PopupEditName(int index) {
|
||||
return m_name->PopupEditName(index);
|
||||
}
|
||||
|
||||
bool DataSource::PopupEditName(const char* name) {
|
||||
return m_name->PopupEditName(name);
|
||||
}
|
||||
|
||||
bool DataSource::InputTextName(const char* label_id,
|
||||
ImGuiInputTextFlags flags) {
|
||||
return m_name->InputTextName(label_id, flags);
|
||||
gContext->sources.erase(m_id);
|
||||
}
|
||||
|
||||
void DataSource::LabelText(const char* label, const char* fmt, ...) const {
|
||||
@@ -82,7 +42,7 @@ void DataSource::LabelText(const char* label, const char* fmt, ...) const {
|
||||
void DataSource::LabelTextV(const char* label, const char* fmt,
|
||||
va_list args) const {
|
||||
ImGui::PushID(label);
|
||||
ImGui::LabelTextV("##input", fmt, args);
|
||||
ImGui::LabelTextV("##input", fmt, args); // NOLINT
|
||||
ImGui::SameLine(0, ImGui::GetStyle().ItemInnerSpacing.x);
|
||||
ImGui::Selectable(label);
|
||||
ImGui::PopID();
|
||||
@@ -141,7 +101,7 @@ void DataSource::EmitDrag(ImGuiDragDropFlags flags) const {
|
||||
if (ImGui::BeginDragDropSource(flags)) {
|
||||
auto self = this;
|
||||
ImGui::SetDragDropPayload("DataSource", &self, sizeof(self)); // NOLINT
|
||||
const char* name = GetName();
|
||||
const char* name = GetName().c_str();
|
||||
ImGui::TextUnformatted(name[0] == '\0' ? m_id.c_str() : name);
|
||||
ImGui::EndDragDropSource();
|
||||
}
|
||||
@@ -152,5 +112,5 @@ DataSource* DataSource::Find(std::string_view id) {
|
||||
if (it == gContext->sources.end()) {
|
||||
return nullptr;
|
||||
}
|
||||
return it->getValue().source;
|
||||
return it->getValue();
|
||||
}
|
||||
|
||||
@@ -6,8 +6,12 @@
|
||||
|
||||
#include <cstdio>
|
||||
|
||||
#include <imgui.h>
|
||||
#include <wpigui.h>
|
||||
|
||||
#include "glass/Context.h"
|
||||
#include "glass/ContextInternal.h"
|
||||
|
||||
using namespace glass;
|
||||
|
||||
void MainMenuBar::AddMainMenu(std::function<void()> menu) {
|
||||
@@ -25,6 +29,8 @@ void MainMenuBar::AddOptionMenu(std::function<void()> menu) {
|
||||
void MainMenuBar::Display() {
|
||||
ImGui::BeginMainMenuBar();
|
||||
|
||||
WorkspaceMenu();
|
||||
|
||||
if (!m_optionMenus.empty()) {
|
||||
if (ImGui::BeginMenu("Options")) {
|
||||
for (auto&& menu : m_optionMenus) {
|
||||
@@ -55,3 +61,46 @@ void MainMenuBar::Display() {
|
||||
#endif
|
||||
ImGui::EndMainMenuBar();
|
||||
}
|
||||
|
||||
void MainMenuBar::WorkspaceMenu() {
|
||||
if (ImGui::BeginMenu("Workspace")) {
|
||||
if (ImGui::MenuItem("Open...")) {
|
||||
m_openFolder =
|
||||
std::make_unique<pfd::select_folder>("Choose folder to open");
|
||||
}
|
||||
if (ImGui::MenuItem("Save As...")) {
|
||||
m_saveFolder = std::make_unique<pfd::select_folder>("Choose save folder");
|
||||
}
|
||||
if (ImGui::MenuItem("Save As Global", nullptr, false,
|
||||
!gContext->isPlatformSaveDir)) {
|
||||
SetStorageDir(wpi::gui::GetPlatformSaveFileDir());
|
||||
SaveStorage();
|
||||
}
|
||||
ImGui::Separator();
|
||||
if (ImGui::MenuItem("Reset")) {
|
||||
WorkspaceReset();
|
||||
}
|
||||
ImGui::Separator();
|
||||
if (ImGui::MenuItem("Exit")) {
|
||||
wpi::gui::Exit();
|
||||
}
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
|
||||
if (m_openFolder && m_openFolder->ready(0)) {
|
||||
auto result = m_openFolder->result();
|
||||
if (!result.empty()) {
|
||||
LoadStorage(result);
|
||||
}
|
||||
m_openFolder.reset();
|
||||
}
|
||||
|
||||
if (m_saveFolder && m_saveFolder->ready(0)) {
|
||||
auto result = m_saveFolder->result();
|
||||
if (!result.empty()) {
|
||||
SetStorageDir(result);
|
||||
SaveStorage(result);
|
||||
}
|
||||
m_saveFolder.reset();
|
||||
}
|
||||
}
|
||||
|
||||
688
glass/src/lib/native/cpp/Storage.cpp
Normal file
688
glass/src/lib/native/cpp/Storage.cpp
Normal file
@@ -0,0 +1,688 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
#include "glass/Storage.h"
|
||||
|
||||
#include <type_traits>
|
||||
|
||||
#include <imgui.h>
|
||||
#include <wpi/StringExtras.h>
|
||||
#include <wpi/json.h>
|
||||
|
||||
using namespace glass;
|
||||
|
||||
template <typename To>
|
||||
bool ConvertFromString(To* out, std::string_view str) {
|
||||
if constexpr (std::is_same_v<To, bool>) {
|
||||
if (str == "true") {
|
||||
*out = true;
|
||||
} else if (str == "false") {
|
||||
*out = false;
|
||||
} else if (auto val = wpi::parse_integer<int>(str, 10)) {
|
||||
*out = val.value() != 0;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else if constexpr (std::is_floating_point_v<To>) {
|
||||
if (auto val = wpi::parse_float<To>(str)) {
|
||||
*out = val.value();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (auto val = wpi::parse_integer<To>(str, 10)) {
|
||||
*out = val.value();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
#define CONVERT(CapsName, LowerName, CType) \
|
||||
static bool Convert##CapsName(Storage::Value* value) { \
|
||||
switch (value->type) { \
|
||||
case Storage::Value::kBool: \
|
||||
value->LowerName##Val = value->boolVal; \
|
||||
value->LowerName##Default = value->boolDefault; \
|
||||
break; \
|
||||
case Storage::Value::kDouble: \
|
||||
value->LowerName##Val = value->doubleVal; \
|
||||
value->LowerName##Default = value->doubleDefault; \
|
||||
break; \
|
||||
case Storage::Value::kFloat: \
|
||||
value->LowerName##Val = value->floatVal; \
|
||||
value->LowerName##Default = value->floatDefault; \
|
||||
break; \
|
||||
case Storage::Value::kInt: \
|
||||
value->LowerName##Val = value->intVal; \
|
||||
value->LowerName##Default = value->intDefault; \
|
||||
break; \
|
||||
case Storage::Value::kInt64: \
|
||||
value->LowerName##Val = value->int64Val; \
|
||||
value->LowerName##Default = value->int64Default; \
|
||||
break; \
|
||||
case Storage::Value::kString: \
|
||||
if (!ConvertFromString(&value->LowerName##Val, value->stringVal)) { \
|
||||
return false; \
|
||||
} \
|
||||
if (!ConvertFromString(&value->LowerName##Default, \
|
||||
value->stringDefault)) { \
|
||||
return false; \
|
||||
} \
|
||||
break; \
|
||||
default: \
|
||||
return false; \
|
||||
} \
|
||||
value->type = Storage::Value::k##CapsName; \
|
||||
return true; \
|
||||
}
|
||||
|
||||
CONVERT(Int, int, int)
|
||||
CONVERT(Int64, int64, int64_t)
|
||||
CONVERT(Float, float, float)
|
||||
CONVERT(Double, double, double)
|
||||
CONVERT(Bool, bool, bool)
|
||||
|
||||
static inline bool ConvertString(Storage::Value* value) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Arrays can only come from JSON, so we only have to worry about conversions
|
||||
// between the various number types, not bool or string
|
||||
|
||||
template <typename From, typename To>
|
||||
static void ConvertArray(std::vector<To>** outPtr, std::vector<From>** inPtr) {
|
||||
if (*inPtr) {
|
||||
std::vector<To>* tmp;
|
||||
tmp = new std::vector<To>{(*inPtr)->begin(), (*inPtr)->end()};
|
||||
delete *inPtr;
|
||||
*outPtr = tmp;
|
||||
} else {
|
||||
*outPtr = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
#define CONVERT_ARRAY(CapsName, LowerName) \
|
||||
static bool Convert##CapsName##Array(Storage::Value* value) { \
|
||||
switch (value->type) { \
|
||||
case Storage::Value::kDoubleArray: \
|
||||
ConvertArray(&value->LowerName##Array, &value->doubleArray); \
|
||||
ConvertArray(&value->LowerName##ArrayDefault, \
|
||||
&value->doubleArrayDefault); \
|
||||
break; \
|
||||
case Storage::Value::kFloatArray: \
|
||||
ConvertArray(&value->LowerName##Array, &value->floatArray); \
|
||||
ConvertArray(&value->LowerName##ArrayDefault, \
|
||||
&value->floatArrayDefault); \
|
||||
break; \
|
||||
case Storage::Value::kIntArray: \
|
||||
ConvertArray(&value->LowerName##Array, &value->intArray); \
|
||||
ConvertArray(&value->LowerName##ArrayDefault, \
|
||||
&value->intArrayDefault); \
|
||||
break; \
|
||||
case Storage::Value::kInt64Array: \
|
||||
ConvertArray(&value->LowerName##Array, &value->int64Array); \
|
||||
ConvertArray(&value->LowerName##ArrayDefault, \
|
||||
&value->int64ArrayDefault); \
|
||||
break; \
|
||||
default: \
|
||||
return false; \
|
||||
} \
|
||||
value->type = Storage::Value::k##CapsName##Array; \
|
||||
return true; \
|
||||
}
|
||||
|
||||
CONVERT_ARRAY(Int, int)
|
||||
CONVERT_ARRAY(Int64, int64)
|
||||
CONVERT_ARRAY(Float, float)
|
||||
CONVERT_ARRAY(Double, double)
|
||||
|
||||
static inline bool ConvertBoolArray(Storage::Value* value) {
|
||||
return false;
|
||||
}
|
||||
|
||||
static inline bool ConvertStringArray(Storage::Value* value) {
|
||||
return false;
|
||||
}
|
||||
|
||||
void Storage::Value::Reset(Type newType) {
|
||||
switch (type) {
|
||||
case kChild:
|
||||
delete child;
|
||||
break;
|
||||
case kIntArray:
|
||||
delete intArray;
|
||||
delete intArrayDefault;
|
||||
break;
|
||||
case kInt64Array:
|
||||
delete int64Array;
|
||||
delete int64ArrayDefault;
|
||||
break;
|
||||
case kBoolArray:
|
||||
delete boolArray;
|
||||
delete boolArrayDefault;
|
||||
break;
|
||||
case kFloatArray:
|
||||
delete floatArray;
|
||||
delete floatArrayDefault;
|
||||
break;
|
||||
case kDoubleArray:
|
||||
delete doubleArray;
|
||||
delete doubleArrayDefault;
|
||||
break;
|
||||
case kStringArray:
|
||||
delete stringArray;
|
||||
delete stringArrayDefault;
|
||||
break;
|
||||
case kChildArray:
|
||||
delete childArray;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
type = newType;
|
||||
}
|
||||
|
||||
Storage::Value* Storage::FindValue(std::string_view key) {
|
||||
auto it = m_values.find(key);
|
||||
if (it == m_values.end()) {
|
||||
return nullptr;
|
||||
}
|
||||
return it->second.get();
|
||||
}
|
||||
|
||||
Storage::Value& Storage::GetValue(std::string_view key) {
|
||||
auto& val = m_values[key];
|
||||
if (!val) {
|
||||
val = std::make_unique<Value>();
|
||||
}
|
||||
return *val;
|
||||
}
|
||||
|
||||
#define DEFUN(CapsName, LowerName, CType, CParamType, ArrCType) \
|
||||
CType Storage::Read##CapsName(std::string_view key, CParamType defaultVal) \
|
||||
const { \
|
||||
auto it = m_values.find(key); \
|
||||
if (it == m_values.end()) { \
|
||||
return CType{defaultVal}; \
|
||||
} \
|
||||
Value& value = *it->second; \
|
||||
if (value.type != Value::k##CapsName) { \
|
||||
if (!Convert##CapsName(&value)) { \
|
||||
value.Reset(Value::k##CapsName); \
|
||||
value.LowerName##Val = defaultVal; \
|
||||
value.LowerName##Default = defaultVal; \
|
||||
value.hasDefault = true; \
|
||||
} \
|
||||
} \
|
||||
return value.LowerName##Val; \
|
||||
} \
|
||||
\
|
||||
void Storage::Set##CapsName(std::string_view key, CParamType val) { \
|
||||
auto& valuePtr = m_values[key]; \
|
||||
if (!valuePtr) { \
|
||||
valuePtr = std::make_unique<Value>(Value::k##CapsName); \
|
||||
} else { \
|
||||
valuePtr->Reset(Value::k##CapsName); \
|
||||
} \
|
||||
valuePtr->LowerName##Val = val; \
|
||||
valuePtr->LowerName##Default = {}; \
|
||||
} \
|
||||
\
|
||||
CType& Storage::Get##CapsName(std::string_view key, CParamType defaultVal) { \
|
||||
auto& valuePtr = m_values[key]; \
|
||||
bool setValue = false; \
|
||||
if (!valuePtr) { \
|
||||
valuePtr = std::make_unique<Value>(Value::k##CapsName); \
|
||||
setValue = true; \
|
||||
} else if (valuePtr->type != Value::k##CapsName) { \
|
||||
if (!Convert##CapsName(valuePtr.get())) { \
|
||||
valuePtr->Reset(Value::k##CapsName); \
|
||||
setValue = true; \
|
||||
} \
|
||||
} \
|
||||
if (setValue) { \
|
||||
valuePtr->LowerName##Val = defaultVal; \
|
||||
} \
|
||||
if (!valuePtr->hasDefault) { \
|
||||
valuePtr->LowerName##Default = defaultVal; \
|
||||
valuePtr->hasDefault = true; \
|
||||
} \
|
||||
return valuePtr->LowerName##Val; \
|
||||
} \
|
||||
\
|
||||
std::vector<ArrCType>& Storage::Get##CapsName##Array( \
|
||||
std::string_view key, wpi::span<const ArrCType> defaultVal) { \
|
||||
auto& valuePtr = m_values[key]; \
|
||||
bool setValue = false; \
|
||||
if (!valuePtr) { \
|
||||
valuePtr = std::make_unique<Value>(Value::k##CapsName##Array); \
|
||||
setValue = true; \
|
||||
} else if (valuePtr->type != Value::k##CapsName##Array) { \
|
||||
if (!Convert##CapsName##Array(valuePtr.get())) { \
|
||||
valuePtr->Reset(Value::k##CapsName##Array); \
|
||||
setValue = true; \
|
||||
} \
|
||||
} \
|
||||
if (setValue) { \
|
||||
valuePtr->LowerName##Array = \
|
||||
new std::vector<ArrCType>{defaultVal.begin(), defaultVal.end()}; \
|
||||
} \
|
||||
if (!valuePtr->hasDefault) { \
|
||||
if (defaultVal.empty()) { \
|
||||
valuePtr->LowerName##ArrayDefault = nullptr; \
|
||||
} else { \
|
||||
valuePtr->LowerName##ArrayDefault = \
|
||||
new std::vector<ArrCType>{defaultVal.begin(), defaultVal.end()}; \
|
||||
} \
|
||||
valuePtr->hasDefault = true; \
|
||||
} \
|
||||
assert(valuePtr->LowerName##Array); \
|
||||
return *valuePtr->LowerName##Array; \
|
||||
}
|
||||
|
||||
DEFUN(Int, int, int, int, int)
|
||||
DEFUN(Int64, int64, int64_t, int64_t, int64_t)
|
||||
DEFUN(Bool, bool, bool, bool, int)
|
||||
DEFUN(Float, float, float, float, float)
|
||||
DEFUN(Double, double, double, double, double)
|
||||
DEFUN(String, string, std::string, std::string_view, std::string)
|
||||
|
||||
Storage& Storage::GetChild(std::string_view label_id) {
|
||||
auto [label, id] = wpi::split(label_id, "###");
|
||||
if (id.empty()) {
|
||||
id = label;
|
||||
}
|
||||
auto& childPtr = m_values[id];
|
||||
if (!childPtr) {
|
||||
childPtr = std::make_unique<Value>();
|
||||
}
|
||||
if (childPtr->type != Value::kChild) {
|
||||
childPtr->type = Value::kChild;
|
||||
childPtr->child = new Storage;
|
||||
}
|
||||
return *childPtr->child;
|
||||
}
|
||||
|
||||
std::vector<std::unique_ptr<Storage>>& Storage::GetChildArray(
|
||||
std::string_view key) {
|
||||
auto& valuePtr = m_values[key];
|
||||
if (!valuePtr) {
|
||||
valuePtr = std::make_unique<Value>(Value::kChildArray);
|
||||
valuePtr->childArray = new std::vector<std::unique_ptr<Storage>>();
|
||||
} else if (valuePtr->type != Value::kChildArray) {
|
||||
valuePtr->Reset(Value::kChildArray);
|
||||
valuePtr->childArray = new std::vector<std::unique_ptr<Storage>>();
|
||||
}
|
||||
|
||||
return *valuePtr->childArray;
|
||||
}
|
||||
|
||||
std::unique_ptr<Storage::Value> Storage::Erase(std::string_view key) {
|
||||
auto it = m_values.find(key);
|
||||
if (it != m_values.end()) {
|
||||
auto rv = std::move(it->getValue());
|
||||
m_values.erase(it);
|
||||
return rv;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void Storage::EraseChildren() {
|
||||
for (auto&& kv : m_values) {
|
||||
if (kv.getValue()->type == Value::kChild) {
|
||||
m_values.remove(&kv);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool JsonArrayToStorage(Storage::Value* valuePtr, const wpi::json& jarr,
|
||||
const char* filename) {
|
||||
auto& arr = jarr.get_ref<const wpi::json::array_t&>();
|
||||
if (arr.empty()) {
|
||||
ImGui::LogText("empty array in %s, ignoring", filename);
|
||||
return false;
|
||||
}
|
||||
|
||||
// guess array type from first element
|
||||
switch (arr[0].type()) {
|
||||
case wpi::json::value_t::boolean:
|
||||
if (valuePtr->type != Storage::Value::kBoolArray) {
|
||||
valuePtr->Reset(Storage::Value::kBoolArray);
|
||||
valuePtr->boolArray = new std::vector<int>();
|
||||
valuePtr->boolArrayDefault = nullptr;
|
||||
}
|
||||
break;
|
||||
case wpi::json::value_t::number_float:
|
||||
if (valuePtr->type != Storage::Value::kDoubleArray) {
|
||||
valuePtr->Reset(Storage::Value::kDoubleArray);
|
||||
valuePtr->doubleArray = new std::vector<double>();
|
||||
valuePtr->doubleArrayDefault = nullptr;
|
||||
}
|
||||
break;
|
||||
case wpi::json::value_t::number_integer:
|
||||
case wpi::json::value_t::number_unsigned:
|
||||
if (valuePtr->type != Storage::Value::kInt64Array) {
|
||||
valuePtr->Reset(Storage::Value::kInt64Array);
|
||||
valuePtr->int64Array = new std::vector<int64_t>();
|
||||
valuePtr->int64ArrayDefault = nullptr;
|
||||
}
|
||||
break;
|
||||
case wpi::json::value_t::string:
|
||||
if (valuePtr->type != Storage::Value::kStringArray) {
|
||||
valuePtr->Reset(Storage::Value::kStringArray);
|
||||
valuePtr->stringArray = new std::vector<std::string>();
|
||||
valuePtr->stringArrayDefault = nullptr;
|
||||
}
|
||||
break;
|
||||
case wpi::json::value_t::object:
|
||||
if (valuePtr->type != Storage::Value::kChildArray) {
|
||||
valuePtr->Reset(Storage::Value::kChildArray);
|
||||
valuePtr->childArray = new std::vector<std::unique_ptr<Storage>>();
|
||||
}
|
||||
break;
|
||||
case wpi::json::value_t::array:
|
||||
ImGui::LogText("nested array in %s, ignoring", filename);
|
||||
return false;
|
||||
default:
|
||||
ImGui::LogText("null value in %s, ignoring", filename);
|
||||
return false;
|
||||
}
|
||||
|
||||
// loop over array to store elements
|
||||
for (auto jvalue : arr) {
|
||||
switch (jvalue.type()) {
|
||||
case wpi::json::value_t::boolean:
|
||||
if (valuePtr->type == Storage::Value::kBoolArray) {
|
||||
valuePtr->boolArray->push_back(jvalue.get<bool>());
|
||||
} else {
|
||||
goto error;
|
||||
}
|
||||
break;
|
||||
case wpi::json::value_t::number_float:
|
||||
if (valuePtr->type == Storage::Value::kDoubleArray) {
|
||||
valuePtr->doubleArray->push_back(jvalue.get<double>());
|
||||
} else {
|
||||
goto error;
|
||||
}
|
||||
break;
|
||||
case wpi::json::value_t::number_integer:
|
||||
if (valuePtr->type == Storage::Value::kInt64Array) {
|
||||
valuePtr->int64Array->push_back(jvalue.get<int64_t>());
|
||||
} else if (valuePtr->type == Storage::Value::kDoubleArray) {
|
||||
valuePtr->doubleArray->push_back(jvalue.get<int64_t>());
|
||||
} else {
|
||||
goto error;
|
||||
}
|
||||
break;
|
||||
case wpi::json::value_t::number_unsigned:
|
||||
if (valuePtr->type == Storage::Value::kInt64Array) {
|
||||
valuePtr->int64Array->push_back(jvalue.get<uint64_t>());
|
||||
} else if (valuePtr->type == Storage::Value::kDoubleArray) {
|
||||
valuePtr->doubleArray->push_back(jvalue.get<uint64_t>());
|
||||
} else {
|
||||
goto error;
|
||||
}
|
||||
break;
|
||||
case wpi::json::value_t::string:
|
||||
if (valuePtr->type == Storage::Value::kStringArray) {
|
||||
valuePtr->stringArray->emplace_back(
|
||||
jvalue.get_ref<const std::string&>());
|
||||
} else {
|
||||
goto error;
|
||||
}
|
||||
break;
|
||||
case wpi::json::value_t::object:
|
||||
if (valuePtr->type == Storage::Value::kChildArray) {
|
||||
valuePtr->childArray->emplace_back(std::make_unique<Storage>());
|
||||
valuePtr->childArray->back()->FromJson(jvalue, filename);
|
||||
} else {
|
||||
goto error;
|
||||
}
|
||||
break;
|
||||
case wpi::json::value_t::array:
|
||||
ImGui::LogText("nested array in %s, ignoring", filename);
|
||||
return false;
|
||||
default:
|
||||
ImGui::LogText("null value in %s, ignoring", filename);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
|
||||
error:
|
||||
ImGui::LogText("array with variant types in %s, ignoring", filename);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Storage::FromJson(const wpi::json& json, const char* filename) {
|
||||
if (m_fromJson) {
|
||||
return m_fromJson(json, filename);
|
||||
}
|
||||
|
||||
if (!json.is_object()) {
|
||||
ImGui::LogText("non-object in %s", filename);
|
||||
return false;
|
||||
}
|
||||
for (auto&& jkv : json.items()) {
|
||||
auto& valuePtr = m_values[jkv.key()];
|
||||
bool created = false;
|
||||
if (!valuePtr) {
|
||||
valuePtr = std::make_unique<Value>();
|
||||
created = true;
|
||||
}
|
||||
auto& jvalue = jkv.value();
|
||||
switch (jvalue.type()) {
|
||||
case wpi::json::value_t::boolean:
|
||||
valuePtr->Reset(Value::kBool);
|
||||
valuePtr->boolVal = jvalue.get<bool>();
|
||||
break;
|
||||
case wpi::json::value_t::number_float:
|
||||
valuePtr->Reset(Value::kDouble);
|
||||
valuePtr->doubleVal = jvalue.get<double>();
|
||||
break;
|
||||
case wpi::json::value_t::number_integer:
|
||||
valuePtr->Reset(Value::kInt64);
|
||||
valuePtr->int64Val = jvalue.get<int64_t>();
|
||||
break;
|
||||
case wpi::json::value_t::number_unsigned:
|
||||
valuePtr->Reset(Value::kInt64);
|
||||
valuePtr->int64Val = jvalue.get<uint64_t>();
|
||||
break;
|
||||
case wpi::json::value_t::string:
|
||||
valuePtr->Reset(Value::kString);
|
||||
valuePtr->stringVal = jvalue.get_ref<const std::string&>();
|
||||
break;
|
||||
case wpi::json::value_t::object:
|
||||
if (valuePtr->type != Value::kChild) {
|
||||
valuePtr->Reset(Value::kChild);
|
||||
valuePtr->child = new Storage;
|
||||
}
|
||||
valuePtr->child->FromJson(jvalue, filename); // recurse
|
||||
break;
|
||||
case wpi::json::value_t::array:
|
||||
if (!JsonArrayToStorage(valuePtr.get(), jvalue, filename)) {
|
||||
if (created) {
|
||||
m_values.erase(jkv.key());
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
ImGui::LogText("null value in %s, ignoring", filename);
|
||||
if (created) {
|
||||
m_values.erase(jkv.key());
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static wpi::json StorageToJsonArray(const std::vector<T>& arr) {
|
||||
wpi::json jarr = wpi::json::array();
|
||||
for (auto&& v : arr) {
|
||||
jarr.emplace_back(v);
|
||||
}
|
||||
return jarr;
|
||||
}
|
||||
|
||||
template <>
|
||||
wpi::json StorageToJsonArray<std::unique_ptr<Storage>>(
|
||||
const std::vector<std::unique_ptr<Storage>>& arr) {
|
||||
wpi::json jarr = wpi::json::array();
|
||||
for (auto&& v : arr) {
|
||||
jarr.emplace_back(v->ToJson());
|
||||
}
|
||||
// remove any trailing empty items
|
||||
while (!jarr.empty() && jarr.back().empty()) {
|
||||
jarr.get_ref<wpi::json::array_t&>().pop_back();
|
||||
}
|
||||
return jarr;
|
||||
}
|
||||
|
||||
wpi::json Storage::ToJson() const {
|
||||
if (m_toJson) {
|
||||
return m_toJson();
|
||||
}
|
||||
|
||||
wpi::json j = wpi::json::object();
|
||||
for (auto&& kv : m_values) {
|
||||
wpi::json jelem;
|
||||
auto& value = *kv.getValue();
|
||||
switch (value.type) {
|
||||
#define CASE(CapsName, LowerName) \
|
||||
case Value::k##CapsName: \
|
||||
if (value.hasDefault && \
|
||||
value.LowerName##Val == value.LowerName##Default) { \
|
||||
continue; \
|
||||
} \
|
||||
jelem = value.LowerName##Val; \
|
||||
break; \
|
||||
case Value::k##CapsName##Array: \
|
||||
if (value.hasDefault && \
|
||||
((!value.LowerName##ArrayDefault && \
|
||||
value.LowerName##Array->empty()) || \
|
||||
(value.LowerName##ArrayDefault && \
|
||||
*value.LowerName##Array == *value.LowerName##ArrayDefault))) { \
|
||||
continue; \
|
||||
} \
|
||||
jelem = StorageToJsonArray(*value.LowerName##Array); \
|
||||
break;
|
||||
|
||||
CASE(Int, int)
|
||||
CASE(Int64, int64)
|
||||
CASE(Bool, bool)
|
||||
CASE(Float, float)
|
||||
CASE(Double, double)
|
||||
CASE(String, string)
|
||||
|
||||
case Value::kChild:
|
||||
jelem = value.child->ToJson(); // recurse
|
||||
if (jelem.empty()) {
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
case Value::kChildArray:
|
||||
jelem = StorageToJsonArray(*value.childArray);
|
||||
if (jelem.empty()) {
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
j.emplace(kv.getKey(), std::move(jelem));
|
||||
}
|
||||
return j;
|
||||
}
|
||||
|
||||
void Storage::Clear() {
|
||||
if (m_clear) {
|
||||
return m_clear();
|
||||
}
|
||||
|
||||
ClearValues();
|
||||
}
|
||||
|
||||
void Storage::ClearValues() {
|
||||
for (auto&& kv : m_values) {
|
||||
auto& value = *kv.getValue();
|
||||
switch (value.type) {
|
||||
case Value::kInt:
|
||||
value.intVal = value.intDefault;
|
||||
break;
|
||||
case Value::kInt64:
|
||||
value.int64Val = value.int64Default;
|
||||
break;
|
||||
case Value::kBool:
|
||||
value.boolVal = value.boolDefault;
|
||||
break;
|
||||
case Value::kFloat:
|
||||
value.floatVal = value.floatDefault;
|
||||
break;
|
||||
case Value::kDouble:
|
||||
value.doubleVal = value.doubleDefault;
|
||||
break;
|
||||
case Value::kString:
|
||||
value.stringVal = value.stringDefault;
|
||||
break;
|
||||
case Value::kIntArray:
|
||||
*value.intArray = *value.intArrayDefault;
|
||||
break;
|
||||
case Value::kInt64Array:
|
||||
*value.int64Array = *value.int64ArrayDefault;
|
||||
break;
|
||||
case Value::kBoolArray:
|
||||
*value.boolArray = *value.boolArrayDefault;
|
||||
break;
|
||||
case Value::kFloatArray:
|
||||
*value.floatArray = *value.floatArrayDefault;
|
||||
break;
|
||||
case Value::kDoubleArray:
|
||||
*value.doubleArray = *value.doubleArrayDefault;
|
||||
break;
|
||||
case Value::kStringArray:
|
||||
*value.stringArray = *value.stringArrayDefault;
|
||||
break;
|
||||
case Value::kChild:
|
||||
value.child->Clear();
|
||||
break;
|
||||
case Value::kChildArray:
|
||||
for (auto&& child : *value.childArray) {
|
||||
child->Clear();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Storage::Apply() {
|
||||
if (m_apply) {
|
||||
return m_apply();
|
||||
}
|
||||
|
||||
ApplyChildren();
|
||||
}
|
||||
|
||||
void Storage::ApplyChildren() {
|
||||
for (auto&& kv : m_values) {
|
||||
auto& value = *kv.getValue();
|
||||
switch (value.type) {
|
||||
case Value::kChild:
|
||||
value.child->Apply();
|
||||
break;
|
||||
case Value::kChildArray:
|
||||
for (auto&& child : *value.childArray) {
|
||||
child->Apply();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,23 +8,28 @@
|
||||
#include <wpi/StringExtras.h>
|
||||
|
||||
#include "glass/Context.h"
|
||||
#include "glass/Storage.h"
|
||||
|
||||
using namespace glass;
|
||||
|
||||
Window::Window(Storage& storage, std::string_view id,
|
||||
Visibility defaultVisibility)
|
||||
: m_id{id},
|
||||
m_name{storage.GetString("name")},
|
||||
m_defaultName{id},
|
||||
m_visible{storage.GetBool("visible", defaultVisibility != kHide)},
|
||||
m_enabled{storage.GetBool("enabled", defaultVisibility != kDisabled)},
|
||||
m_defaultVisible{storage.GetValue("visible").boolDefault},
|
||||
m_defaultEnabled{storage.GetValue("enabled").boolDefault} {}
|
||||
|
||||
void Window::SetVisibility(Visibility visibility) {
|
||||
switch (visibility) {
|
||||
case kHide:
|
||||
m_visible = false;
|
||||
m_enabled = true;
|
||||
break;
|
||||
case kShow:
|
||||
m_visible = true;
|
||||
m_enabled = true;
|
||||
break;
|
||||
case kDisabled:
|
||||
m_enabled = false;
|
||||
break;
|
||||
}
|
||||
m_visible = visibility != kHide;
|
||||
m_enabled = visibility != kDisabled;
|
||||
}
|
||||
|
||||
void Window::SetDefaultVisibility(Visibility visibility) {
|
||||
m_defaultVisible = visibility != kHide;
|
||||
m_defaultEnabled = visibility != kDisabled;
|
||||
}
|
||||
|
||||
void Window::Display() {
|
||||
@@ -85,27 +90,3 @@ void Window::ScaleDefault(float scale) {
|
||||
m_size.y *= scale;
|
||||
}
|
||||
}
|
||||
|
||||
void Window::IniReadLine(const char* line) {
|
||||
auto [name, value] = wpi::split(line, '=');
|
||||
name = wpi::trim(name);
|
||||
value = wpi::trim(value);
|
||||
|
||||
if (name == "name") {
|
||||
m_name = value;
|
||||
} else if (name == "visible") {
|
||||
if (auto num = wpi::parse_integer<int>(value, 10)) {
|
||||
m_visible = num.value();
|
||||
}
|
||||
} else if (name == "enabled") {
|
||||
if (auto num = wpi::parse_integer<int>(value, 10)) {
|
||||
m_enabled = num.value();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Window::IniWriteAll(const char* typeName, ImGuiTextBuffer* out_buf) {
|
||||
out_buf->appendf("[%s][%s]\nname=%s\nvisible=%d\nenabled=%d\n\n", typeName,
|
||||
m_id.c_str(), m_name.c_str(), m_visible ? 1 : 0,
|
||||
m_enabled ? 1 : 0);
|
||||
}
|
||||
|
||||
@@ -10,30 +10,23 @@
|
||||
#include <fmt/format.h>
|
||||
#include <wpigui.h>
|
||||
|
||||
#include "glass/Context.h"
|
||||
#include "glass/Storage.h"
|
||||
|
||||
using namespace glass;
|
||||
|
||||
WindowManager::WindowManager(std::string_view iniName)
|
||||
: m_iniSaver{iniName, this} {}
|
||||
|
||||
// read/write open state to ini file
|
||||
void* WindowManager::IniSaver::IniReadOpen(const char* name) {
|
||||
return m_manager->GetOrAddWindow(name, true);
|
||||
}
|
||||
|
||||
void WindowManager::IniSaver::IniReadLine(void* entry, const char* lineStr) {
|
||||
static_cast<Window*>(entry)->IniReadLine(lineStr);
|
||||
}
|
||||
|
||||
void WindowManager::IniSaver::IniWriteAll(ImGuiTextBuffer* out_buf) {
|
||||
const char* typeName = GetTypeName();
|
||||
for (auto&& window : m_manager->m_windows) {
|
||||
window->IniWriteAll(typeName, out_buf);
|
||||
}
|
||||
WindowManager::WindowManager(Storage& storage) : m_storage{storage} {
|
||||
storage.SetCustomApply([this] {
|
||||
for (auto&& childIt : m_storage.GetChildren()) {
|
||||
GetOrAddWindow(childIt.key(), true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Window* WindowManager::AddWindow(std::string_view id,
|
||||
wpi::unique_function<void()> display) {
|
||||
auto win = GetOrAddWindow(id, false);
|
||||
wpi::unique_function<void()> display,
|
||||
Window::Visibility defaultVisibility) {
|
||||
auto win = GetOrAddWindow(id, false, defaultVisibility);
|
||||
if (!win) {
|
||||
return nullptr;
|
||||
}
|
||||
@@ -46,8 +39,9 @@ Window* WindowManager::AddWindow(std::string_view id,
|
||||
}
|
||||
|
||||
Window* WindowManager::AddWindow(std::string_view id,
|
||||
std::unique_ptr<View> view) {
|
||||
auto win = GetOrAddWindow(id, false);
|
||||
std::unique_ptr<View> view,
|
||||
Window::Visibility defaultVisibility) {
|
||||
auto win = GetOrAddWindow(id, false, defaultVisibility);
|
||||
if (!win) {
|
||||
return nullptr;
|
||||
}
|
||||
@@ -59,7 +53,8 @@ Window* WindowManager::AddWindow(std::string_view id,
|
||||
return win;
|
||||
}
|
||||
|
||||
Window* WindowManager::GetOrAddWindow(std::string_view id, bool duplicateOk) {
|
||||
Window* WindowManager::GetOrAddWindow(std::string_view id, bool duplicateOk,
|
||||
Window::Visibility defaultVisibility) {
|
||||
// binary search
|
||||
auto it = std::lower_bound(
|
||||
m_windows.begin(), m_windows.end(), id,
|
||||
@@ -72,7 +67,11 @@ Window* WindowManager::GetOrAddWindow(std::string_view id, bool duplicateOk) {
|
||||
return it->get();
|
||||
}
|
||||
// insert before (keeps sort)
|
||||
return m_windows.emplace(it, std::make_unique<Window>(id))->get();
|
||||
return m_windows
|
||||
.emplace(it, std::make_unique<Window>(
|
||||
m_storage.GetChild(id).GetChild("window"), id,
|
||||
defaultVisibility))
|
||||
->get();
|
||||
}
|
||||
|
||||
Window* WindowManager::GetWindow(std::string_view id) {
|
||||
@@ -86,8 +85,12 @@ Window* WindowManager::GetWindow(std::string_view id) {
|
||||
return it->get();
|
||||
}
|
||||
|
||||
void WindowManager::RemoveWindow(size_t index) {
|
||||
m_storage.Erase(m_windows[index]->GetId());
|
||||
m_windows.erase(m_windows.begin() + index);
|
||||
}
|
||||
|
||||
void WindowManager::GlobalInit() {
|
||||
wpi::gui::AddInit([this] { m_iniSaver.Initialize(); });
|
||||
wpi::gui::AddWindowScaler([this](float scale) {
|
||||
// scale default window positions
|
||||
for (auto&& window : m_windows) {
|
||||
@@ -104,7 +107,9 @@ void WindowManager::DisplayMenu() {
|
||||
}
|
||||
|
||||
void WindowManager::DisplayWindows() {
|
||||
PushStorageStack(m_storage);
|
||||
for (auto&& window : m_windows) {
|
||||
window->Display();
|
||||
}
|
||||
PopStorageStack();
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
#include "glass/Context.h"
|
||||
#include "glass/DataSource.h"
|
||||
#include "glass/Storage.h"
|
||||
|
||||
using namespace glass;
|
||||
|
||||
@@ -18,10 +19,10 @@ void glass::DisplayAnalogInput(AnalogInputModel* model, int index) {
|
||||
}
|
||||
|
||||
// build label
|
||||
std::string* name = GetStorage().GetStringRef("name");
|
||||
std::string& name = GetStorage().GetString("name");
|
||||
char label[128];
|
||||
if (!name->empty()) {
|
||||
std::snprintf(label, sizeof(label), "%s [%d]###name", name->c_str(), index);
|
||||
if (!name.empty()) {
|
||||
std::snprintf(label, sizeof(label), "%s [%d]###name", name.c_str(), index);
|
||||
} else {
|
||||
std::snprintf(label, sizeof(label), "In[%d]###name", index);
|
||||
}
|
||||
@@ -42,8 +43,8 @@ void glass::DisplayAnalogInput(AnalogInputModel* model, int index) {
|
||||
}
|
||||
|
||||
// context menu to change name
|
||||
if (PopupEditName("name", name)) {
|
||||
voltageData->SetName(name->c_str());
|
||||
if (PopupEditName("name", &name)) {
|
||||
voltageData->SetName(name);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
#include "glass/Context.h"
|
||||
#include "glass/DataSource.h"
|
||||
#include "glass/Storage.h"
|
||||
#include "glass/other/DeviceTree.h"
|
||||
|
||||
using namespace glass;
|
||||
@@ -26,10 +27,10 @@ void glass::DisplayAnalogOutputsDevice(AnalogOutputsModel* model) {
|
||||
PushID(i);
|
||||
|
||||
// build label
|
||||
std::string* name = GetStorage().GetStringRef("name");
|
||||
std::string& name = GetStorage().GetString("name");
|
||||
char label[128];
|
||||
if (!name->empty()) {
|
||||
std::snprintf(label, sizeof(label), "%s [%d]###name", name->c_str(), i);
|
||||
if (!name.empty()) {
|
||||
std::snprintf(label, sizeof(label), "%s [%d]###name", name.c_str(), i);
|
||||
} else {
|
||||
std::snprintf(label, sizeof(label), "Out[%d]###name", i);
|
||||
}
|
||||
@@ -37,9 +38,9 @@ void glass::DisplayAnalogOutputsDevice(AnalogOutputsModel* model) {
|
||||
double value = analogOutData->GetValue();
|
||||
DeviceDouble(label, true, &value, analogOutData);
|
||||
|
||||
if (PopupEditName("name", name)) {
|
||||
if (PopupEditName("name", &name)) {
|
||||
if (analogOutData) {
|
||||
analogOutData->SetName(name->c_str());
|
||||
analogOutData->SetName(name);
|
||||
}
|
||||
}
|
||||
PopID();
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
#include "glass/DataSource.h"
|
||||
#include "glass/hardware/Encoder.h"
|
||||
#include "glass/support/IniSaverInfo.h"
|
||||
#include "glass/support/NameSetting.h"
|
||||
|
||||
using namespace glass;
|
||||
|
||||
@@ -28,17 +28,18 @@ void DisplayDIOImpl(DIOModel* model, int index, bool outputsEnabled) {
|
||||
auto dutyCycleData = dutyCycle ? dutyCycle->GetValueData() : nullptr;
|
||||
|
||||
bool exists = model->Exists();
|
||||
auto& info = dioData->GetNameInfo();
|
||||
NameSetting dioName{dioData->GetName()};
|
||||
char label[128];
|
||||
if (exists && dpwmData) {
|
||||
dpwmData->GetNameInfo().GetLabel(label, sizeof(label), "PWM", index);
|
||||
NameSetting{dpwmData->GetName()}.GetLabel(label, sizeof(label), "PWM",
|
||||
index);
|
||||
if (auto simDevice = dpwm->GetSimDevice()) {
|
||||
LabelSimDevice(label, simDevice);
|
||||
} else {
|
||||
dpwmData->LabelText(label, "%0.3f", dpwmData->GetValue());
|
||||
}
|
||||
} else if (exists && encoder) {
|
||||
info.GetLabel(label, sizeof(label), " In", index);
|
||||
dioName.GetLabel(label, sizeof(label), " In", index);
|
||||
if (auto simDevice = encoder->GetSimDevice()) {
|
||||
LabelSimDevice(label, simDevice);
|
||||
} else {
|
||||
@@ -48,7 +49,8 @@ void DisplayDIOImpl(DIOModel* model, int index, bool outputsEnabled) {
|
||||
ImGui::PopStyleColor();
|
||||
}
|
||||
} else if (exists && dutyCycleData) {
|
||||
dutyCycleData->GetNameInfo().GetLabel(label, sizeof(label), "Dty", index);
|
||||
NameSetting{dutyCycleData->GetName()}.GetLabel(label, sizeof(label), "Dty",
|
||||
index);
|
||||
if (auto simDevice = dutyCycle->GetSimDevice()) {
|
||||
LabelSimDevice(label, simDevice);
|
||||
} else {
|
||||
@@ -60,10 +62,10 @@ void DisplayDIOImpl(DIOModel* model, int index, bool outputsEnabled) {
|
||||
} else {
|
||||
const char* name = model->GetName();
|
||||
if (name[0] != '\0') {
|
||||
info.GetLabel(label, sizeof(label), name);
|
||||
dioName.GetLabel(label, sizeof(label), name);
|
||||
} else {
|
||||
info.GetLabel(label, sizeof(label), model->IsInput() ? " In" : "Out",
|
||||
index);
|
||||
dioName.GetLabel(label, sizeof(label), model->IsInput() ? " In" : "Out",
|
||||
index);
|
||||
}
|
||||
if (auto simDevice = model->GetSimDevice()) {
|
||||
LabelSimDevice(label, simDevice);
|
||||
@@ -87,12 +89,12 @@ void DisplayDIOImpl(DIOModel* model, int index, bool outputsEnabled) {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (info.PopupEditName(index)) {
|
||||
if (dioName.PopupEditName(index)) {
|
||||
if (dpwmData) {
|
||||
dpwmData->SetName(info.GetName());
|
||||
dpwmData->SetName(dioName.GetName());
|
||||
}
|
||||
if (dutyCycleData) {
|
||||
dutyCycleData->SetName(info.GetName());
|
||||
dutyCycleData->SetName(dioName.GetName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
|
||||
#include "glass/Context.h"
|
||||
#include "glass/DataSource.h"
|
||||
#include "glass/Storage.h"
|
||||
|
||||
using namespace glass;
|
||||
|
||||
@@ -66,10 +67,10 @@ void glass::DisplayEncoder(EncoderModel* model) {
|
||||
int chB = model->GetChannelB();
|
||||
|
||||
// build header label
|
||||
std::string* name = GetStorage().GetStringRef("name");
|
||||
std::string& name = GetStorage().GetString("name");
|
||||
char label[128];
|
||||
if (!name->empty()) {
|
||||
std::snprintf(label, sizeof(label), "%s [%d,%d]###name", name->c_str(), chA,
|
||||
if (!name.empty()) {
|
||||
std::snprintf(label, sizeof(label), "%s [%d,%d]###name", name.c_str(), chA,
|
||||
chB);
|
||||
} else {
|
||||
std::snprintf(label, sizeof(label), "Encoder[%d,%d]###name", chA, chB);
|
||||
@@ -79,8 +80,8 @@ void glass::DisplayEncoder(EncoderModel* model) {
|
||||
bool open = CollapsingHeader(label);
|
||||
|
||||
// context menu to change name
|
||||
if (PopupEditName("name", name)) {
|
||||
model->SetName(name->c_str());
|
||||
if (PopupEditName("name", &name)) {
|
||||
model->SetName(name);
|
||||
}
|
||||
|
||||
if (!open) {
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <wpi/SmallVector.h>
|
||||
|
||||
#include "glass/Context.h"
|
||||
#include "glass/Storage.h"
|
||||
#include "glass/support/ExtraGuiWidgets.h"
|
||||
|
||||
using namespace glass;
|
||||
@@ -25,27 +26,27 @@ void glass::DisplayLEDDisplay(LEDDisplayModel* model, int index) {
|
||||
bool running = model->IsRunning();
|
||||
auto& storage = GetStorage();
|
||||
|
||||
int* numColumns = storage.GetIntRef("columns", 10);
|
||||
bool* serpentine = storage.GetBoolRef("serpentine", false);
|
||||
int* order = storage.GetIntRef("order", LEDConfig::RowMajor);
|
||||
int* start = storage.GetIntRef("start", LEDConfig::UpperLeft);
|
||||
int& numColumns = storage.GetInt("columns", 10);
|
||||
bool& serpentine = storage.GetBool("serpentine", false);
|
||||
int& order = storage.GetInt("order", LEDConfig::RowMajor);
|
||||
int& start = storage.GetInt("start", LEDConfig::UpperLeft);
|
||||
|
||||
ImGui::PushItemWidth(ImGui::GetFontSize() * 6);
|
||||
ImGui::LabelText("Length", "%d", length);
|
||||
ImGui::LabelText("Running", "%s", running ? "Yes" : "No");
|
||||
ImGui::InputInt("Columns", numColumns);
|
||||
ImGui::InputInt("Columns", &numColumns);
|
||||
{
|
||||
static const char* options[] = {"Row Major", "Column Major"};
|
||||
ImGui::Combo("Order", order, options, 2);
|
||||
ImGui::Combo("Order", &order, options, 2);
|
||||
}
|
||||
{
|
||||
static const char* options[] = {"Upper Left", "Lower Left", "Upper Right",
|
||||
"Lower Right"};
|
||||
ImGui::Combo("Start", start, options, 4);
|
||||
ImGui::Combo("Start", &start, options, 4);
|
||||
}
|
||||
ImGui::Checkbox("Serpentine", serpentine);
|
||||
if (*numColumns < 1) {
|
||||
*numColumns = 1;
|
||||
ImGui::Checkbox("Serpentine", &serpentine);
|
||||
if (numColumns < 1) {
|
||||
numColumns = 1;
|
||||
}
|
||||
ImGui::PopItemWidth();
|
||||
|
||||
@@ -74,12 +75,12 @@ void glass::DisplayLEDDisplay(LEDDisplayModel* model, int index) {
|
||||
}
|
||||
|
||||
LEDConfig config;
|
||||
config.serpentine = *serpentine;
|
||||
config.order = static_cast<LEDConfig::Order>(*order);
|
||||
config.start = static_cast<LEDConfig::Start>(*start);
|
||||
config.serpentine = serpentine;
|
||||
config.order = static_cast<LEDConfig::Order>(order);
|
||||
config.start = static_cast<LEDConfig::Start>(start);
|
||||
|
||||
DrawLEDs(iData->values.data(), length, *numColumns, iData->colors.data(), 0,
|
||||
0, config);
|
||||
DrawLEDs(iData->values.data(), length, numColumns, iData->colors.data(), 0, 0,
|
||||
config);
|
||||
}
|
||||
|
||||
void glass::DisplayLEDDisplays(LEDDisplaysModel* model) {
|
||||
|
||||
@@ -12,9 +12,10 @@
|
||||
|
||||
#include "glass/Context.h"
|
||||
#include "glass/DataSource.h"
|
||||
#include "glass/Storage.h"
|
||||
#include "glass/other/DeviceTree.h"
|
||||
#include "glass/support/ExtraGuiWidgets.h"
|
||||
#include "glass/support/IniSaverInfo.h"
|
||||
#include "glass/support/NameSetting.h"
|
||||
|
||||
using namespace glass;
|
||||
|
||||
@@ -42,10 +43,10 @@ bool glass::DisplayPCMSolenoids(PCMModel* model, int index,
|
||||
}
|
||||
|
||||
// build header label
|
||||
std::string* name = GetStorage().GetStringRef("name");
|
||||
std::string& name = GetStorage().GetString("name");
|
||||
char label[128];
|
||||
if (!name->empty()) {
|
||||
std::snprintf(label, sizeof(label), "%s [%d]###name", name->c_str(), index);
|
||||
if (!name.empty()) {
|
||||
std::snprintf(label, sizeof(label), "%s [%d]###name", name.c_str(), index);
|
||||
} else {
|
||||
std::snprintf(label, sizeof(label), "PCM[%d]###name", index);
|
||||
}
|
||||
@@ -53,7 +54,7 @@ bool glass::DisplayPCMSolenoids(PCMModel* model, int index,
|
||||
// header
|
||||
bool open = CollapsingHeader(label);
|
||||
|
||||
PopupEditName("name", name);
|
||||
PopupEditName("name", &name);
|
||||
|
||||
ImGui::SetItemAllowOverlap();
|
||||
ImGui::SameLine();
|
||||
@@ -68,11 +69,11 @@ bool glass::DisplayPCMSolenoids(PCMModel* model, int index,
|
||||
model->ForEachSolenoid([&](SolenoidModel& solenoid, int j) {
|
||||
if (auto data = solenoid.GetOutputData()) {
|
||||
PushID(j);
|
||||
char solenoidName[64];
|
||||
auto& info = data->GetNameInfo();
|
||||
info.GetLabel(solenoidName, sizeof(solenoidName), "Solenoid", j);
|
||||
data->LabelText(solenoidName, "%s", channels[j] == 1 ? "On" : "Off");
|
||||
info.PopupEditName(j);
|
||||
char label[64];
|
||||
NameSetting name{data->GetName()};
|
||||
name.GetLabel(label, sizeof(label), "Solenoid", j);
|
||||
data->LabelText(label, "%s", channels[j] == 1 ? "On" : "Off");
|
||||
name.PopupEditName(j);
|
||||
PopID();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
#include "glass/Context.h"
|
||||
#include "glass/DataSource.h"
|
||||
#include "glass/Storage.h"
|
||||
|
||||
using namespace glass;
|
||||
|
||||
@@ -18,10 +19,10 @@ void glass::DisplayPWM(PWMModel* model, int index, bool outputsEnabled) {
|
||||
}
|
||||
|
||||
// build label
|
||||
std::string* name = GetStorage().GetStringRef("name");
|
||||
std::string& name = GetStorage().GetString("name");
|
||||
char label[128];
|
||||
if (!name->empty()) {
|
||||
std::snprintf(label, sizeof(label), "%s [%d]###name", name->c_str(), index);
|
||||
if (!name.empty()) {
|
||||
std::snprintf(label, sizeof(label), "%s [%d]###name", name.c_str(), index);
|
||||
} else {
|
||||
std::snprintf(label, sizeof(label), "PWM[%d]###name", index);
|
||||
}
|
||||
@@ -35,8 +36,8 @@ void glass::DisplayPWM(PWMModel* model, int index, bool outputsEnabled) {
|
||||
float val = outputsEnabled ? data->GetValue() : 0;
|
||||
data->LabelText(label, "%0.3f", val);
|
||||
}
|
||||
if (PopupEditName("name", name)) {
|
||||
data->SetName(name->c_str());
|
||||
if (PopupEditName("name", &name)) {
|
||||
data->SetName(name);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
#include "glass/Context.h"
|
||||
#include "glass/DataSource.h"
|
||||
#include "glass/support/IniSaverInfo.h"
|
||||
#include "glass/support/NameSetting.h"
|
||||
|
||||
using namespace glass;
|
||||
|
||||
@@ -19,16 +19,16 @@ static float DisplayChannel(PowerDistributionModel& pdp, int channel) {
|
||||
float width = 0;
|
||||
if (auto currentData = pdp.GetCurrentData(channel)) {
|
||||
ImGui::PushID(channel);
|
||||
auto& leftInfo = currentData->GetNameInfo();
|
||||
NameSetting leftName{currentData->GetName()};
|
||||
char name[64];
|
||||
leftInfo.GetLabel(name, sizeof(name), "", channel);
|
||||
leftName.GetLabel(name, sizeof(name), "", channel);
|
||||
double val = currentData->GetValue();
|
||||
ImGui::SetNextItemWidth(ImGui::GetFontSize() * 4);
|
||||
if (currentData->InputDouble(name, &val, 0, 0, "%.3f")) {
|
||||
pdp.SetCurrent(channel, val);
|
||||
}
|
||||
width = ImGui::GetItemRectSize().x;
|
||||
leftInfo.PopupEditName(channel);
|
||||
leftName.PopupEditName(channel);
|
||||
ImGui::PopID();
|
||||
}
|
||||
return width;
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
#include "glass/Context.h"
|
||||
#include "glass/DataSource.h"
|
||||
#include "glass/Storage.h"
|
||||
#include "glass/support/ExtraGuiWidgets.h"
|
||||
|
||||
using namespace glass;
|
||||
@@ -31,20 +32,20 @@ void glass::DisplayRelay(RelayModel* model, int index, bool outputsEnabled) {
|
||||
}
|
||||
}
|
||||
|
||||
std::string* name = GetStorage().GetStringRef("name");
|
||||
std::string& name = GetStorage().GetString("name");
|
||||
ImGui::PushID("name");
|
||||
if (!name->empty()) {
|
||||
ImGui::Text("%s [%d]", name->c_str(), index);
|
||||
if (!name.empty()) {
|
||||
ImGui::Text("%s [%d]", name.c_str(), index);
|
||||
} else {
|
||||
ImGui::Text("Relay[%d]", index);
|
||||
}
|
||||
ImGui::PopID();
|
||||
if (PopupEditName("name", name)) {
|
||||
if (PopupEditName("name", &name)) {
|
||||
if (forwardData) {
|
||||
forwardData->SetName(name->c_str());
|
||||
forwardData->SetName(name);
|
||||
}
|
||||
if (reverseData) {
|
||||
reverseData->SetName(name->c_str());
|
||||
reverseData->SetName(name);
|
||||
}
|
||||
}
|
||||
ImGui::SameLine();
|
||||
|
||||
@@ -51,13 +51,13 @@ bool glass::BeginDevice(const char* id, ImGuiTreeNodeFlags flags) {
|
||||
PushID(id);
|
||||
|
||||
// build label
|
||||
std::string* name = GetStorage().GetStringRef("name");
|
||||
std::string& name = GetStorage().GetString("name");
|
||||
char label[128];
|
||||
std::snprintf(label, sizeof(label), "%s###name",
|
||||
name->empty() ? id : name->c_str());
|
||||
name.empty() ? id : name.c_str());
|
||||
|
||||
bool open = CollapsingHeader(label, flags);
|
||||
PopupEditName("name", name);
|
||||
PopupEditName("name", &name);
|
||||
|
||||
if (!open) {
|
||||
PopID();
|
||||
|
||||
@@ -32,6 +32,9 @@
|
||||
#include <wpigui.h>
|
||||
|
||||
#include "glass/Context.h"
|
||||
#include "glass/Storage.h"
|
||||
#include "glass/support/ColorSetting.h"
|
||||
#include "glass/support/EnumSetting.h"
|
||||
|
||||
using namespace glass;
|
||||
|
||||
@@ -114,12 +117,14 @@ struct DisplayOptions {
|
||||
|
||||
static constexpr Style kDefaultStyle = kBoxImage;
|
||||
static constexpr float kDefaultWeight = 4.0f;
|
||||
static constexpr float kDefaultColorFloat[] = {255, 0, 0, 255};
|
||||
static constexpr ImU32 kDefaultColor = IM_COL32(255, 0, 0, 255);
|
||||
static constexpr auto kDefaultWidth = 0.6858_m;
|
||||
static constexpr auto kDefaultLength = 0.8204_m;
|
||||
static constexpr bool kDefaultArrows = true;
|
||||
static constexpr int kDefaultArrowSize = 50;
|
||||
static constexpr float kDefaultArrowWeight = 4.0f;
|
||||
static constexpr float kDefaultArrowColorFloat[] = {0, 255, 0, 255};
|
||||
static constexpr ImU32 kDefaultArrowColor = IM_COL32(0, 255, 0, 255);
|
||||
static constexpr bool kDefaultSelectable = true;
|
||||
|
||||
@@ -180,7 +185,7 @@ class PoseFrameData {
|
||||
|
||||
class ObjectInfo {
|
||||
public:
|
||||
ObjectInfo();
|
||||
explicit ObjectInfo(Storage& storage);
|
||||
|
||||
DisplayOptions GetDisplayOptions() const;
|
||||
void DisplaySettings();
|
||||
@@ -191,26 +196,26 @@ class ObjectInfo {
|
||||
|
||||
private:
|
||||
void Reset();
|
||||
bool LoadImageImpl(const char* fn);
|
||||
bool LoadImageImpl(const std::string& fn);
|
||||
|
||||
std::unique_ptr<pfd::open_file> m_fileOpener;
|
||||
|
||||
// in meters
|
||||
float* m_pWidth;
|
||||
float* m_pLength;
|
||||
float& m_width;
|
||||
float& m_length;
|
||||
|
||||
int* m_pStyle; // DisplayOptions::Style
|
||||
float* m_pWeight;
|
||||
int* m_pColor;
|
||||
EnumSetting m_style; // DisplayOptions::Style
|
||||
float& m_weight;
|
||||
ColorSetting m_color;
|
||||
|
||||
bool* m_pArrows;
|
||||
int* m_pArrowSize;
|
||||
float* m_pArrowWeight;
|
||||
int* m_pArrowColor;
|
||||
bool& m_arrows;
|
||||
int& m_arrowSize;
|
||||
float& m_arrowWeight;
|
||||
ColorSetting m_arrowColor;
|
||||
|
||||
bool* m_pSelectable;
|
||||
bool& m_selectable;
|
||||
|
||||
std::string* m_pFilename;
|
||||
std::string& m_filename;
|
||||
gui::Texture m_texture;
|
||||
};
|
||||
|
||||
@@ -219,7 +224,7 @@ class FieldInfo {
|
||||
static constexpr auto kDefaultWidth = 15.98_m;
|
||||
static constexpr auto kDefaultHeight = 8.21_m;
|
||||
|
||||
FieldInfo();
|
||||
explicit FieldInfo(Storage& storage);
|
||||
|
||||
void DisplaySettings();
|
||||
|
||||
@@ -231,25 +236,25 @@ class FieldInfo {
|
||||
|
||||
private:
|
||||
void Reset();
|
||||
bool LoadImageImpl(const char* fn);
|
||||
bool LoadImageImpl(const std::string& fn);
|
||||
void LoadJson(std::string_view jsonfile);
|
||||
|
||||
std::unique_ptr<pfd::open_file> m_fileOpener;
|
||||
|
||||
std::string* m_pFilename;
|
||||
std::string& m_filename;
|
||||
gui::Texture m_texture;
|
||||
|
||||
// in meters
|
||||
float* m_pWidth;
|
||||
float* m_pHeight;
|
||||
float& m_width;
|
||||
float& m_height;
|
||||
|
||||
// in image pixels
|
||||
int m_imageWidth;
|
||||
int m_imageHeight;
|
||||
int* m_pTop;
|
||||
int* m_pLeft;
|
||||
int* m_pBottom;
|
||||
int* m_pRight;
|
||||
int& m_top;
|
||||
int& m_left;
|
||||
int& m_bottom;
|
||||
int& m_right;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
@@ -334,16 +339,14 @@ static bool InputPose(frc::Pose2d* pose) {
|
||||
return changed;
|
||||
}
|
||||
|
||||
FieldInfo::FieldInfo() {
|
||||
auto& storage = GetStorage();
|
||||
m_pFilename = storage.GetStringRef("image");
|
||||
m_pTop = storage.GetIntRef("top", 0);
|
||||
m_pLeft = storage.GetIntRef("left", 0);
|
||||
m_pBottom = storage.GetIntRef("bottom", -1);
|
||||
m_pRight = storage.GetIntRef("right", -1);
|
||||
m_pWidth = storage.GetFloatRef("width", kDefaultWidth.to<float>());
|
||||
m_pHeight = storage.GetFloatRef("height", kDefaultHeight.to<float>());
|
||||
}
|
||||
FieldInfo::FieldInfo(Storage& storage)
|
||||
: m_filename{storage.GetString("image")},
|
||||
m_width{storage.GetFloat("width", kDefaultWidth.to<float>())},
|
||||
m_height{storage.GetFloat("height", kDefaultHeight.to<float>())},
|
||||
m_top{storage.GetInt("top", 0)},
|
||||
m_left{storage.GetInt("left", 0)},
|
||||
m_bottom{storage.GetInt("bottom", -1)},
|
||||
m_right{storage.GetInt("right", -1)} {}
|
||||
|
||||
void FieldInfo::DisplaySettings() {
|
||||
if (ImGui::Button("Choose image...")) {
|
||||
@@ -357,23 +360,23 @@ void FieldInfo::DisplaySettings() {
|
||||
if (ImGui::Button("Reset image")) {
|
||||
Reset();
|
||||
}
|
||||
InputFloatLength("Field Width", m_pWidth);
|
||||
InputFloatLength("Field Height", m_pHeight);
|
||||
// ImGui::InputInt("Field Top", m_pTop);
|
||||
// ImGui::InputInt("Field Left", m_pLeft);
|
||||
// ImGui::InputInt("Field Right", m_pRight);
|
||||
// ImGui::InputInt("Field Bottom", m_pBottom);
|
||||
InputFloatLength("Field Width", &m_width);
|
||||
InputFloatLength("Field Height", &m_height);
|
||||
// ImGui::InputInt("Field Top", &m_top);
|
||||
// ImGui::InputInt("Field Left", &m_left);
|
||||
// ImGui::InputInt("Field Right", &m_right);
|
||||
// ImGui::InputInt("Field Bottom", &m_bottom);
|
||||
}
|
||||
|
||||
void FieldInfo::Reset() {
|
||||
m_texture = gui::Texture{};
|
||||
m_pFilename->clear();
|
||||
m_filename.clear();
|
||||
m_imageWidth = 0;
|
||||
m_imageHeight = 0;
|
||||
*m_pTop = 0;
|
||||
*m_pLeft = 0;
|
||||
*m_pBottom = -1;
|
||||
*m_pRight = -1;
|
||||
m_top = 0;
|
||||
m_left = 0;
|
||||
m_bottom = -1;
|
||||
m_right = -1;
|
||||
}
|
||||
|
||||
void FieldInfo::LoadImage() {
|
||||
@@ -384,17 +387,17 @@ void FieldInfo::LoadImage() {
|
||||
LoadJson(result[0]);
|
||||
} else {
|
||||
LoadImageImpl(result[0].c_str());
|
||||
*m_pTop = 0;
|
||||
*m_pLeft = 0;
|
||||
*m_pBottom = -1;
|
||||
*m_pRight = -1;
|
||||
m_top = 0;
|
||||
m_left = 0;
|
||||
m_bottom = -1;
|
||||
m_right = -1;
|
||||
}
|
||||
}
|
||||
m_fileOpener.reset();
|
||||
}
|
||||
if (!m_texture && !m_pFilename->empty()) {
|
||||
if (!LoadImageImpl(m_pFilename->c_str())) {
|
||||
m_pFilename->clear();
|
||||
if (!m_texture && !m_filename.empty()) {
|
||||
if (!LoadImageImpl(m_filename)) {
|
||||
m_filename.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -478,18 +481,18 @@ void FieldInfo::LoadJson(std::string_view jsonfile) {
|
||||
}
|
||||
|
||||
// save to field info
|
||||
*m_pFilename = pathname;
|
||||
*m_pTop = top;
|
||||
*m_pLeft = left;
|
||||
*m_pBottom = bottom;
|
||||
*m_pRight = right;
|
||||
*m_pWidth = width;
|
||||
*m_pHeight = height;
|
||||
m_filename = pathname;
|
||||
m_top = top;
|
||||
m_left = left;
|
||||
m_bottom = bottom;
|
||||
m_right = right;
|
||||
m_width = width;
|
||||
m_height = height;
|
||||
}
|
||||
|
||||
bool FieldInfo::LoadImageImpl(const char* fn) {
|
||||
bool FieldInfo::LoadImageImpl(const std::string& fn) {
|
||||
fmt::print("GUI: loading field image '{}'\n", fn);
|
||||
auto texture = gui::Texture::CreateFromFile(fn);
|
||||
auto texture = gui::Texture::CreateFromFile(fn.c_str());
|
||||
if (!texture) {
|
||||
std::puts("GUI: could not read field image");
|
||||
return false;
|
||||
@@ -497,7 +500,7 @@ bool FieldInfo::LoadImageImpl(const char* fn) {
|
||||
m_texture = std::move(texture);
|
||||
m_imageWidth = m_texture.GetWidth();
|
||||
m_imageHeight = m_texture.GetHeight();
|
||||
*m_pFilename = fn;
|
||||
m_filename = fn;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -512,19 +515,19 @@ FieldFrameData FieldInfo::GetFrameData(ImVec2 min, ImVec2 max) const {
|
||||
ffd.imageMax = max;
|
||||
|
||||
// size down the box by the image corners (if any)
|
||||
if (*m_pBottom > 0 && *m_pRight > 0) {
|
||||
min.x += *m_pLeft * (max.x - min.x) / m_imageWidth;
|
||||
min.y += *m_pTop * (max.y - min.y) / m_imageHeight;
|
||||
max.x -= (m_imageWidth - *m_pRight) * (max.x - min.x) / m_imageWidth;
|
||||
max.y -= (m_imageHeight - *m_pBottom) * (max.y - min.y) / m_imageHeight;
|
||||
if (m_bottom > 0 && m_right > 0) {
|
||||
min.x += m_left * (max.x - min.x) / m_imageWidth;
|
||||
min.y += m_top * (max.y - min.y) / m_imageHeight;
|
||||
max.x -= (m_imageWidth - m_right) * (max.x - min.x) / m_imageWidth;
|
||||
max.y -= (m_imageHeight - m_bottom) * (max.y - min.y) / m_imageHeight;
|
||||
}
|
||||
|
||||
// draw the field "active area" as a yellow boundary box
|
||||
gui::MaxFit(&min, &max, *m_pWidth, *m_pHeight);
|
||||
gui::MaxFit(&min, &max, m_width, m_height);
|
||||
|
||||
ffd.min = min;
|
||||
ffd.max = max;
|
||||
ffd.scale = (max.x - min.x) / *m_pWidth;
|
||||
ffd.scale = (max.x - min.x) / m_width;
|
||||
return ffd;
|
||||
}
|
||||
|
||||
@@ -537,48 +540,47 @@ void FieldInfo::Draw(ImDrawList* drawList, const FieldFrameData& ffd) const {
|
||||
drawList->AddRect(ffd.min, ffd.max, IM_COL32(255, 255, 0, 255));
|
||||
}
|
||||
|
||||
ObjectInfo::ObjectInfo() {
|
||||
auto& storage = GetStorage();
|
||||
m_pFilename = storage.GetStringRef("image");
|
||||
m_pWidth =
|
||||
storage.GetFloatRef("width", DisplayOptions::kDefaultWidth.to<float>());
|
||||
m_pLength =
|
||||
storage.GetFloatRef("length", DisplayOptions::kDefaultLength.to<float>());
|
||||
m_pStyle = storage.GetIntRef("style", DisplayOptions::kDefaultStyle);
|
||||
m_pWeight = storage.GetFloatRef("weight", DisplayOptions::kDefaultWeight);
|
||||
m_pColor = storage.GetIntRef("color", DisplayOptions::kDefaultColor);
|
||||
m_pArrows = storage.GetBoolRef("arrows", DisplayOptions::kDefaultArrows);
|
||||
m_pArrowSize =
|
||||
storage.GetIntRef("arrowSize", DisplayOptions::kDefaultArrowSize);
|
||||
m_pArrowWeight =
|
||||
storage.GetFloatRef("arrowWeight", DisplayOptions::kDefaultArrowWeight);
|
||||
m_pArrowColor =
|
||||
storage.GetIntRef("arrowColor", DisplayOptions::kDefaultArrowColor);
|
||||
m_pSelectable =
|
||||
storage.GetBoolRef("selectable", DisplayOptions::kDefaultSelectable);
|
||||
}
|
||||
ObjectInfo::ObjectInfo(Storage& storage)
|
||||
: m_width{storage.GetFloat("width",
|
||||
DisplayOptions::kDefaultWidth.to<float>())},
|
||||
m_length{storage.GetFloat("length",
|
||||
DisplayOptions::kDefaultLength.to<float>())},
|
||||
m_style{storage.GetString("style"),
|
||||
DisplayOptions::kDefaultStyle,
|
||||
{"Box/Image", "Line", "Line (Closed)", "Track"}},
|
||||
m_weight{storage.GetFloat("weight", DisplayOptions::kDefaultWeight)},
|
||||
m_color{
|
||||
storage.GetFloatArray("color", DisplayOptions::kDefaultColorFloat)},
|
||||
m_arrows{storage.GetBool("arrows", DisplayOptions::kDefaultArrows)},
|
||||
m_arrowSize{
|
||||
storage.GetInt("arrowSize", DisplayOptions::kDefaultArrowSize)},
|
||||
m_arrowWeight{
|
||||
storage.GetFloat("arrowWeight", DisplayOptions::kDefaultArrowWeight)},
|
||||
m_arrowColor{storage.GetFloatArray(
|
||||
"arrowColor", DisplayOptions::kDefaultArrowColorFloat)},
|
||||
m_selectable{
|
||||
storage.GetBool("selectable", DisplayOptions::kDefaultSelectable)},
|
||||
m_filename{storage.GetString("image")} {}
|
||||
|
||||
DisplayOptions ObjectInfo::GetDisplayOptions() const {
|
||||
DisplayOptions rv{m_texture};
|
||||
rv.style = static_cast<DisplayOptions::Style>(*m_pStyle);
|
||||
rv.weight = *m_pWeight;
|
||||
rv.color = *m_pColor;
|
||||
rv.width = units::meter_t{*m_pWidth};
|
||||
rv.length = units::meter_t{*m_pLength};
|
||||
rv.arrows = *m_pArrows;
|
||||
rv.arrowSize = *m_pArrowSize;
|
||||
rv.arrowWeight = *m_pArrowWeight;
|
||||
rv.arrowColor = *m_pArrowColor;
|
||||
rv.selectable = *m_pSelectable;
|
||||
rv.style = static_cast<DisplayOptions::Style>(m_style.GetValue());
|
||||
rv.weight = m_weight;
|
||||
rv.color = ImGui::ColorConvertFloat4ToU32(m_color.GetColor());
|
||||
rv.width = units::meter_t{m_width};
|
||||
rv.length = units::meter_t{m_length};
|
||||
rv.arrows = m_arrows;
|
||||
rv.arrowSize = m_arrowSize;
|
||||
rv.arrowWeight = m_arrowWeight;
|
||||
rv.arrowColor = ImGui::ColorConvertFloat4ToU32(m_arrowColor.GetColor());
|
||||
rv.selectable = m_selectable;
|
||||
return rv;
|
||||
}
|
||||
|
||||
void ObjectInfo::DisplaySettings() {
|
||||
static const char* styleChoices[] = {"Box/Image", "Line", "Line (Closed)",
|
||||
"Track"};
|
||||
ImGui::SetNextItemWidth(ImGui::GetFontSize() * 8);
|
||||
ImGui::Combo("Style", m_pStyle, styleChoices, IM_ARRAYSIZE(styleChoices));
|
||||
switch (*m_pStyle) {
|
||||
m_style.Combo("Style");
|
||||
switch (m_style.GetValue()) {
|
||||
case DisplayOptions::kBoxImage:
|
||||
if (ImGui::Button("Choose image...")) {
|
||||
m_fileOpener = std::make_unique<pfd::open_file>(
|
||||
@@ -591,35 +593,27 @@ void ObjectInfo::DisplaySettings() {
|
||||
if (ImGui::Button("Reset image")) {
|
||||
Reset();
|
||||
}
|
||||
InputFloatLength("Width", m_pWidth);
|
||||
InputFloatLength("Length", m_pLength);
|
||||
InputFloatLength("Width", &m_width);
|
||||
InputFloatLength("Length", &m_length);
|
||||
break;
|
||||
case DisplayOptions::kTrack:
|
||||
InputFloatLength("Width", m_pWidth);
|
||||
InputFloatLength("Width", &m_width);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
ImGui::InputFloat("Line Weight", m_pWeight);
|
||||
ImColor col(*m_pColor);
|
||||
if (ImGui::ColorEdit3("Line Color", &col.Value.x,
|
||||
ImGuiColorEditFlags_NoInputs)) {
|
||||
*m_pColor = col;
|
||||
}
|
||||
ImGui::Checkbox("Arrows", m_pArrows);
|
||||
if (*m_pArrows) {
|
||||
ImGui::SliderInt("Arrow Size", m_pArrowSize, 0, 100, "%d%%",
|
||||
ImGui::InputFloat("Line Weight", &m_weight);
|
||||
m_color.ColorEdit3("Line Color", ImGuiColorEditFlags_NoInputs);
|
||||
ImGui::Checkbox("Arrows", &m_arrows);
|
||||
if (m_arrows) {
|
||||
ImGui::SliderInt("Arrow Size", &m_arrowSize, 0, 100, "%d%%",
|
||||
ImGuiSliderFlags_AlwaysClamp);
|
||||
ImGui::InputFloat("Arrow Weight", m_pArrowWeight);
|
||||
ImColor col(*m_pArrowColor);
|
||||
if (ImGui::ColorEdit3("Arrow Color", &col.Value.x,
|
||||
ImGuiColorEditFlags_NoInputs)) {
|
||||
*m_pArrowColor = col;
|
||||
}
|
||||
ImGui::InputFloat("Arrow Weight", &m_arrowWeight);
|
||||
m_arrowColor.ColorEdit3("Arrow Color", ImGuiColorEditFlags_NoInputs);
|
||||
}
|
||||
|
||||
ImGui::Checkbox("Selectable", m_pSelectable);
|
||||
ImGui::Checkbox("Selectable", &m_selectable);
|
||||
}
|
||||
|
||||
void ObjectInfo::DrawLine(ImDrawList* drawList,
|
||||
@@ -629,10 +623,12 @@ void ObjectInfo::DrawLine(ImDrawList* drawList,
|
||||
}
|
||||
|
||||
if (points.size() == 1) {
|
||||
drawList->AddCircleFilled(points.front(), *m_pWeight, *m_pWeight);
|
||||
drawList->AddCircleFilled(points.front(), m_weight, m_weight);
|
||||
return;
|
||||
}
|
||||
|
||||
ImU32 color = ImGui::ColorConvertFloat4ToU32(m_color.GetColor());
|
||||
|
||||
// PolyLine doesn't handle acute angles well; workaround from
|
||||
// https://github.com/ocornut/imgui/issues/3366
|
||||
size_t i = 0;
|
||||
@@ -651,18 +647,18 @@ void ObjectInfo::DrawLine(ImDrawList* drawList,
|
||||
++nlin;
|
||||
}
|
||||
|
||||
drawList->AddPolyline(&points[i], nlin, *m_pColor, false, *m_pWeight);
|
||||
drawList->AddPolyline(&points[i], nlin, color, false, m_weight);
|
||||
i += nlin - 1;
|
||||
}
|
||||
|
||||
if (points.size() > 2 && *m_pStyle == DisplayOptions::kLineClosed) {
|
||||
drawList->AddLine(points.back(), points.front(), *m_pColor, *m_pWeight);
|
||||
if (points.size() > 2 && m_style.GetValue() == DisplayOptions::kLineClosed) {
|
||||
drawList->AddLine(points.back(), points.front(), color, m_weight);
|
||||
}
|
||||
}
|
||||
|
||||
void ObjectInfo::Reset() {
|
||||
m_texture = gui::Texture{};
|
||||
m_pFilename->clear();
|
||||
m_filename.clear();
|
||||
}
|
||||
|
||||
void ObjectInfo::LoadImage() {
|
||||
@@ -673,22 +669,22 @@ void ObjectInfo::LoadImage() {
|
||||
}
|
||||
m_fileOpener.reset();
|
||||
}
|
||||
if (!m_texture && !m_pFilename->empty()) {
|
||||
if (!LoadImageImpl(m_pFilename->c_str())) {
|
||||
m_pFilename->clear();
|
||||
if (!m_texture && !m_filename.empty()) {
|
||||
if (!LoadImageImpl(m_filename)) {
|
||||
m_filename.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool ObjectInfo::LoadImageImpl(const char* fn) {
|
||||
bool ObjectInfo::LoadImageImpl(const std::string& fn) {
|
||||
fmt::print("GUI: loading object image '{}'\n", fn);
|
||||
auto texture = gui::Texture::CreateFromFile(fn);
|
||||
auto texture = gui::Texture::CreateFromFile(fn.c_str());
|
||||
if (!texture) {
|
||||
std::fputs("GUI: could not read object image\n", stderr);
|
||||
return false;
|
||||
}
|
||||
m_texture = std::move(texture);
|
||||
*m_pFilename = fn;
|
||||
m_filename = fn;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -857,15 +853,16 @@ void glass::DisplayField2DSettings(Field2DModel* model) {
|
||||
auto& storage = GetStorage();
|
||||
auto field = storage.GetData<FieldInfo>();
|
||||
if (!field) {
|
||||
storage.SetData(std::make_shared<FieldInfo>());
|
||||
storage.SetData(std::make_shared<FieldInfo>(storage));
|
||||
field = storage.GetData<FieldInfo>();
|
||||
}
|
||||
|
||||
static const char* unitNames[] = {"meters", "feet", "inches"};
|
||||
int* pDisplayUnits = GetStorage().GetIntRef("units", kDisplayMeters);
|
||||
EnumSetting displayUnits{GetStorage().GetString("units"),
|
||||
kDisplayMeters,
|
||||
{"meters", "feet", "inches"}};
|
||||
ImGui::SetNextItemWidth(ImGui::GetFontSize() * 8);
|
||||
ImGui::Combo("Units", pDisplayUnits, unitNames, IM_ARRAYSIZE(unitNames));
|
||||
gDisplayUnits = static_cast<DisplayUnits>(*pDisplayUnits);
|
||||
displayUnits.Combo("Units");
|
||||
gDisplayUnits = static_cast<DisplayUnits>(displayUnits.GetValue());
|
||||
|
||||
ImGui::PushItemWidth(ImGui::GetFontSize() * 4);
|
||||
if (ImGui::CollapsingHeader("Field")) {
|
||||
@@ -881,7 +878,7 @@ void glass::DisplayField2DSettings(Field2DModel* model) {
|
||||
PushID(name);
|
||||
auto& objRef = field->m_objects[name];
|
||||
if (!objRef) {
|
||||
objRef = std::make_unique<ObjectInfo>();
|
||||
objRef = std::make_unique<ObjectInfo>(GetStorage());
|
||||
}
|
||||
auto obj = objRef.get();
|
||||
|
||||
@@ -1025,7 +1022,7 @@ void FieldDisplay::DisplayObject(FieldObjectModel& model,
|
||||
PushID(name);
|
||||
auto& objRef = m_field->m_objects[name];
|
||||
if (!objRef) {
|
||||
objRef = std::make_unique<ObjectInfo>();
|
||||
objRef = std::make_unique<ObjectInfo>(GetStorage());
|
||||
}
|
||||
auto obj = objRef.get();
|
||||
obj->LoadImage();
|
||||
@@ -1205,7 +1202,7 @@ void glass::DisplayField2D(Field2DModel* model, const ImVec2& contentSize) {
|
||||
auto& storage = GetStorage();
|
||||
auto field = storage.GetData<FieldInfo>();
|
||||
if (!field) {
|
||||
storage.SetData(std::make_shared<FieldInfo>());
|
||||
storage.SetData(std::make_shared<FieldInfo>(storage));
|
||||
field = storage.GetData<FieldInfo>();
|
||||
}
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
#include <wpigui.h>
|
||||
|
||||
#include "glass/Context.h"
|
||||
#include "glass/Storage.h"
|
||||
|
||||
using namespace glass;
|
||||
|
||||
@@ -61,7 +62,7 @@ struct FrameData {
|
||||
|
||||
class BackgroundInfo {
|
||||
public:
|
||||
BackgroundInfo();
|
||||
explicit BackgroundInfo(Storage& storage);
|
||||
|
||||
void DisplaySettings();
|
||||
|
||||
@@ -72,11 +73,11 @@ class BackgroundInfo {
|
||||
|
||||
private:
|
||||
void Reset();
|
||||
bool LoadImageImpl(const char* fn);
|
||||
bool LoadImageImpl(const std::string& fn);
|
||||
|
||||
std::unique_ptr<pfd::open_file> m_fileOpener;
|
||||
|
||||
std::string* m_pFilename;
|
||||
std::string& m_filename;
|
||||
gui::Texture m_texture;
|
||||
|
||||
// in image pixels
|
||||
@@ -86,10 +87,8 @@ class BackgroundInfo {
|
||||
|
||||
} // namespace
|
||||
|
||||
BackgroundInfo::BackgroundInfo() {
|
||||
auto& storage = GetStorage();
|
||||
m_pFilename = storage.GetStringRef("image");
|
||||
}
|
||||
BackgroundInfo::BackgroundInfo(Storage& storage)
|
||||
: m_filename{storage.GetString("image")} {}
|
||||
|
||||
void BackgroundInfo::DisplaySettings() {
|
||||
if (ImGui::Button("Choose image...")) {
|
||||
@@ -106,7 +105,7 @@ void BackgroundInfo::DisplaySettings() {
|
||||
|
||||
void BackgroundInfo::Reset() {
|
||||
m_texture = gui::Texture{};
|
||||
m_pFilename->clear();
|
||||
m_filename.clear();
|
||||
m_imageWidth = 0;
|
||||
m_imageHeight = 0;
|
||||
}
|
||||
@@ -119,16 +118,16 @@ void BackgroundInfo::LoadImage() {
|
||||
}
|
||||
m_fileOpener.reset();
|
||||
}
|
||||
if (!m_texture && !m_pFilename->empty()) {
|
||||
if (!LoadImageImpl(m_pFilename->c_str())) {
|
||||
m_pFilename->clear();
|
||||
if (!m_texture && !m_filename.empty()) {
|
||||
if (!LoadImageImpl(m_filename)) {
|
||||
m_filename.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool BackgroundInfo::LoadImageImpl(const char* fn) {
|
||||
bool BackgroundInfo::LoadImageImpl(const std::string& fn) {
|
||||
fmt::print("GUI: loading background image '{}'\n", fn);
|
||||
auto texture = gui::Texture::CreateFromFile(fn);
|
||||
auto texture = gui::Texture::CreateFromFile(fn.c_str());
|
||||
if (!texture) {
|
||||
std::puts("GUI: could not read background image");
|
||||
return false;
|
||||
@@ -136,7 +135,7 @@ bool BackgroundInfo::LoadImageImpl(const char* fn) {
|
||||
m_texture = std::move(texture);
|
||||
m_imageWidth = m_texture.GetWidth();
|
||||
m_imageHeight = m_texture.GetHeight();
|
||||
*m_pFilename = fn;
|
||||
m_filename = fn;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -175,7 +174,7 @@ void glass::DisplayMechanism2DSettings(Mechanism2DModel* model) {
|
||||
auto& storage = GetStorage();
|
||||
auto bg = storage.GetData<BackgroundInfo>();
|
||||
if (!bg) {
|
||||
storage.SetData(std::make_shared<BackgroundInfo>());
|
||||
storage.SetData(std::make_shared<BackgroundInfo>(storage));
|
||||
bg = storage.GetData<BackgroundInfo>();
|
||||
}
|
||||
bg->DisplaySettings();
|
||||
@@ -208,7 +207,7 @@ void glass::DisplayMechanism2D(Mechanism2DModel* model,
|
||||
auto& storage = GetStorage();
|
||||
auto bg = storage.GetData<BackgroundInfo>();
|
||||
if (!bg) {
|
||||
storage.SetData(std::make_shared<BackgroundInfo>());
|
||||
storage.SetData(std::make_shared<BackgroundInfo>(storage));
|
||||
bg = storage.GetData<BackgroundInfo>();
|
||||
}
|
||||
|
||||
|
||||
@@ -19,10 +19,8 @@
|
||||
|
||||
#define IMGUI_DEFINE_MATH_OPERATORS
|
||||
#include <imgui.h>
|
||||
#include <imgui_internal.h>
|
||||
#include <imgui_stdlib.h>
|
||||
#include <implot.h>
|
||||
#include <wpigui.h>
|
||||
#include <wpi/Signal.h>
|
||||
#include <wpi/SmallString.h>
|
||||
#include <wpi/SmallVector.h>
|
||||
@@ -31,6 +29,9 @@
|
||||
|
||||
#include "glass/Context.h"
|
||||
#include "glass/DataSource.h"
|
||||
#include "glass/Storage.h"
|
||||
#include "glass/support/ColorSetting.h"
|
||||
#include "glass/support/EnumSetting.h"
|
||||
#include "glass/support/ExtraGuiWidgets.h"
|
||||
|
||||
using namespace glass;
|
||||
@@ -45,9 +46,11 @@ struct PlotSeriesRef {
|
||||
};
|
||||
|
||||
class PlotSeries {
|
||||
explicit PlotSeries(Storage& storage, int yAxis = 0);
|
||||
|
||||
public:
|
||||
explicit PlotSeries(std::string_view id);
|
||||
explicit PlotSeries(DataSource* source, int yAxis = 0);
|
||||
PlotSeries(Storage& storage, std::string_view id);
|
||||
PlotSeries(Storage& storage, DataSource* source, int yAxis = 0);
|
||||
|
||||
const std::string& GetId() const { return m_id; }
|
||||
|
||||
@@ -56,9 +59,6 @@ class PlotSeries {
|
||||
void SetSource(DataSource* source);
|
||||
DataSource* GetSource() const { return m_source; }
|
||||
|
||||
bool ReadIni(std::string_view name, std::string_view value);
|
||||
void WriteIni(ImGuiTextBuffer* out);
|
||||
|
||||
enum Action { kNone, kMoveUp, kMoveDown, kDelete };
|
||||
Action EmitPlot(PlotView& view, double now, size_t i, size_t plotIndex);
|
||||
void EmitSettings(size_t i);
|
||||
@@ -69,10 +69,12 @@ class PlotSeries {
|
||||
int GetYAxis() const { return m_yAxis; }
|
||||
void SetYAxis(int yAxis) { m_yAxis = yAxis; }
|
||||
|
||||
void SetColor(const ImVec4& color) { m_color.SetColor(color); }
|
||||
|
||||
private:
|
||||
bool IsDigital() const {
|
||||
return m_digital == kDigital ||
|
||||
(m_digital == kAuto && m_source && m_source->IsDigital());
|
||||
return m_digital.GetValue() == kDigital ||
|
||||
(m_digital.GetValue() == kAuto && m_source && m_source->IsDigital());
|
||||
}
|
||||
void AppendValue(double value, uint64_t time);
|
||||
|
||||
@@ -80,19 +82,20 @@ class PlotSeries {
|
||||
DataSource* m_source = nullptr;
|
||||
wpi::sig::ScopedConnection m_sourceCreatedConn;
|
||||
wpi::sig::ScopedConnection m_newValueConn;
|
||||
std::string m_id;
|
||||
std::string& m_id;
|
||||
|
||||
// user settings
|
||||
std::string m_name;
|
||||
int m_yAxis = 0;
|
||||
ImVec4 m_color = IMPLOT_AUTO_COL;
|
||||
int m_marker = 0;
|
||||
float m_weight = IMPLOT_AUTO;
|
||||
std::string& m_name;
|
||||
int& m_yAxis;
|
||||
static constexpr float kDefaultColor[4] = {0.0, 0.0, 0.0, IMPLOT_AUTO};
|
||||
ColorSetting m_color;
|
||||
EnumSetting m_marker;
|
||||
float& m_weight;
|
||||
|
||||
enum Digital { kAuto, kDigital, kAnalog };
|
||||
int m_digital = 0;
|
||||
int m_digitalBitHeight = 8;
|
||||
int m_digitalBitGap = 4;
|
||||
EnumSetting m_digital;
|
||||
int& m_digitalBitHeight;
|
||||
int& m_digitalBitGap;
|
||||
|
||||
// value storage
|
||||
static constexpr int kMaxSize = 2000;
|
||||
@@ -104,10 +107,7 @@ class PlotSeries {
|
||||
|
||||
class Plot {
|
||||
public:
|
||||
Plot();
|
||||
|
||||
bool ReadIni(std::string_view name, std::string_view value);
|
||||
void WriteIni(ImGuiTextBuffer* out);
|
||||
explicit Plot(Storage& storage);
|
||||
|
||||
void DragDropTarget(PlotView& view, size_t i, bool inPlot);
|
||||
void EmitPlot(PlotView& view, double now, bool paused, size_t i);
|
||||
@@ -116,6 +116,7 @@ class Plot {
|
||||
const std::string& GetName() const { return m_name; }
|
||||
|
||||
std::vector<std::unique_ptr<PlotSeries>> m_series;
|
||||
std::vector<std::unique_ptr<Storage>>& m_seriesStorage;
|
||||
|
||||
// Returns base height; does not include actual plot height if auto-sized.
|
||||
int GetAutoBaseHeight(bool* isAuto, size_t i);
|
||||
@@ -129,30 +130,35 @@ class Plot {
|
||||
private:
|
||||
void EmitSettingsLimits(int axis);
|
||||
|
||||
std::string m_name;
|
||||
bool m_visible = true;
|
||||
bool m_showPause = true;
|
||||
unsigned int m_plotFlags = ImPlotFlags_None;
|
||||
bool m_lockPrevX = false;
|
||||
bool m_paused = false;
|
||||
float m_viewTime = 10;
|
||||
bool m_autoHeight = true;
|
||||
int m_height = 300;
|
||||
|
||||
std::string& m_name;
|
||||
bool& m_visible;
|
||||
bool& m_showPause;
|
||||
bool& m_lockPrevX;
|
||||
bool& m_legend;
|
||||
bool& m_yAxis2;
|
||||
bool& m_yAxis3;
|
||||
float& m_viewTime;
|
||||
bool& m_autoHeight;
|
||||
int& m_height;
|
||||
struct PlotRange {
|
||||
double min = 0;
|
||||
double max = 1;
|
||||
bool lockMin = false;
|
||||
bool lockMax = false;
|
||||
explicit PlotRange(Storage& storage);
|
||||
|
||||
std::string& label;
|
||||
double& min;
|
||||
double& max;
|
||||
bool& lockMin;
|
||||
bool& lockMax;
|
||||
bool apply = false;
|
||||
};
|
||||
std::string m_axisLabel[3];
|
||||
PlotRange m_axisRange[3];
|
||||
std::vector<PlotRange> m_axis;
|
||||
ImPlotRange m_xaxisRange; // read from plot, used for lockPrevX
|
||||
};
|
||||
|
||||
class PlotView : public View {
|
||||
public:
|
||||
explicit PlotView(PlotProvider* provider) : m_provider{provider} {}
|
||||
PlotView(PlotProvider* provider, Storage& storage);
|
||||
|
||||
void Display() override;
|
||||
|
||||
@@ -163,12 +169,30 @@ class PlotView : public View {
|
||||
size_t toSeriesIndex, int yAxis = -1);
|
||||
|
||||
PlotProvider* m_provider;
|
||||
std::vector<std::unique_ptr<Storage>>& m_plotsStorage;
|
||||
std::vector<std::unique_ptr<Plot>> m_plots;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
PlotSeries::PlotSeries(std::string_view id) : m_id(id) {
|
||||
PlotSeries::PlotSeries(Storage& storage, int yAxis)
|
||||
: m_id{storage.GetString("id")},
|
||||
m_name{storage.GetString("name")},
|
||||
m_yAxis{storage.GetInt("yAxis", yAxis)},
|
||||
m_color{storage.GetFloatArray("color", kDefaultColor)},
|
||||
m_marker{storage.GetString("marker"),
|
||||
0,
|
||||
{"None", "Circle", "Square", "Diamond", "Up", "Down", "Left",
|
||||
"Right", "Cross", "Plus", "Asterisk"}},
|
||||
m_weight{storage.GetFloat("weight", IMPLOT_AUTO)},
|
||||
m_digital{
|
||||
storage.GetString("digital"), kAuto, {"Auto", "Digital", "Analog"}},
|
||||
m_digitalBitHeight{storage.GetInt("digitalBitHeight", 8)},
|
||||
m_digitalBitGap{storage.GetInt("digitalBitGap", 4)} {}
|
||||
|
||||
PlotSeries::PlotSeries(Storage& storage, std::string_view id)
|
||||
: PlotSeries{storage, 0} {
|
||||
m_id = id;
|
||||
if (DataSource* source = DataSource::Find(id)) {
|
||||
SetSource(source);
|
||||
return;
|
||||
@@ -176,7 +200,8 @@ PlotSeries::PlotSeries(std::string_view id) : m_id(id) {
|
||||
CheckSource();
|
||||
}
|
||||
|
||||
PlotSeries::PlotSeries(DataSource* source, int yAxis) : m_yAxis(yAxis) {
|
||||
PlotSeries::PlotSeries(Storage& storage, DataSource* source, int yAxis)
|
||||
: PlotSeries{storage, yAxis} {
|
||||
SetSource(source);
|
||||
m_id = source->GetId();
|
||||
}
|
||||
@@ -245,66 +270,14 @@ void PlotSeries::AppendValue(double value, uint64_t timeUs) {
|
||||
}
|
||||
}
|
||||
|
||||
bool PlotSeries::ReadIni(std::string_view name, std::string_view value) {
|
||||
if (name == "name") {
|
||||
m_name = value;
|
||||
return true;
|
||||
}
|
||||
if (name == "yAxis") {
|
||||
if (auto num = wpi::parse_integer<int>(value, 10)) {
|
||||
m_yAxis = num.value();
|
||||
}
|
||||
return true;
|
||||
} else if (name == "color") {
|
||||
if (auto num = wpi::parse_integer<unsigned int>(value, 10)) {
|
||||
m_color = ImColor(num.value());
|
||||
}
|
||||
return true;
|
||||
} else if (name == "marker") {
|
||||
if (auto num = wpi::parse_integer<int>(value, 10)) {
|
||||
m_marker = num.value();
|
||||
}
|
||||
return true;
|
||||
} else if (name == "weight") {
|
||||
if (auto num = wpi::parse_float<float>(value)) {
|
||||
m_weight = num.value();
|
||||
}
|
||||
return true;
|
||||
} else if (name == "digital") {
|
||||
if (auto num = wpi::parse_integer<int>(value, 10)) {
|
||||
m_digital = num.value();
|
||||
}
|
||||
return true;
|
||||
} else if (name == "digitalBitHeight") {
|
||||
if (auto num = wpi::parse_integer<int>(value, 10)) {
|
||||
m_digitalBitHeight = num.value();
|
||||
}
|
||||
return true;
|
||||
} else if (name == "digitalBitGap") {
|
||||
if (auto num = wpi::parse_integer<int>(value, 10)) {
|
||||
m_digitalBitGap = num.value();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void PlotSeries::WriteIni(ImGuiTextBuffer* out) {
|
||||
out->appendf(
|
||||
"name=%s\nyAxis=%d\ncolor=%u\nmarker=%d\nweight=%f\ndigital=%d\n"
|
||||
"digitalBitHeight=%d\ndigitalBitGap=%d\n",
|
||||
m_name.c_str(), m_yAxis, static_cast<ImU32>(ImColor(m_color)), m_marker,
|
||||
m_weight, m_digital, m_digitalBitHeight, m_digitalBitGap);
|
||||
}
|
||||
|
||||
const char* PlotSeries::GetName() const {
|
||||
if (!m_name.empty()) {
|
||||
return m_name.c_str();
|
||||
}
|
||||
if (m_newValueConn.connected()) {
|
||||
auto sourceName = m_source->GetName();
|
||||
if (sourceName[0] != '\0') {
|
||||
return sourceName;
|
||||
auto& sourceName = m_source->GetName();
|
||||
if (!sourceName.empty()) {
|
||||
return sourceName.c_str();
|
||||
}
|
||||
}
|
||||
return m_id.c_str();
|
||||
@@ -346,10 +319,10 @@ PlotSeries::Action PlotSeries::EmitPlot(PlotView& view, double now, size_t i,
|
||||
return ImPlotPoint{point->x - d->zeroTime, point->y};
|
||||
};
|
||||
|
||||
if (m_color.w == IMPLOT_AUTO_COL.w) {
|
||||
m_color = ImPlot::GetColormapColor(i);
|
||||
if (m_color.GetColorFloat()[3] == IMPLOT_AUTO) {
|
||||
SetColor(ImPlot::GetColormapColor(i));
|
||||
}
|
||||
ImPlot::SetNextLineStyle(m_color, m_weight);
|
||||
ImPlot::SetNextLineStyle(m_color.GetColor(), m_weight);
|
||||
if (IsDigital()) {
|
||||
ImPlot::PushStyleVar(ImPlotStyleVar_DigitalBitHeight, m_digitalBitHeight);
|
||||
ImPlot::PushStyleVar(ImPlotStyleVar_DigitalBitGap, m_digitalBitGap);
|
||||
@@ -358,7 +331,7 @@ PlotSeries::Action PlotSeries::EmitPlot(PlotView& view, double now, size_t i,
|
||||
ImPlot::PopStyleVar();
|
||||
} else {
|
||||
ImPlot::SetPlotYAxis(m_yAxis);
|
||||
ImPlot::SetNextMarkerStyle(m_marker - 1);
|
||||
ImPlot::SetNextMarkerStyle(m_marker.GetValue() - 1);
|
||||
ImPlot::PlotLineG(label, getter, &getterData, size + 1);
|
||||
}
|
||||
|
||||
@@ -413,10 +386,10 @@ void PlotSeries::EmitDragDropPayload(PlotView& view, size_t i,
|
||||
void PlotSeries::EmitSettings(size_t i) {
|
||||
// Line color
|
||||
{
|
||||
ImGui::ColorEdit3("Color", &m_color.x, ImGuiColorEditFlags_NoInputs);
|
||||
m_color.ColorEdit3("Color", ImGuiColorEditFlags_NoInputs);
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Default")) {
|
||||
m_color = ImPlot::GetColormapColor(i);
|
||||
SetColor(ImPlot::GetColormapColor(i));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -428,10 +401,8 @@ void PlotSeries::EmitSettings(size_t i) {
|
||||
|
||||
// Digital
|
||||
{
|
||||
static const char* const options[] = {"Auto", "Digital", "Analog"};
|
||||
ImGui::SetNextItemWidth(ImGui::GetFontSize() * 6);
|
||||
ImGui::Combo("Digital", &m_digital, options,
|
||||
sizeof(options) / sizeof(options[0]));
|
||||
m_digital.Combo("Digital");
|
||||
}
|
||||
|
||||
if (IsDigital()) {
|
||||
@@ -456,135 +427,44 @@ void PlotSeries::EmitSettings(size_t i) {
|
||||
|
||||
// Marker
|
||||
{
|
||||
static const char* const options[] = {
|
||||
"None", "Circle", "Square", "Diamond", "Up", "Down",
|
||||
"Left", "Right", "Cross", "Plus", "Asterisk"};
|
||||
ImGui::SetNextItemWidth(ImGui::GetFontSize() * 8);
|
||||
ImGui::Combo("Marker", &m_marker, options,
|
||||
sizeof(options) / sizeof(options[0]));
|
||||
m_marker.Combo("Marker");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Plot::Plot() {
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
m_axisRange[i] = PlotRange{};
|
||||
}
|
||||
}
|
||||
Plot::PlotRange::PlotRange(Storage& storage)
|
||||
: label{storage.GetString("label")},
|
||||
min{storage.GetDouble("min", 0)},
|
||||
max{storage.GetDouble("max", 1)},
|
||||
lockMin{storage.GetBool("lockMin", false)},
|
||||
lockMax{storage.GetBool("lockMax", false)} {}
|
||||
|
||||
bool Plot::ReadIni(std::string_view name, std::string_view value) {
|
||||
if (name == "name") {
|
||||
m_name = value;
|
||||
return true;
|
||||
} else if (name == "visible") {
|
||||
if (auto num = wpi::parse_integer<int>(value, 10)) {
|
||||
m_visible = num.value() != 0;
|
||||
}
|
||||
return true;
|
||||
} else if (name == "showPause") {
|
||||
if (auto num = wpi::parse_integer<int>(value, 10)) {
|
||||
m_showPause = num.value() != 0;
|
||||
}
|
||||
return true;
|
||||
} else if (name == "lockPrevX") {
|
||||
if (auto num = wpi::parse_integer<int>(value, 10)) {
|
||||
m_lockPrevX = num.value() != 0;
|
||||
}
|
||||
return true;
|
||||
} else if (name == "legend") {
|
||||
if (auto num = wpi::parse_integer<int>(value, 10)) {
|
||||
if (num.value() == 0) {
|
||||
m_plotFlags |= ImPlotFlags_NoLegend;
|
||||
} else {
|
||||
m_plotFlags &= ~ImPlotFlags_NoLegend;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
} else if (name == "yaxis2") {
|
||||
if (auto num = wpi::parse_integer<int>(value, 10)) {
|
||||
if (num.value() == 0) {
|
||||
m_plotFlags &= ~ImPlotFlags_YAxis2;
|
||||
} else {
|
||||
m_plotFlags |= ImPlotFlags_YAxis2;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
} else if (name == "yaxis3") {
|
||||
if (auto num = wpi::parse_integer<int>(value, 10)) {
|
||||
if (num.value() == 0) {
|
||||
m_plotFlags &= ~ImPlotFlags_YAxis3;
|
||||
} else {
|
||||
m_plotFlags |= ImPlotFlags_YAxis3;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
} else if (name == "viewTime") {
|
||||
if (auto num = wpi::parse_integer<int>(value, 10)) {
|
||||
m_viewTime = num.value() / 1000.0;
|
||||
}
|
||||
return true;
|
||||
} else if (name == "autoHeight") {
|
||||
if (auto num = wpi::parse_integer<int>(value, 10)) {
|
||||
m_autoHeight = num.value() != 0;
|
||||
}
|
||||
return true;
|
||||
} else if (name == "height") {
|
||||
if (auto num = wpi::parse_integer<int>(value, 10)) {
|
||||
m_height = num.value();
|
||||
}
|
||||
return true;
|
||||
} else if (wpi::starts_with(name, 'y')) {
|
||||
auto [yAxisStr, yName] = wpi::split(name, '_');
|
||||
int yAxis =
|
||||
wpi::parse_integer<int>(wpi::drop_front(yAxisStr), 10).value_or(-1);
|
||||
if (yAxis < 0 || yAxis > 3) {
|
||||
return false;
|
||||
}
|
||||
if (yName == "min") {
|
||||
if (auto num = wpi::parse_integer<int>(value, 10)) {
|
||||
m_axisRange[yAxis].min = num.value() / 1000.0;
|
||||
}
|
||||
return true;
|
||||
} else if (yName == "max") {
|
||||
if (auto num = wpi::parse_integer<int>(value, 10)) {
|
||||
m_axisRange[yAxis].max = num.value() / 1000.0;
|
||||
}
|
||||
return true;
|
||||
} else if (yName == "lockMin") {
|
||||
if (auto num = wpi::parse_integer<int>(value, 10)) {
|
||||
m_axisRange[yAxis].lockMin = num.value() != 0;
|
||||
}
|
||||
return true;
|
||||
} else if (yName == "lockMax") {
|
||||
if (auto num = wpi::parse_integer<int>(value, 10)) {
|
||||
m_axisRange[yAxis].lockMax = num.value() != 0;
|
||||
}
|
||||
return true;
|
||||
} else if (yName == "label") {
|
||||
m_axisLabel[yAxis] = value;
|
||||
return true;
|
||||
Plot::Plot(Storage& storage)
|
||||
: m_seriesStorage{storage.GetChildArray("series")},
|
||||
m_name{storage.GetString("name")},
|
||||
m_visible{storage.GetBool("visible", true)},
|
||||
m_showPause{storage.GetBool("showPause", true)},
|
||||
m_lockPrevX{storage.GetBool("lockPrevX", false)},
|
||||
m_legend{storage.GetBool("legend", true)},
|
||||
m_yAxis2{storage.GetBool("yaxis2", false)},
|
||||
m_yAxis3{storage.GetBool("yaxis3", false)},
|
||||
m_viewTime{storage.GetFloat("viewTime", 10)},
|
||||
m_autoHeight{storage.GetBool("autoHeight", true)},
|
||||
m_height{storage.GetInt("height", 300)} {
|
||||
auto& axesStorage = storage.GetChildArray("axis");
|
||||
axesStorage.resize(3);
|
||||
for (auto&& axisStorage : axesStorage) {
|
||||
if (!axisStorage) {
|
||||
axisStorage = std::make_unique<Storage>();
|
||||
}
|
||||
m_axis.emplace_back(*axisStorage);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void Plot::WriteIni(ImGuiTextBuffer* out) {
|
||||
out->appendf(
|
||||
"name=%s\nvisible=%d\nshowPause=%d\nlockPrevX=%d\nlegend=%d\n"
|
||||
"yaxis2=%d\nyaxis3=%d\nviewTime=%d\nautoHeight=%d\nheight=%d\n",
|
||||
m_name.c_str(), m_visible ? 1 : 0, m_showPause ? 1 : 0,
|
||||
m_lockPrevX ? 1 : 0, (m_plotFlags & ImPlotFlags_NoLegend) ? 0 : 1,
|
||||
(m_plotFlags & ImPlotFlags_YAxis2) ? 1 : 0,
|
||||
(m_plotFlags & ImPlotFlags_YAxis3) ? 1 : 0,
|
||||
static_cast<int>(m_viewTime * 1000), m_autoHeight ? 1 : 0, m_height);
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
out->appendf(
|
||||
"y%d_min=%d\ny%d_max=%d\ny%d_lockMin=%d\ny%d_lockMax=%d\n"
|
||||
"y%d_label=%s\n",
|
||||
i, static_cast<int>(m_axisRange[i].min * 1000), i,
|
||||
static_cast<int>(m_axisRange[i].max * 1000), i,
|
||||
m_axisRange[i].lockMin ? 1 : 0, i, m_axisRange[i].lockMax ? 1 : 0, i,
|
||||
m_axisLabel[i].c_str());
|
||||
// loop over series
|
||||
for (auto&& v : m_seriesStorage) {
|
||||
m_series.emplace_back(
|
||||
std::make_unique<PlotSeries>(*v, v->ReadString("id")));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -612,8 +492,9 @@ void Plot::DragDropTarget(PlotView& view, size_t i, bool inPlot) {
|
||||
(yAxis == -1 || elem->GetYAxis() == yAxis);
|
||||
});
|
||||
if (it == m_series.end()) {
|
||||
m_series.emplace_back(
|
||||
std::make_unique<PlotSeries>(source, yAxis == -1 ? 0 : yAxis));
|
||||
m_seriesStorage.emplace_back(std::make_unique<Storage>());
|
||||
m_series.emplace_back(std::make_unique<PlotSeries>(
|
||||
*m_seriesStorage.back(), source, yAxis == -1 ? 0 : yAxis));
|
||||
}
|
||||
} else if (const ImGuiPayload* payload =
|
||||
ImGui::AcceptDragDropPayload("PlotSeries")) {
|
||||
@@ -658,38 +539,45 @@ void Plot::EmitPlot(PlotView& view, double now, bool paused, size_t i) {
|
||||
ImPlotAxisFlags_NoGridLines};
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
ImPlot::SetNextPlotLimitsY(
|
||||
m_axisRange[i].min, m_axisRange[i].max,
|
||||
m_axisRange[i].apply ? ImGuiCond_Always : ImGuiCond_Once, i);
|
||||
m_axisRange[i].apply = false;
|
||||
if (m_axisRange[i].lockMin) {
|
||||
m_axis[i].min, m_axis[i].max,
|
||||
m_axis[i].apply ? ImGuiCond_Always : ImGuiCond_Once, i);
|
||||
m_axis[i].apply = false;
|
||||
if (m_axis[i].lockMin) {
|
||||
yFlags[i] |= ImPlotAxisFlags_LockMin;
|
||||
}
|
||||
if (m_axisRange[i].lockMax) {
|
||||
if (m_axis[i].lockMax) {
|
||||
yFlags[i] |= ImPlotAxisFlags_LockMax;
|
||||
}
|
||||
}
|
||||
|
||||
ImPlotFlags plotFlags = (m_legend ? 0 : ImPlotFlags_NoLegend) |
|
||||
(m_yAxis2 ? ImPlotFlags_YAxis2 : 0) |
|
||||
(m_yAxis3 ? ImPlotFlags_YAxis3 : 0);
|
||||
|
||||
if (ImPlot::BeginPlot(
|
||||
label, nullptr,
|
||||
m_axisLabel[0].empty() ? nullptr : m_axisLabel[0].c_str(),
|
||||
ImVec2(-1, m_height), m_plotFlags, ImPlotAxisFlags_None, yFlags[0],
|
||||
m_axis[0].label.empty() ? nullptr : m_axis[0].label.c_str(),
|
||||
ImVec2(-1, m_height), plotFlags, ImPlotAxisFlags_None, yFlags[0],
|
||||
yFlags[1], yFlags[2],
|
||||
m_axisLabel[1].empty() ? nullptr : m_axisLabel[1].c_str(),
|
||||
m_axisLabel[2].empty() ? nullptr : m_axisLabel[2].c_str())) {
|
||||
m_axis[1].label.empty() ? nullptr : m_axis[1].label.c_str(),
|
||||
m_axis[2].label.empty() ? nullptr : m_axis[2].label.c_str())) {
|
||||
for (size_t j = 0; j < m_series.size(); ++j) {
|
||||
ImGui::PushID(j);
|
||||
switch (m_series[j]->EmitPlot(view, now, j, i)) {
|
||||
case PlotSeries::kMoveUp:
|
||||
if (j > 0) {
|
||||
std::swap(m_seriesStorage[j - 1], m_seriesStorage[j]);
|
||||
std::swap(m_series[j - 1], m_series[j]);
|
||||
}
|
||||
break;
|
||||
case PlotSeries::kMoveDown:
|
||||
if (j < (m_series.size() - 1)) {
|
||||
std::swap(m_seriesStorage[j], m_seriesStorage[j + 1]);
|
||||
std::swap(m_series[j], m_series[j + 1]);
|
||||
}
|
||||
break;
|
||||
case PlotSeries::kDelete:
|
||||
m_seriesStorage.erase(m_seriesStorage.begin() + j);
|
||||
m_series.erase(m_series.begin() + j);
|
||||
break;
|
||||
default:
|
||||
@@ -708,22 +596,22 @@ void Plot::EmitSettingsLimits(int axis) {
|
||||
ImGui::PushID(axis);
|
||||
|
||||
ImGui::SetNextItemWidth(ImGui::GetFontSize() * 10);
|
||||
ImGui::InputText("Label", &m_axisLabel[axis]);
|
||||
ImGui::InputText("Label", &m_axis[axis].label);
|
||||
ImGui::SetNextItemWidth(ImGui::GetFontSize() * 3.5);
|
||||
ImGui::InputDouble("Min", &m_axisRange[axis].min, 0, 0, "%.3f");
|
||||
ImGui::InputDouble("Min", &m_axis[axis].min, 0, 0, "%.3f");
|
||||
ImGui::SameLine();
|
||||
ImGui::SetNextItemWidth(ImGui::GetFontSize() * 3.5);
|
||||
ImGui::InputDouble("Max", &m_axisRange[axis].max, 0, 0, "%.3f");
|
||||
ImGui::InputDouble("Max", &m_axis[axis].max, 0, 0, "%.3f");
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Apply")) {
|
||||
m_axisRange[axis].apply = true;
|
||||
m_axis[axis].apply = true;
|
||||
}
|
||||
|
||||
ImGui::TextUnformatted("Lock Axis");
|
||||
ImGui::SameLine();
|
||||
ImGui::Checkbox("Min##minlock", &m_axisRange[axis].lockMin);
|
||||
ImGui::Checkbox("Min##minlock", &m_axis[axis].lockMin);
|
||||
ImGui::SameLine();
|
||||
ImGui::Checkbox("Max##maxlock", &m_axisRange[axis].lockMax);
|
||||
ImGui::Checkbox("Max##maxlock", &m_axis[axis].lockMax);
|
||||
|
||||
ImGui::PopID();
|
||||
ImGui::Unindent();
|
||||
@@ -734,18 +622,18 @@ void Plot::EmitSettings(size_t i) {
|
||||
ImGui::InputText("##editname", &m_name);
|
||||
ImGui::Checkbox("Visible", &m_visible);
|
||||
ImGui::Checkbox("Show Pause Button", &m_showPause);
|
||||
ImGui::CheckboxFlags("Hide Legend", &m_plotFlags, ImPlotFlags_NoLegend);
|
||||
ImGui::Checkbox("Show Legend", &m_legend);
|
||||
if (i != 0) {
|
||||
ImGui::Checkbox("Lock X-axis to previous plot", &m_lockPrevX);
|
||||
}
|
||||
ImGui::TextUnformatted("Primary Y-Axis");
|
||||
EmitSettingsLimits(0);
|
||||
ImGui::CheckboxFlags("2nd Y-Axis", &m_plotFlags, ImPlotFlags_YAxis2);
|
||||
if ((m_plotFlags & ImPlotFlags_YAxis2) != 0) {
|
||||
ImGui::Checkbox("2nd Y-Axis", &m_yAxis2);
|
||||
if (m_yAxis2) {
|
||||
EmitSettingsLimits(1);
|
||||
}
|
||||
ImGui::CheckboxFlags("3rd Y-Axis", &m_plotFlags, ImPlotFlags_YAxis3);
|
||||
if ((m_plotFlags & ImPlotFlags_YAxis3) != 0) {
|
||||
ImGui::Checkbox("3rd Y-Axis", &m_yAxis3);
|
||||
if (m_yAxis3) {
|
||||
EmitSettingsLimits(2);
|
||||
}
|
||||
ImGui::SetNextItemWidth(ImGui::GetFontSize() * 6);
|
||||
@@ -778,10 +666,20 @@ int Plot::GetAutoBaseHeight(bool* isAuto, size_t i) {
|
||||
return height;
|
||||
}
|
||||
|
||||
PlotView::PlotView(PlotProvider* provider, Storage& storage)
|
||||
: m_provider{provider}, m_plotsStorage{storage.GetChildArray("plots")} {
|
||||
// loop over plots
|
||||
for (auto&& v : m_plotsStorage) {
|
||||
// create plot
|
||||
m_plots.emplace_back(std::make_unique<Plot>(*v));
|
||||
}
|
||||
}
|
||||
|
||||
void PlotView::Display() {
|
||||
if (ImGui::BeginPopupContextItem()) {
|
||||
if (ImGui::Button("Add plot")) {
|
||||
m_plots.emplace_back(std::make_unique<Plot>());
|
||||
m_plotsStorage.emplace_back(std::make_unique<Storage>());
|
||||
m_plots.emplace_back(std::make_unique<Plot>(*m_plotsStorage.back()));
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < m_plots.size(); ++i) {
|
||||
@@ -813,6 +711,7 @@ void PlotView::Display() {
|
||||
if (open) {
|
||||
if (ImGui::Button("Move Up")) {
|
||||
if (i > 0) {
|
||||
std::swap(m_plotsStorage[i - 1], m_plotsStorage[i]);
|
||||
std::swap(m_plots[i - 1], plot);
|
||||
}
|
||||
}
|
||||
@@ -820,12 +719,14 @@ void PlotView::Display() {
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Move Down")) {
|
||||
if (i < (m_plots.size() - 1)) {
|
||||
std::swap(m_plotsStorage[i], m_plotsStorage[i + 1]);
|
||||
std::swap(plot, m_plots[i + 1]);
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Delete")) {
|
||||
m_plotsStorage.erase(m_plotsStorage.begin() + i);
|
||||
m_plots.erase(m_plots.begin() + i);
|
||||
ImGui::PopID();
|
||||
continue;
|
||||
@@ -842,7 +743,8 @@ void PlotView::Display() {
|
||||
|
||||
if (m_plots.empty()) {
|
||||
if (ImGui::Button("Add plot")) {
|
||||
m_plots.emplace_back(std::make_unique<Plot>());
|
||||
m_plotsStorage.emplace_back(std::make_unique<Storage>());
|
||||
m_plots.emplace_back(std::make_unique<Plot>(*m_plotsStorage.back()));
|
||||
}
|
||||
|
||||
// Make "add plot" button a DND target for Plot
|
||||
@@ -889,10 +791,21 @@ void PlotView::MovePlot(PlotView* fromView, size_t fromIndex, size_t toIndex) {
|
||||
if (fromIndex == toIndex) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto st = std::move(m_plotsStorage[fromIndex]);
|
||||
m_plotsStorage.insert(m_plotsStorage.begin() + toIndex, std::move(st));
|
||||
m_plotsStorage.erase(m_plotsStorage.begin() + fromIndex +
|
||||
(fromIndex > toIndex ? 1 : 0));
|
||||
|
||||
auto val = std::move(m_plots[fromIndex]);
|
||||
m_plots.insert(m_plots.begin() + toIndex, std::move(val));
|
||||
m_plots.erase(m_plots.begin() + fromIndex + (fromIndex > toIndex ? 1 : 0));
|
||||
} else {
|
||||
auto st = std::move(fromView->m_plotsStorage[fromIndex]);
|
||||
m_plotsStorage.insert(m_plotsStorage.begin() + toIndex, std::move(st));
|
||||
fromView->m_plotsStorage.erase(fromView->m_plotsStorage.begin() +
|
||||
fromIndex);
|
||||
|
||||
auto val = std::move(fromView->m_plots[fromIndex]);
|
||||
m_plots.insert(m_plots.begin() + toIndex, std::move(val));
|
||||
fromView->m_plots.erase(fromView->m_plots.begin() + fromIndex);
|
||||
@@ -905,6 +818,13 @@ void PlotView::MovePlotSeries(PlotView* fromView, size_t fromPlotIndex,
|
||||
if (fromView == this && fromPlotIndex == toPlotIndex) {
|
||||
// need to handle this specially as the index of the old location changes
|
||||
if (fromSeriesIndex != toSeriesIndex) {
|
||||
auto& seriesStorage = m_plots[fromPlotIndex]->m_seriesStorage;
|
||||
auto st = std::move(seriesStorage[fromSeriesIndex]);
|
||||
seriesStorage.insert(seriesStorage.begin() + toSeriesIndex,
|
||||
std::move(st));
|
||||
seriesStorage.erase(seriesStorage.begin() + fromSeriesIndex +
|
||||
(fromSeriesIndex > toSeriesIndex ? 1 : 0));
|
||||
|
||||
auto& plotSeries = m_plots[fromPlotIndex]->m_series;
|
||||
auto val = std::move(plotSeries[fromSeriesIndex]);
|
||||
// only set Y-axis if actually set
|
||||
@@ -920,34 +840,53 @@ void PlotView::MovePlotSeries(PlotView* fromView, size_t fromPlotIndex,
|
||||
auto& toPlot = *m_plots[toPlotIndex];
|
||||
// always set Y-axis if moving plots
|
||||
fromPlot.m_series[fromSeriesIndex]->SetYAxis(yAxis == -1 ? 0 : yAxis);
|
||||
|
||||
toPlot.m_seriesStorage.insert(
|
||||
toPlot.m_seriesStorage.begin() + toSeriesIndex,
|
||||
std::move(fromPlot.m_seriesStorage[fromSeriesIndex]));
|
||||
fromPlot.m_seriesStorage.erase(fromPlot.m_seriesStorage.begin() +
|
||||
fromSeriesIndex);
|
||||
|
||||
toPlot.m_series.insert(toPlot.m_series.begin() + toSeriesIndex,
|
||||
std::move(fromPlot.m_series[fromSeriesIndex]));
|
||||
fromPlot.m_series.erase(fromPlot.m_series.begin() + fromSeriesIndex);
|
||||
}
|
||||
}
|
||||
|
||||
PlotProvider::PlotProvider(std::string_view iniName)
|
||||
: WindowManager{fmt::format("{}Window", iniName)},
|
||||
m_plotSaver{iniName, this, false},
|
||||
m_seriesSaver{fmt::format("{}Series", iniName), this, true} {}
|
||||
PlotProvider::PlotProvider(Storage& storage) : WindowManager{storage} {
|
||||
storage.SetCustomApply([this] {
|
||||
// loop over windows
|
||||
for (auto&& windowkv : m_storage.GetChildren()) {
|
||||
// get or create window
|
||||
auto win = GetOrAddWindow(windowkv.key(), true);
|
||||
if (!win) {
|
||||
continue;
|
||||
}
|
||||
|
||||
PlotProvider::~PlotProvider() = default;
|
||||
|
||||
void PlotProvider::GlobalInit() {
|
||||
WindowManager::GlobalInit();
|
||||
wpi::gui::AddInit([this] {
|
||||
m_plotSaver.Initialize();
|
||||
m_seriesSaver.Initialize();
|
||||
// get or create view
|
||||
auto view = static_cast<PlotView*>(win->GetView());
|
||||
if (!view) {
|
||||
win->SetView(std::make_unique<PlotView>(this, windowkv.value()));
|
||||
view = static_cast<PlotView*>(win->GetView());
|
||||
}
|
||||
}
|
||||
});
|
||||
storage.SetCustomClear([this] {
|
||||
EraseWindows();
|
||||
m_storage.EraseChildren();
|
||||
});
|
||||
}
|
||||
|
||||
PlotProvider::~PlotProvider() = default;
|
||||
|
||||
void PlotProvider::DisplayMenu() {
|
||||
// use index-based loop due to possible RemoveWindow call
|
||||
for (size_t i = 0; i < m_windows.size(); ++i) {
|
||||
m_windows[i]->DisplayMenuItem();
|
||||
// provide method to destroy the plot window
|
||||
if (ImGui::BeginPopupContextItem()) {
|
||||
if (ImGui::Selectable("Destroy Plot Window")) {
|
||||
m_windows.erase(m_windows.begin() + i);
|
||||
RemoveWindow(i);
|
||||
ImGui::CloseCurrentPopup();
|
||||
}
|
||||
ImGui::EndPopup();
|
||||
@@ -971,105 +910,9 @@ void PlotProvider::DisplayMenu() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (auto win = AddWindow(id, std::make_unique<PlotView>(this))) {
|
||||
if (auto win = AddWindow(
|
||||
id, std::make_unique<PlotView>(this, m_storage.GetChild(id)))) {
|
||||
win->SetDefaultSize(700, 400);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PlotProvider::DisplayWindows() {
|
||||
// create views if not already created
|
||||
for (auto&& window : m_windows) {
|
||||
if (!window->HasView()) {
|
||||
window->SetView(std::make_unique<PlotView>(this));
|
||||
}
|
||||
}
|
||||
WindowManager::DisplayWindows();
|
||||
}
|
||||
|
||||
PlotProvider::IniSaver::IniSaver(std::string_view typeName,
|
||||
PlotProvider* provider, bool forSeries)
|
||||
: IniSaverBase{typeName}, m_provider{provider}, m_forSeries{forSeries} {}
|
||||
|
||||
void* PlotProvider::IniSaver::IniReadOpen(const char* name) {
|
||||
auto [viewId, plotNumStr] = wpi::split(name, '#');
|
||||
std::string_view seriesId;
|
||||
if (m_forSeries) {
|
||||
std::tie(plotNumStr, seriesId) = wpi::split(plotNumStr, '#');
|
||||
if (seriesId.empty()) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
unsigned int plotNum;
|
||||
if (auto plotNumOpt = wpi::parse_integer<unsigned int>(plotNumStr, 10)) {
|
||||
plotNum = plotNumOpt.value();
|
||||
} else {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// get or create window
|
||||
auto win = m_provider->GetOrAddWindow(viewId, true);
|
||||
if (!win) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// get or create view
|
||||
auto view = static_cast<PlotView*>(win->GetView());
|
||||
if (!view) {
|
||||
win->SetView(std::make_unique<PlotView>(m_provider));
|
||||
view = static_cast<PlotView*>(win->GetView());
|
||||
}
|
||||
|
||||
// get or create plot
|
||||
if (view->m_plots.size() <= plotNum) {
|
||||
view->m_plots.resize(plotNum + 1);
|
||||
}
|
||||
auto& plot = view->m_plots[plotNum];
|
||||
if (!plot) {
|
||||
plot = std::make_unique<Plot>();
|
||||
}
|
||||
|
||||
// early exit for plot data
|
||||
if (!m_forSeries) {
|
||||
return plot.get();
|
||||
}
|
||||
|
||||
// get or create series
|
||||
return plot->m_series.emplace_back(std::make_unique<PlotSeries>(seriesId))
|
||||
.get();
|
||||
}
|
||||
|
||||
void PlotProvider::IniSaver::IniReadLine(void* entry, const char* line) {
|
||||
auto [name, value] = wpi::split(line, '=');
|
||||
name = wpi::trim(name);
|
||||
value = wpi::trim(value);
|
||||
if (m_forSeries) {
|
||||
static_cast<PlotSeries*>(entry)->ReadIni(name, value);
|
||||
} else {
|
||||
static_cast<Plot*>(entry)->ReadIni(name, value);
|
||||
}
|
||||
}
|
||||
|
||||
void PlotProvider::IniSaver::IniWriteAll(ImGuiTextBuffer* out_buf) {
|
||||
for (auto&& win : m_provider->m_windows) {
|
||||
auto view = static_cast<PlotView*>(win->GetView());
|
||||
auto id = win->GetId();
|
||||
for (size_t i = 0; i < view->m_plots.size(); ++i) {
|
||||
if (m_forSeries) {
|
||||
// Loop over series
|
||||
for (auto&& series : view->m_plots[i]->m_series) {
|
||||
out_buf->appendf("[%s][%s#%d#%s]\n", GetTypeName(), id.data(),
|
||||
static_cast<int>(i), series->GetId().c_str());
|
||||
series->WriteIni(out_buf);
|
||||
out_buf->append("\n");
|
||||
}
|
||||
} else {
|
||||
// Just the plot
|
||||
out_buf->appendf("[%s][%s#%d]\n", GetTypeName(), id.data(),
|
||||
static_cast<int>(i));
|
||||
view->m_plots[i]->WriteIni(out_buf);
|
||||
out_buf->append("\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
11
glass/src/lib/native/cpp/support/ColorSetting.cpp
Normal file
11
glass/src/lib/native/cpp/support/ColorSetting.cpp
Normal file
@@ -0,0 +1,11 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
#include "glass/support/ColorSetting.h"
|
||||
|
||||
using namespace glass;
|
||||
|
||||
ColorSetting::ColorSetting(std::vector<float>& color) : m_color{color} {
|
||||
m_color.resize(4);
|
||||
}
|
||||
40
glass/src/lib/native/cpp/support/EnumSetting.cpp
Normal file
40
glass/src/lib/native/cpp/support/EnumSetting.cpp
Normal file
@@ -0,0 +1,40 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
#include "glass/support/EnumSetting.h"
|
||||
|
||||
#include <imgui.h>
|
||||
|
||||
using namespace glass;
|
||||
|
||||
EnumSetting::EnumSetting(std::string& str, int defaultValue,
|
||||
std::initializer_list<const char*> choices)
|
||||
: m_str{str}, m_choices{choices}, m_value{defaultValue} {
|
||||
// override default value if str is one of the choices
|
||||
int i = 0;
|
||||
for (auto choice : choices) {
|
||||
if (str == choice) {
|
||||
m_value = i;
|
||||
break;
|
||||
}
|
||||
++i;
|
||||
}
|
||||
}
|
||||
|
||||
void EnumSetting::SetValue(int value) {
|
||||
m_value = value;
|
||||
m_str = m_choices[m_value];
|
||||
}
|
||||
|
||||
bool EnumSetting::Combo(const char* label, int numOptions,
|
||||
int popup_max_height_in_items) {
|
||||
if (ImGui::Combo(
|
||||
label, &m_value, m_choices.data(),
|
||||
numOptions < 0 ? m_choices.size() : static_cast<size_t>(numOptions),
|
||||
popup_max_height_in_items)) {
|
||||
m_str = m_choices[m_value]; // update stored string
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
#include "glass/support/IniSaverBase.h"
|
||||
|
||||
#include <imgui_internal.h>
|
||||
|
||||
using namespace glass;
|
||||
|
||||
namespace {
|
||||
class ImGuiSaver : public IniSaverBackend {
|
||||
public:
|
||||
void Register(IniSaverBase* iniSaver) override;
|
||||
void Unregister(IniSaverBase* iniSaver) override;
|
||||
};
|
||||
} // namespace
|
||||
|
||||
void ImGuiSaver::Register(IniSaverBase* iniSaver) {
|
||||
// hook ini handler to save settings
|
||||
ImGuiSettingsHandler iniHandler;
|
||||
iniHandler.TypeName = iniSaver->GetTypeName();
|
||||
iniHandler.TypeHash = ImHashStr(iniHandler.TypeName);
|
||||
iniHandler.ReadOpenFn = [](ImGuiContext* ctx, ImGuiSettingsHandler* handler,
|
||||
const char* name) {
|
||||
return static_cast<IniSaverBase*>(handler->UserData)->IniReadOpen(name);
|
||||
};
|
||||
iniHandler.ReadLineFn = [](ImGuiContext* ctx, ImGuiSettingsHandler* handler,
|
||||
void* entry, const char* line) {
|
||||
static_cast<IniSaverBase*>(handler->UserData)->IniReadLine(entry, line);
|
||||
};
|
||||
iniHandler.WriteAllFn = [](ImGuiContext* ctx, ImGuiSettingsHandler* handler,
|
||||
ImGuiTextBuffer* out_buf) {
|
||||
static_cast<IniSaverBase*>(handler->UserData)->IniWriteAll(out_buf);
|
||||
};
|
||||
iniHandler.UserData = iniSaver;
|
||||
ImGui::GetCurrentContext()->SettingsHandlers.push_back(iniHandler);
|
||||
}
|
||||
|
||||
void ImGuiSaver::Unregister(IniSaverBase* iniSaver) {
|
||||
if (auto ctx = ImGui::GetCurrentContext()) {
|
||||
auto& handlers = ctx->SettingsHandlers;
|
||||
for (auto it = handlers.begin(), end = handlers.end(); it != end; ++it) {
|
||||
if (it->UserData == iniSaver) {
|
||||
handlers.erase(it);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static ImGuiSaver* GetSaverInstance() {
|
||||
static ImGuiSaver* inst = new ImGuiSaver;
|
||||
return inst;
|
||||
}
|
||||
|
||||
IniSaverBase::IniSaverBase(std::string_view typeName, IniSaverBackend* backend)
|
||||
: m_typeName(typeName), m_backend{backend ? backend : GetSaverInstance()} {}
|
||||
|
||||
IniSaverBase::~IniSaverBase() {
|
||||
m_backend->Unregister(this);
|
||||
}
|
||||
@@ -1,168 +0,0 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
#include "glass/support/IniSaverInfo.h"
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
|
||||
#include <imgui_internal.h>
|
||||
#include <wpi/StringExtras.h>
|
||||
|
||||
using namespace glass;
|
||||
|
||||
void NameInfo::SetName(std::string_view name) {
|
||||
size_t len = (std::min)(name.size(), sizeof(m_name) - 1);
|
||||
std::memcpy(m_name, name.data(), len);
|
||||
m_name[len] = '\0';
|
||||
}
|
||||
|
||||
void NameInfo::GetName(char* buf, size_t size, const char* defaultName) const {
|
||||
if (m_name[0] != '\0') {
|
||||
std::snprintf(buf, size, "%s", m_name);
|
||||
} else {
|
||||
std::snprintf(buf, size, "%s", defaultName);
|
||||
}
|
||||
}
|
||||
|
||||
void NameInfo::GetName(char* buf, size_t size, const char* defaultName,
|
||||
int index) const {
|
||||
if (m_name[0] != '\0') {
|
||||
std::snprintf(buf, size, "%s [%d]", m_name, index);
|
||||
} else {
|
||||
std::snprintf(buf, size, "%s[%d]", defaultName, index);
|
||||
}
|
||||
}
|
||||
|
||||
void NameInfo::GetName(char* buf, size_t size, const char* defaultName,
|
||||
int index, int index2) const {
|
||||
if (m_name[0] != '\0') {
|
||||
std::snprintf(buf, size, "%s [%d,%d]", m_name, index, index2);
|
||||
} else {
|
||||
std::snprintf(buf, size, "%s[%d,%d]", defaultName, index, index2);
|
||||
}
|
||||
}
|
||||
|
||||
void NameInfo::GetLabel(char* buf, size_t size, const char* defaultName) const {
|
||||
if (m_name[0] != '\0') {
|
||||
std::snprintf(buf, size, "%s###Name%s", m_name, defaultName);
|
||||
} else {
|
||||
std::snprintf(buf, size, "%s###Name%s", defaultName, defaultName);
|
||||
}
|
||||
}
|
||||
|
||||
void NameInfo::GetLabel(char* buf, size_t size, const char* defaultName,
|
||||
int index) const {
|
||||
if (m_name[0] != '\0') {
|
||||
std::snprintf(buf, size, "%s [%d]###Name%d", m_name, index, index);
|
||||
} else {
|
||||
std::snprintf(buf, size, "%s[%d]###Name%d", defaultName, index, index);
|
||||
}
|
||||
}
|
||||
|
||||
void NameInfo::GetLabel(char* buf, size_t size, const char* defaultName,
|
||||
int index, int index2) const {
|
||||
if (m_name[0] != '\0') {
|
||||
std::snprintf(buf, size, "%s [%d,%d]###Name%d", m_name, index, index2,
|
||||
index);
|
||||
} else {
|
||||
std::snprintf(buf, size, "%s[%d,%d]###Name%d", defaultName, index, index2,
|
||||
index);
|
||||
}
|
||||
}
|
||||
|
||||
bool NameInfo::ReadIni(std::string_view name, std::string_view value) {
|
||||
if (name != "name") {
|
||||
return false;
|
||||
}
|
||||
size_t len = (std::min)(value.size(), sizeof(m_name) - 1);
|
||||
std::memcpy(m_name, value.data(), len);
|
||||
m_name[len] = '\0';
|
||||
return true;
|
||||
}
|
||||
|
||||
void NameInfo::WriteIni(ImGuiTextBuffer* out) {
|
||||
out->appendf("name=%s\n", m_name);
|
||||
}
|
||||
|
||||
void NameInfo::PushEditNameId(int index) {
|
||||
char id[64];
|
||||
std::snprintf(id, sizeof(id), "Name%d", index);
|
||||
ImGui::PushID(id);
|
||||
}
|
||||
|
||||
void NameInfo::PushEditNameId(const char* name) {
|
||||
char id[128];
|
||||
std::snprintf(id, sizeof(id), "Name%s", name);
|
||||
ImGui::PushID(id);
|
||||
}
|
||||
|
||||
bool NameInfo::PopupEditName(int index) {
|
||||
bool rv = false;
|
||||
char id[64];
|
||||
std::snprintf(id, sizeof(id), "Name%d", index);
|
||||
if (ImGui::BeginPopupContextItem(id)) {
|
||||
ImGui::Text("Edit name:");
|
||||
if (InputTextName("##edit")) {
|
||||
rv = true;
|
||||
}
|
||||
if (ImGui::Button("Close") || ImGui::IsKeyPressedMap(ImGuiKey_Enter) ||
|
||||
ImGui::IsKeyPressedMap(ImGuiKey_KeyPadEnter)) {
|
||||
ImGui::CloseCurrentPopup();
|
||||
}
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
bool NameInfo::PopupEditName(const char* name) {
|
||||
bool rv = false;
|
||||
char id[128];
|
||||
std::snprintf(id, sizeof(id), "Name%s", name);
|
||||
if (ImGui::BeginPopupContextItem(id)) {
|
||||
ImGui::Text("Edit name:");
|
||||
if (InputTextName("##edit")) {
|
||||
rv = true;
|
||||
}
|
||||
if (ImGui::Button("Close") || ImGui::IsKeyPressedMap(ImGuiKey_Enter) ||
|
||||
ImGui::IsKeyPressedMap(ImGuiKey_KeyPadEnter)) {
|
||||
ImGui::CloseCurrentPopup();
|
||||
}
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
bool NameInfo::InputTextName(const char* label_id, ImGuiInputTextFlags flags) {
|
||||
return ImGui::InputText(label_id, m_name, sizeof(m_name), flags);
|
||||
}
|
||||
|
||||
bool OpenInfo::ReadIni(std::string_view name, std::string_view value) {
|
||||
if (name != "open") {
|
||||
return false;
|
||||
}
|
||||
if (auto num = wpi::parse_integer<int>(value, 10)) {
|
||||
m_open = num.value();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void OpenInfo::WriteIni(ImGuiTextBuffer* out) {
|
||||
out->appendf("open=%d\n", m_open ? 1 : 0);
|
||||
}
|
||||
|
||||
bool NameOpenInfo::ReadIni(std::string_view name, std::string_view value) {
|
||||
if (NameInfo::ReadIni(name, value)) {
|
||||
return true;
|
||||
}
|
||||
if (OpenInfo::ReadIni(name, value)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void NameOpenInfo::WriteIni(ImGuiTextBuffer* out) {
|
||||
NameInfo::WriteIni(out);
|
||||
OpenInfo::WriteIni(out);
|
||||
}
|
||||
123
glass/src/lib/native/cpp/support/NameSetting.cpp
Normal file
123
glass/src/lib/native/cpp/support/NameSetting.cpp
Normal file
@@ -0,0 +1,123 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
#include "glass/support/NameSetting.h"
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
|
||||
#include <imgui_internal.h>
|
||||
#include <imgui_stdlib.h>
|
||||
#include <wpi/StringExtras.h>
|
||||
|
||||
using namespace glass;
|
||||
|
||||
void NameSetting::GetName(char* buf, size_t size,
|
||||
const char* defaultName) const {
|
||||
if (!m_name.empty()) {
|
||||
std::snprintf(buf, size, "%s", m_name.c_str());
|
||||
} else {
|
||||
std::snprintf(buf, size, "%s", defaultName);
|
||||
}
|
||||
}
|
||||
|
||||
void NameSetting::GetName(char* buf, size_t size, const char* defaultName,
|
||||
int index) const {
|
||||
if (!m_name.empty()) {
|
||||
std::snprintf(buf, size, "%s [%d]", m_name.c_str(), index);
|
||||
} else {
|
||||
std::snprintf(buf, size, "%s[%d]", defaultName, index);
|
||||
}
|
||||
}
|
||||
|
||||
void NameSetting::GetName(char* buf, size_t size, const char* defaultName,
|
||||
int index, int index2) const {
|
||||
if (!m_name.empty()) {
|
||||
std::snprintf(buf, size, "%s [%d,%d]", m_name.c_str(), index, index2);
|
||||
} else {
|
||||
std::snprintf(buf, size, "%s[%d,%d]", defaultName, index, index2);
|
||||
}
|
||||
}
|
||||
|
||||
void NameSetting::GetLabel(char* buf, size_t size,
|
||||
const char* defaultName) const {
|
||||
if (!m_name.empty()) {
|
||||
std::snprintf(buf, size, "%s###Name%s", m_name.c_str(), defaultName);
|
||||
} else {
|
||||
std::snprintf(buf, size, "%s###Name%s", defaultName, defaultName);
|
||||
}
|
||||
}
|
||||
|
||||
void NameSetting::GetLabel(char* buf, size_t size, const char* defaultName,
|
||||
int index) const {
|
||||
if (!m_name.empty()) {
|
||||
std::snprintf(buf, size, "%s [%d]###Name%d", m_name.c_str(), index, index);
|
||||
} else {
|
||||
std::snprintf(buf, size, "%s[%d]###Name%d", defaultName, index, index);
|
||||
}
|
||||
}
|
||||
|
||||
void NameSetting::GetLabel(char* buf, size_t size, const char* defaultName,
|
||||
int index, int index2) const {
|
||||
if (!m_name.empty()) {
|
||||
std::snprintf(buf, size, "%s [%d,%d]###Name%d", m_name.c_str(), index,
|
||||
index2, index);
|
||||
} else {
|
||||
std::snprintf(buf, size, "%s[%d,%d]###Name%d", defaultName, index, index2,
|
||||
index);
|
||||
}
|
||||
}
|
||||
|
||||
void NameSetting::PushEditNameId(int index) {
|
||||
char id[64];
|
||||
std::snprintf(id, sizeof(id), "Name%d", index);
|
||||
ImGui::PushID(id);
|
||||
}
|
||||
|
||||
void NameSetting::PushEditNameId(const char* name) {
|
||||
char id[128];
|
||||
std::snprintf(id, sizeof(id), "Name%s", name);
|
||||
ImGui::PushID(id);
|
||||
}
|
||||
|
||||
bool NameSetting::PopupEditName(int index) {
|
||||
bool rv = false;
|
||||
char id[64];
|
||||
std::snprintf(id, sizeof(id), "Name%d", index);
|
||||
if (ImGui::BeginPopupContextItem(id)) {
|
||||
ImGui::Text("Edit name:");
|
||||
if (InputTextName("##edit")) {
|
||||
rv = true;
|
||||
}
|
||||
if (ImGui::Button("Close") || ImGui::IsKeyPressedMap(ImGuiKey_Enter) ||
|
||||
ImGui::IsKeyPressedMap(ImGuiKey_KeyPadEnter)) {
|
||||
ImGui::CloseCurrentPopup();
|
||||
}
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
bool NameSetting::PopupEditName(const char* name) {
|
||||
bool rv = false;
|
||||
char id[128];
|
||||
std::snprintf(id, sizeof(id), "Name%s", name);
|
||||
if (ImGui::BeginPopupContextItem(id)) {
|
||||
ImGui::Text("Edit name:");
|
||||
if (InputTextName("##edit")) {
|
||||
rv = true;
|
||||
}
|
||||
if (ImGui::Button("Close") || ImGui::IsKeyPressedMap(ImGuiKey_Enter) ||
|
||||
ImGui::IsKeyPressedMap(ImGuiKey_KeyPadEnter)) {
|
||||
ImGui::CloseCurrentPopup();
|
||||
}
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
bool NameSetting::InputTextName(const char* label_id,
|
||||
ImGuiInputTextFlags flags) {
|
||||
return ImGui::InputText(label_id, &m_name, flags);
|
||||
}
|
||||
Reference in New Issue
Block a user