diff --git a/glass/src/app/native/cpp/main.cpp b/glass/src/app/native/cpp/main.cpp index ed47061e62..5e3bfd94ca 100644 --- a/glass/src/app/native/cpp/main.cpp +++ b/glass/src/app/native/cpp/main.cpp @@ -11,7 +11,9 @@ #include #include "glass/Context.h" +#include "glass/MainMenuBar.h" #include "glass/Model.h" +#include "glass/Storage.h" #include "glass/View.h" #include "glass/networktables/NetworkTables.h" #include "glass/networktables/NetworkTablesProvider.h" @@ -39,9 +41,12 @@ static std::unique_ptr gNtProvider; static std::unique_ptr gNetworkTablesModel; static std::unique_ptr gNetworkTablesSettings; static glass::LogData gNetworkTablesLog; -static glass::Window* gNetworkTablesWindow; -static glass::Window* gNetworkTablesSettingsWindow; -static glass::Window* gNetworkTablesLogWindow; +static std::unique_ptr gNetworkTablesWindow; +static std::unique_ptr gNetworkTablesSettingsWindow; +static std::unique_ptr gNetworkTablesLogWindow; + +static glass::MainMenuBar gMainMenu; +static bool gAbout = false; static void NtInitialize() { // update window title when connection status changes @@ -84,48 +89,65 @@ static void NtInitialize() { } }); - gNetworkTablesLogWindow = gNtProvider->AddWindow( - "NetworkTables Log", + gNetworkTablesLogWindow = std::make_unique( + glass::GetStorageRoot().GetChild("NetworkTables Log"), + "NetworkTables Log", glass::Window::kHide); + gNetworkTablesLogWindow->SetView( std::make_unique(&gNetworkTablesLog)); - if (gNetworkTablesLogWindow) { - gNetworkTablesLogWindow->SetDefaultPos(250, 615); - gNetworkTablesLogWindow->SetDefaultSize(600, 130); - gNetworkTablesLogWindow->SetVisible(false); - gNetworkTablesLogWindow->DisableRenamePopup(); - } + gNetworkTablesLogWindow->SetDefaultPos(250, 615); + gNetworkTablesLogWindow->SetDefaultSize(600, 130); + gNetworkTablesLogWindow->DisableRenamePopup(); + gui::AddLateExecute([] { gNetworkTablesLogWindow->Display(); }); // NetworkTables table window gNetworkTablesModel = std::make_unique(); gui::AddEarlyExecute([] { gNetworkTablesModel->Update(); }); - gNetworkTablesWindow = gNtProvider->AddWindow( - "NetworkTables", + gNetworkTablesWindow = std::make_unique( + glass::GetStorageRoot().GetChild("NetworkTables View"), "NetworkTables"); + gNetworkTablesWindow->SetView( std::make_unique(gNetworkTablesModel.get())); - if (gNetworkTablesWindow) { - gNetworkTablesWindow->SetDefaultPos(250, 277); - gNetworkTablesWindow->SetDefaultSize(750, 185); - gNetworkTablesWindow->DisableRenamePopup(); - } + gNetworkTablesWindow->SetDefaultPos(250, 277); + gNetworkTablesWindow->SetDefaultSize(750, 185); + gNetworkTablesWindow->DisableRenamePopup(); + gui::AddLateExecute([] { gNetworkTablesWindow->Display(); }); // NetworkTables settings window - gNetworkTablesSettings = std::make_unique(); + gNetworkTablesSettings = std::make_unique( + glass::GetStorageRoot().GetChild("NetworkTables Settings")); gui::AddEarlyExecute([] { gNetworkTablesSettings->Update(); }); - gNetworkTablesSettingsWindow = gNtProvider->AddWindow( - "NetworkTables Settings", [] { gNetworkTablesSettings->Display(); }); - if (gNetworkTablesSettingsWindow) { - gNetworkTablesSettingsWindow->SetDefaultPos(30, 30); - gNetworkTablesSettingsWindow->SetFlags(ImGuiWindowFlags_AlwaysAutoResize); - gNetworkTablesSettingsWindow->DisableRenamePopup(); - } + gNetworkTablesSettingsWindow = std::make_unique( + glass::GetStorageRoot().GetChild("NetworkTables Settings"), + "NetworkTables Settings"); + gNetworkTablesSettingsWindow->SetView( + glass::MakeFunctionView([] { gNetworkTablesSettings->Display(); })); + gNetworkTablesSettingsWindow->SetDefaultPos(30, 30); + gNetworkTablesSettingsWindow->SetFlags(ImGuiWindowFlags_AlwaysAutoResize); + gNetworkTablesSettingsWindow->DisableRenamePopup(); + gui::AddLateExecute([] { gNetworkTablesSettingsWindow->Display(); }); + + gui::AddWindowScaler([](float scale) { + // scale default window positions + gNetworkTablesLogWindow->ScaleDefault(scale); + gNetworkTablesWindow->ScaleDefault(scale); + gNetworkTablesSettingsWindow->ScaleDefault(scale); + }); } #ifdef _WIN32 int __stdcall WinMain(void* hInstance, void* hPrevInstance, char* pCmdLine, int nCmdShow) { + int argc = __argc; + char** argv = __argv; #else -int main() { +int main(int argc, char** argv) { #endif + std::string_view saveDir; + if (argc == 2) { + saveDir = argv[1]; + } + gui::CreateContext(); glass::CreateContext(); @@ -137,20 +159,24 @@ int main() { gui::AddIcon(glass::GetResource_glass_256_png()); gui::AddIcon(glass::GetResource_glass_512_png()); - gPlotProvider = std::make_unique("Plot"); - gNtProvider = std::make_unique("NTProvider"); + gPlotProvider = std::make_unique( + glass::GetStorageRoot().GetChild("Plots")); + gNtProvider = std::make_unique( + glass::GetStorageRoot().GetChild("NetworkTables")); - gui::ConfigurePlatformSaveFile("glass.ini"); + glass::SetStorageName("glass"); + glass::SetStorageDir(saveDir.empty() ? gui::GetPlatformSaveFileDir() + : saveDir); gPlotProvider->GlobalInit(); gui::AddInit([] { glass::ResetTime(); }); gNtProvider->GlobalInit(); - gui::AddInit(NtInitialize); + NtInitialize(); glass::AddStandardNetworkTablesViews(*gNtProvider); - gui::AddLateExecute([] { - ImGui::BeginMainMenuBar(); - gui::EmitViewMenu(); + gui::AddLateExecute([] { gMainMenu.Display(); }); + + gMainMenu.AddMainMenu([] { if (ImGui::BeginMenu("View")) { if (ImGui::MenuItem("Reset Time")) { glass::ResetTime(); @@ -181,23 +207,25 @@ int main() { ImGui::EndMenu(); } - bool about = false; if (ImGui::BeginMenu("Info")) { if (ImGui::MenuItem("About")) { - about = true; + gAbout = true; } ImGui::EndMenu(); } - ImGui::EndMainMenuBar(); + }); - if (about) { + gui::AddLateExecute([] { + if (gAbout) { ImGui::OpenPopup("About"); - about = false; + gAbout = false; } if (ImGui::BeginPopupModal("About")) { ImGui::Text("Glass: A different kind of dashboard"); ImGui::Separator(); ImGui::Text("v%s", GetWPILibVersion()); + ImGui::Separator(); + ImGui::Text("Save location: %s", glass::GetStorageDir().c_str()); if (ImGui::Button("Close")) { ImGui::CloseCurrentPopup(); } @@ -208,8 +236,10 @@ int main() { gui::Initialize("Glass - DISCONNECTED", 1024, 768); gui::Main(); + gNetworkTablesSettingsWindow.reset(); + gNetworkTablesLogWindow.reset(); + gNetworkTablesWindow.reset(); gNetworkTablesModel.reset(); - gNetworkTablesSettings.reset(); gNtProvider.reset(); gPlotProvider.reset(); diff --git a/glass/src/lib/native/cpp/Context.cpp b/glass/src/lib/native/cpp/Context.cpp index 936acb2d95..1de4af8840 100644 --- a/glass/src/lib/native/cpp/Context.cpp +++ b/glass/src/lib/native/cpp/Context.cpp @@ -7,13 +7,21 @@ #include #include #include +#include +#include #include #include #include #include +#include +#include +#include +#include +#include #include #include +#include #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(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(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(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(); + 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(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(value->stringVal)) { - value->doubleVal = val.value(); - return true; - } - return false; -} - -static void* GlassStorageReadOpen(ImGuiContext*, ImGuiSettingsHandler* handler, - const char* name) { - auto ctx = static_cast(handler->UserData); - auto& storage = ctx->storage[name]; - if (!storage) { - storage = std::make_unique(); - } - return storage.get(); -} - -static void GlassStorageReadLine(ImGuiContext*, ImGuiSettingsHandler*, - void* entry, const char* line) { - auto storage = static_cast(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(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(handler->UserData); - - // sort for output - std::vector>> 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(); + 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()}) + .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()); - return *m_values.back(); - } else { - return *m_values[it - m_keys.begin()]; +void glass::WorkspaceReset() { + WorkspaceResetImpl(); + WorkspaceInit(); +} + +void glass::AddWorkspaceInit(std::function 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()); \ - 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()); \ - 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(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 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(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(); + } + 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(); - } - return *storage; + return *gContext->storageStack.back(); } -Storage& glass::GetStorage(std::string_view id) { - auto& storage = gContext->storage[id]; - if (!storage) { - storage = std::make_unique(); - } - 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) { diff --git a/glass/src/lib/native/cpp/DataSource.cpp b/glass/src/lib/native/cpp/DataSource.cpp index adab6e75a3..d9bee62e20 100644 --- a/glass/src/lib/native/cpp/DataSource.cpp +++ b/glass/src/lib/native/cpp/DataSource.cpp @@ -12,13 +12,9 @@ using namespace glass; wpi::sig::Signal 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(); } diff --git a/glass/src/lib/native/cpp/MainMenuBar.cpp b/glass/src/lib/native/cpp/MainMenuBar.cpp index 2c4d371256..879f6648f4 100644 --- a/glass/src/lib/native/cpp/MainMenuBar.cpp +++ b/glass/src/lib/native/cpp/MainMenuBar.cpp @@ -6,8 +6,12 @@ #include +#include #include +#include "glass/Context.h" +#include "glass/ContextInternal.h" + using namespace glass; void MainMenuBar::AddMainMenu(std::function menu) { @@ -25,6 +29,8 @@ void MainMenuBar::AddOptionMenu(std::function 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("Choose folder to open"); + } + if (ImGui::MenuItem("Save As...")) { + m_saveFolder = std::make_unique("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(); + } +} diff --git a/glass/src/lib/native/cpp/Storage.cpp b/glass/src/lib/native/cpp/Storage.cpp new file mode 100644 index 0000000000..28af20b654 --- /dev/null +++ b/glass/src/lib/native/cpp/Storage.cpp @@ -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 + +#include +#include +#include + +using namespace glass; + +template +bool ConvertFromString(To* out, std::string_view str) { + if constexpr (std::is_same_v) { + if (str == "true") { + *out = true; + } else if (str == "false") { + *out = false; + } else if (auto val = wpi::parse_integer(str, 10)) { + *out = val.value() != 0; + } else { + return false; + } + } else if constexpr (std::is_floating_point_v) { + if (auto val = wpi::parse_float(str)) { + *out = val.value(); + } else { + return false; + } + } else { + if (auto val = wpi::parse_integer(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 +static void ConvertArray(std::vector** outPtr, std::vector** inPtr) { + if (*inPtr) { + std::vector* tmp; + tmp = new std::vector{(*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(); + } + 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::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::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& Storage::Get##CapsName##Array( \ + std::string_view key, wpi::span defaultVal) { \ + auto& valuePtr = m_values[key]; \ + bool setValue = false; \ + if (!valuePtr) { \ + valuePtr = std::make_unique(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{defaultVal.begin(), defaultVal.end()}; \ + } \ + if (!valuePtr->hasDefault) { \ + if (defaultVal.empty()) { \ + valuePtr->LowerName##ArrayDefault = nullptr; \ + } else { \ + valuePtr->LowerName##ArrayDefault = \ + new std::vector{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(); + } + if (childPtr->type != Value::kChild) { + childPtr->type = Value::kChild; + childPtr->child = new Storage; + } + return *childPtr->child; +} + +std::vector>& Storage::GetChildArray( + std::string_view key) { + auto& valuePtr = m_values[key]; + if (!valuePtr) { + valuePtr = std::make_unique(Value::kChildArray); + valuePtr->childArray = new std::vector>(); + } else if (valuePtr->type != Value::kChildArray) { + valuePtr->Reset(Value::kChildArray); + valuePtr->childArray = new std::vector>(); + } + + return *valuePtr->childArray; +} + +std::unique_ptr 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(); + 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(); + 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(); + 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(); + 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(); + 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>(); + } + 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()); + } else { + goto error; + } + break; + case wpi::json::value_t::number_float: + if (valuePtr->type == Storage::Value::kDoubleArray) { + valuePtr->doubleArray->push_back(jvalue.get()); + } else { + goto error; + } + break; + case wpi::json::value_t::number_integer: + if (valuePtr->type == Storage::Value::kInt64Array) { + valuePtr->int64Array->push_back(jvalue.get()); + } else if (valuePtr->type == Storage::Value::kDoubleArray) { + valuePtr->doubleArray->push_back(jvalue.get()); + } else { + goto error; + } + break; + case wpi::json::value_t::number_unsigned: + if (valuePtr->type == Storage::Value::kInt64Array) { + valuePtr->int64Array->push_back(jvalue.get()); + } else if (valuePtr->type == Storage::Value::kDoubleArray) { + valuePtr->doubleArray->push_back(jvalue.get()); + } else { + goto error; + } + break; + case wpi::json::value_t::string: + if (valuePtr->type == Storage::Value::kStringArray) { + valuePtr->stringArray->emplace_back( + jvalue.get_ref()); + } else { + goto error; + } + break; + case wpi::json::value_t::object: + if (valuePtr->type == Storage::Value::kChildArray) { + valuePtr->childArray->emplace_back(std::make_unique()); + 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(); + created = true; + } + auto& jvalue = jkv.value(); + switch (jvalue.type()) { + case wpi::json::value_t::boolean: + valuePtr->Reset(Value::kBool); + valuePtr->boolVal = jvalue.get(); + break; + case wpi::json::value_t::number_float: + valuePtr->Reset(Value::kDouble); + valuePtr->doubleVal = jvalue.get(); + break; + case wpi::json::value_t::number_integer: + valuePtr->Reset(Value::kInt64); + valuePtr->int64Val = jvalue.get(); + break; + case wpi::json::value_t::number_unsigned: + valuePtr->Reset(Value::kInt64); + valuePtr->int64Val = jvalue.get(); + break; + case wpi::json::value_t::string: + valuePtr->Reset(Value::kString); + valuePtr->stringVal = jvalue.get_ref(); + 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 +static wpi::json StorageToJsonArray(const std::vector& arr) { + wpi::json jarr = wpi::json::array(); + for (auto&& v : arr) { + jarr.emplace_back(v); + } + return jarr; +} + +template <> +wpi::json StorageToJsonArray>( + const std::vector>& 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().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; + } + } +} diff --git a/glass/src/lib/native/cpp/Window.cpp b/glass/src/lib/native/cpp/Window.cpp index 5c014eb7b1..6296fab046 100644 --- a/glass/src/lib/native/cpp/Window.cpp +++ b/glass/src/lib/native/cpp/Window.cpp @@ -8,23 +8,28 @@ #include #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(value, 10)) { - m_visible = num.value(); - } - } else if (name == "enabled") { - if (auto num = wpi::parse_integer(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); -} diff --git a/glass/src/lib/native/cpp/WindowManager.cpp b/glass/src/lib/native/cpp/WindowManager.cpp index 037b9bdd2b..333dad4bc5 100644 --- a/glass/src/lib/native/cpp/WindowManager.cpp +++ b/glass/src/lib/native/cpp/WindowManager.cpp @@ -10,30 +10,23 @@ #include #include +#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(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 display) { - auto win = GetOrAddWindow(id, false); + wpi::unique_function 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) { - auto win = GetOrAddWindow(id, false); + std::unique_ptr 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(id))->get(); + return m_windows + .emplace(it, std::make_unique( + 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(); } diff --git a/glass/src/lib/native/cpp/hardware/AnalogInput.cpp b/glass/src/lib/native/cpp/hardware/AnalogInput.cpp index b9699a4797..af22511650 100644 --- a/glass/src/lib/native/cpp/hardware/AnalogInput.cpp +++ b/glass/src/lib/native/cpp/hardware/AnalogInput.cpp @@ -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); } } diff --git a/glass/src/lib/native/cpp/hardware/AnalogOutput.cpp b/glass/src/lib/native/cpp/hardware/AnalogOutput.cpp index 3a9594be4b..174e0136de 100644 --- a/glass/src/lib/native/cpp/hardware/AnalogOutput.cpp +++ b/glass/src/lib/native/cpp/hardware/AnalogOutput.cpp @@ -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(); diff --git a/glass/src/lib/native/cpp/hardware/DIO.cpp b/glass/src/lib/native/cpp/hardware/DIO.cpp index 59d71f892e..f52974a5a3 100644 --- a/glass/src/lib/native/cpp/hardware/DIO.cpp +++ b/glass/src/lib/native/cpp/hardware/DIO.cpp @@ -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()); } } } diff --git a/glass/src/lib/native/cpp/hardware/Encoder.cpp b/glass/src/lib/native/cpp/hardware/Encoder.cpp index 599a5b8a50..2b37b40c9b 100644 --- a/glass/src/lib/native/cpp/hardware/Encoder.cpp +++ b/glass/src/lib/native/cpp/hardware/Encoder.cpp @@ -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) { diff --git a/glass/src/lib/native/cpp/hardware/LEDDisplay.cpp b/glass/src/lib/native/cpp/hardware/LEDDisplay.cpp index c1ece3a142..c3c2406dbc 100644 --- a/glass/src/lib/native/cpp/hardware/LEDDisplay.cpp +++ b/glass/src/lib/native/cpp/hardware/LEDDisplay.cpp @@ -7,6 +7,7 @@ #include #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(*order); - config.start = static_cast(*start); + config.serpentine = serpentine; + config.order = static_cast(order); + config.start = static_cast(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) { diff --git a/glass/src/lib/native/cpp/hardware/PCM.cpp b/glass/src/lib/native/cpp/hardware/PCM.cpp index 23746be142..c59c1da8c3 100644 --- a/glass/src/lib/native/cpp/hardware/PCM.cpp +++ b/glass/src/lib/native/cpp/hardware/PCM.cpp @@ -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(); } }); diff --git a/glass/src/lib/native/cpp/hardware/PWM.cpp b/glass/src/lib/native/cpp/hardware/PWM.cpp index 3ff8e5273b..0200ac6922 100644 --- a/glass/src/lib/native/cpp/hardware/PWM.cpp +++ b/glass/src/lib/native/cpp/hardware/PWM.cpp @@ -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); } } diff --git a/glass/src/lib/native/cpp/hardware/PowerDistribution.cpp b/glass/src/lib/native/cpp/hardware/PowerDistribution.cpp index 46e550b204..f1de46186a 100644 --- a/glass/src/lib/native/cpp/hardware/PowerDistribution.cpp +++ b/glass/src/lib/native/cpp/hardware/PowerDistribution.cpp @@ -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; diff --git a/glass/src/lib/native/cpp/hardware/Relay.cpp b/glass/src/lib/native/cpp/hardware/Relay.cpp index 59bbc51b64..e071de7d3c 100644 --- a/glass/src/lib/native/cpp/hardware/Relay.cpp +++ b/glass/src/lib/native/cpp/hardware/Relay.cpp @@ -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(); diff --git a/glass/src/lib/native/cpp/other/DeviceTree.cpp b/glass/src/lib/native/cpp/other/DeviceTree.cpp index cd69eb7d20..126668a96d 100644 --- a/glass/src/lib/native/cpp/other/DeviceTree.cpp +++ b/glass/src/lib/native/cpp/other/DeviceTree.cpp @@ -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(); diff --git a/glass/src/lib/native/cpp/other/Field2D.cpp b/glass/src/lib/native/cpp/other/Field2D.cpp index ec0210e375..c3ef86097f 100644 --- a/glass/src/lib/native/cpp/other/Field2D.cpp +++ b/glass/src/lib/native/cpp/other/Field2D.cpp @@ -32,6 +32,9 @@ #include #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 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 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()); - m_pHeight = storage.GetFloatRef("height", kDefaultHeight.to()); -} +FieldInfo::FieldInfo(Storage& storage) + : m_filename{storage.GetString("image")}, + m_width{storage.GetFloat("width", kDefaultWidth.to())}, + m_height{storage.GetFloat("height", kDefaultHeight.to())}, + 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()); - m_pLength = - storage.GetFloatRef("length", DisplayOptions::kDefaultLength.to()); - 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())}, + m_length{storage.GetFloat("length", + DisplayOptions::kDefaultLength.to())}, + 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(*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(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( @@ -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(); if (!field) { - storage.SetData(std::make_shared()); + storage.SetData(std::make_shared(storage)); field = storage.GetData(); } - 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(*pDisplayUnits); + displayUnits.Combo("Units"); + gDisplayUnits = static_cast(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(); + objRef = std::make_unique(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(); + objRef = std::make_unique(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(); if (!field) { - storage.SetData(std::make_shared()); + storage.SetData(std::make_shared(storage)); field = storage.GetData(); } diff --git a/glass/src/lib/native/cpp/other/Mechanism2D.cpp b/glass/src/lib/native/cpp/other/Mechanism2D.cpp index 07e83e2f86..aa801a773b 100644 --- a/glass/src/lib/native/cpp/other/Mechanism2D.cpp +++ b/glass/src/lib/native/cpp/other/Mechanism2D.cpp @@ -27,6 +27,7 @@ #include #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 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(); if (!bg) { - storage.SetData(std::make_shared()); + storage.SetData(std::make_shared(storage)); bg = storage.GetData(); } bg->DisplaySettings(); @@ -208,7 +207,7 @@ void glass::DisplayMechanism2D(Mechanism2DModel* model, auto& storage = GetStorage(); auto bg = storage.GetData(); if (!bg) { - storage.SetData(std::make_shared()); + storage.SetData(std::make_shared(storage)); bg = storage.GetData(); } diff --git a/glass/src/lib/native/cpp/other/Plot.cpp b/glass/src/lib/native/cpp/other/Plot.cpp index 372f8c959b..ab8443629a 100644 --- a/glass/src/lib/native/cpp/other/Plot.cpp +++ b/glass/src/lib/native/cpp/other/Plot.cpp @@ -19,10 +19,8 @@ #define IMGUI_DEFINE_MATH_OPERATORS #include -#include #include #include -#include #include #include #include @@ -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> m_series; + std::vector>& 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 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>& m_plotsStorage; std::vector> 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(value, 10)) { - m_yAxis = num.value(); - } - return true; - } else if (name == "color") { - if (auto num = wpi::parse_integer(value, 10)) { - m_color = ImColor(num.value()); - } - return true; - } else if (name == "marker") { - if (auto num = wpi::parse_integer(value, 10)) { - m_marker = num.value(); - } - return true; - } else if (name == "weight") { - if (auto num = wpi::parse_float(value)) { - m_weight = num.value(); - } - return true; - } else if (name == "digital") { - if (auto num = wpi::parse_integer(value, 10)) { - m_digital = num.value(); - } - return true; - } else if (name == "digitalBitHeight") { - if (auto num = wpi::parse_integer(value, 10)) { - m_digitalBitHeight = num.value(); - } - return true; - } else if (name == "digitalBitGap") { - if (auto num = wpi::parse_integer(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(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(value, 10)) { - m_visible = num.value() != 0; - } - return true; - } else if (name == "showPause") { - if (auto num = wpi::parse_integer(value, 10)) { - m_showPause = num.value() != 0; - } - return true; - } else if (name == "lockPrevX") { - if (auto num = wpi::parse_integer(value, 10)) { - m_lockPrevX = num.value() != 0; - } - return true; - } else if (name == "legend") { - if (auto num = wpi::parse_integer(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(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(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(value, 10)) { - m_viewTime = num.value() / 1000.0; - } - return true; - } else if (name == "autoHeight") { - if (auto num = wpi::parse_integer(value, 10)) { - m_autoHeight = num.value() != 0; - } - return true; - } else if (name == "height") { - if (auto num = wpi::parse_integer(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(wpi::drop_front(yAxisStr), 10).value_or(-1); - if (yAxis < 0 || yAxis > 3) { - return false; - } - if (yName == "min") { - if (auto num = wpi::parse_integer(value, 10)) { - m_axisRange[yAxis].min = num.value() / 1000.0; - } - return true; - } else if (yName == "max") { - if (auto num = wpi::parse_integer(value, 10)) { - m_axisRange[yAxis].max = num.value() / 1000.0; - } - return true; - } else if (yName == "lockMin") { - if (auto num = wpi::parse_integer(value, 10)) { - m_axisRange[yAxis].lockMin = num.value() != 0; - } - return true; - } else if (yName == "lockMax") { - if (auto num = wpi::parse_integer(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(); } + 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(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(m_axisRange[i].min * 1000), i, - static_cast(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(*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(source, yAxis == -1 ? 0 : yAxis)); + m_seriesStorage.emplace_back(std::make_unique()); + m_series.emplace_back(std::make_unique( + *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(*v)); + } +} + void PlotView::Display() { if (ImGui::BeginPopupContextItem()) { if (ImGui::Button("Add plot")) { - m_plots.emplace_back(std::make_unique()); + m_plotsStorage.emplace_back(std::make_unique()); + m_plots.emplace_back(std::make_unique(*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()); + m_plotsStorage.emplace_back(std::make_unique()); + m_plots.emplace_back(std::make_unique(*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(win->GetView()); + if (!view) { + win->SetView(std::make_unique(this, windowkv.value())); + view = static_cast(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(this))) { + if (auto win = AddWindow( + id, std::make_unique(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(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(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(win->GetView()); - if (!view) { - win->SetView(std::make_unique(m_provider)); - view = static_cast(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(); - } - - // early exit for plot data - if (!m_forSeries) { - return plot.get(); - } - - // get or create series - return plot->m_series.emplace_back(std::make_unique(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(entry)->ReadIni(name, value); - } else { - static_cast(entry)->ReadIni(name, value); - } -} - -void PlotProvider::IniSaver::IniWriteAll(ImGuiTextBuffer* out_buf) { - for (auto&& win : m_provider->m_windows) { - auto view = static_cast(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(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(i)); - view->m_plots[i]->WriteIni(out_buf); - out_buf->append("\n"); - } - } - } -} diff --git a/glass/src/lib/native/cpp/support/ColorSetting.cpp b/glass/src/lib/native/cpp/support/ColorSetting.cpp new file mode 100644 index 0000000000..9f20a0b305 --- /dev/null +++ b/glass/src/lib/native/cpp/support/ColorSetting.cpp @@ -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& color) : m_color{color} { + m_color.resize(4); +} diff --git a/glass/src/lib/native/cpp/support/EnumSetting.cpp b/glass/src/lib/native/cpp/support/EnumSetting.cpp new file mode 100644 index 0000000000..848b588120 --- /dev/null +++ b/glass/src/lib/native/cpp/support/EnumSetting.cpp @@ -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 + +using namespace glass; + +EnumSetting::EnumSetting(std::string& str, int defaultValue, + std::initializer_list 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(numOptions), + popup_max_height_in_items)) { + m_str = m_choices[m_value]; // update stored string + return true; + } + return false; +} diff --git a/glass/src/lib/native/cpp/support/IniSaverBase.cpp b/glass/src/lib/native/cpp/support/IniSaverBase.cpp deleted file mode 100644 index ae8d811d41..0000000000 --- a/glass/src/lib/native/cpp/support/IniSaverBase.cpp +++ /dev/null @@ -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 - -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(handler->UserData)->IniReadOpen(name); - }; - iniHandler.ReadLineFn = [](ImGuiContext* ctx, ImGuiSettingsHandler* handler, - void* entry, const char* line) { - static_cast(handler->UserData)->IniReadLine(entry, line); - }; - iniHandler.WriteAllFn = [](ImGuiContext* ctx, ImGuiSettingsHandler* handler, - ImGuiTextBuffer* out_buf) { - static_cast(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); -} diff --git a/glass/src/lib/native/cpp/support/IniSaverInfo.cpp b/glass/src/lib/native/cpp/support/IniSaverInfo.cpp deleted file mode 100644 index 6525e8e69d..0000000000 --- a/glass/src/lib/native/cpp/support/IniSaverInfo.cpp +++ /dev/null @@ -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 -#include - -#include -#include - -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(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); -} diff --git a/glass/src/lib/native/cpp/support/NameSetting.cpp b/glass/src/lib/native/cpp/support/NameSetting.cpp new file mode 100644 index 0000000000..1dc1d2059f --- /dev/null +++ b/glass/src/lib/native/cpp/support/NameSetting.cpp @@ -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 +#include + +#include +#include +#include + +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); +} diff --git a/glass/src/lib/native/include/glass/Context.h b/glass/src/lib/native/include/glass/Context.h index 62b0a33470..14ade2c983 100644 --- a/glass/src/lib/native/include/glass/Context.h +++ b/glass/src/lib/native/include/glass/Context.h @@ -4,17 +4,16 @@ #pragma once -#include +#include #include #include -#include -#include #include namespace glass { -struct Context; +class Context; +class Storage; Context* CreateContext(); void DestroyContext(Context* ctx = nullptr); @@ -32,86 +31,135 @@ void ResetTime(); uint64_t GetZeroTime(); /** - * Storage provides both persistent and non-persistent key/value storage for - * widgets. - * - * Keys are always strings. The storage also provides non-persistent arbitrary - * data storage (via std::shared_ptr). - * - * Storage is automatically indexed internally by the ID stack. Note it is - * necessary to use the glass wrappers for PushID et al to preserve naming in - * the save file (unnamed values are still stored, but this is non-ideal for - * users trying to hand-edit the save file). + * Resets the workspace (all storage except window storage). + * Operates effectively like calling LoadStorage() on a path with no existing + * storage files. Note this will result in auto-saving of the reset state to + * storage. */ -class Storage { - public: - struct Value { - Value() = default; - explicit Value(std::string_view str) : stringVal{str} {} +void WorkspaceReset(); - enum Type { kNone, kInt, kInt64, kBool, kFloat, kDouble, kString }; - Type type = kNone; - union { - int intVal; - int64_t int64Val; - bool boolVal; - float floatVal; - double doubleVal; - }; - std::string stringVal; - }; +/** + * Adds function to be called during workspace (storage) initialization/load. + * This should set up any initial default state, restore stored + * settings/windows, etc. This will be called after the storage is initialized. + * This must be called prior to WorkspaceInit() for proper automatic startup + * loading. + * + * @param init initialization function + */ +void AddWorkspaceInit(std::function init); - int GetInt(std::string_view key, int defaultVal = 0) const; - int64_t GetInt64(std::string_view key, int64_t defaultVal = 0) const; - bool GetBool(std::string_view key, bool defaultVal = false) const; - float GetFloat(std::string_view key, float defaultVal = 0.0f) const; - double GetDouble(std::string_view key, double defaultVal = 0.0) const; - std::string GetString(std::string_view key, - std::string_view defaultVal = {}) const; +/** + * Adds function to be called during workspace (storage) reset. This should + * bring back the state to startup state (e.g. remove any storage references, + * destroy windows, etc). This will be called prior to the storage being + * destroyed. + * + * @param reset reset function + */ +void AddWorkspaceReset(std::function reset); - void SetInt(std::string_view key, int val); - void SetInt64(std::string_view key, int64_t val); - void SetBool(std::string_view key, bool val); - void SetFloat(std::string_view key, float val); - void SetDouble(std::string_view key, double val); - void SetString(std::string_view key, std::string_view val); +/** + * Sets storage load and auto-save name. + * Call this prior to calling wpi::gui::Initialize() for automatic startup + * loading. + * + * @param name base name, suffix will be generated + */ +void SetStorageName(std::string_view name); - int* GetIntRef(std::string_view key, int defaultVal = 0); - int64_t* GetInt64Ref(std::string_view key, int64_t defaultVal = 0); - bool* GetBoolRef(std::string_view key, bool defaultVal = false); - float* GetFloatRef(std::string_view key, float defaultVal = 0.0f); - double* GetDoubleRef(std::string_view key, double defaultVal = 0.0); - std::string* GetStringRef(std::string_view key, - std::string_view defaultVal = {}); +/** + * Sets storage load and auto-save directory. For more customized behavior, set + * Context::storageLoadPath and Context::storageAutoSavePath directly. + * Call this prior to calling wpi::gui::Initialize() for automatic startup + * loading. + * + * @param dir path to directory + */ +void SetStorageDir(std::string_view dir); - Value& GetValue(std::string_view key); +/** + * Gets storage auto-save directory. + * + * @return Path to directory + */ +std::string GetStorageDir(); - void SetData(std::shared_ptr&& data) { m_data = std::move(data); } +/** + * Explicitly load storage. Set Context::storageLoadDir prior to calling + * wpi::gui::Initialize() for automatic startup loading. + * + * Non-empty root names are not loaded unless GetStorageRoot() is called during + * initialization (or before this function is called). + * + * @param dir path to directory + */ +bool LoadStorage(std::string_view dir); - template - T* GetData() const { - return static_cast(m_data.get()); - } +/** + * Save storage to automatic on-change save location. + */ +bool SaveStorage(); - Storage() = default; - Storage(const Storage&) = delete; - Storage& operator=(const Storage&) = delete; +/** + * Explicitly save storage. Set Context::storageAutoSaveDir prior to calling + * wpi::gui::Initialize() for automatic on-change saving. + * + * @param dir path to directory + */ +bool SaveStorage(std::string_view dir); - std::vector& GetKeys() { return m_keys; } - const std::vector& GetKeys() const { return m_keys; } - std::vector>& GetValues() { return m_values; } - const std::vector>& GetValues() const { - return m_values; - } +/** + * Gets the storage root for the current ID stack (e.g. the last call to + * ResetStorageStack). + * + * @return Storage object + */ +Storage& GetCurStorageRoot(); - private: - mutable std::vector m_keys; - mutable std::vector> m_values; - std::shared_ptr m_data; -}; +/** + * Gets an arbitrary storage root. + * + * Non-empty root names are saved but not loaded unless GetStorageRoot() + * is called during initialization (or before LoadStorage is called). + * + * @param rootName root name + * @return Storage object + */ +Storage& GetStorageRoot(std::string_view rootName = {}); +/** + * Resets storage stack. Should only be called at top level. + * + * @param rootName root name + */ +void ResetStorageStack(std::string_view rootName = {}); + +/** + * Gets the storage object for the current point in the ID stack. + * + * @return Storage object + */ Storage& GetStorage(); -Storage& GetStorage(std::string_view id); + +/** + * Pushes label/ID onto the storage stack, without pushing the imgui ID stack. + * + * @param label_id label or label###id + */ +void PushStorageStack(std::string_view label_id); + +/** + * Pushes specific storage onto the storage stack. + * + * @param storage storage + */ +void PushStorageStack(Storage& storage); + +/** + * Pops storage stack, without popping the imgui ID stack. + */ +void PopStorageStack(); bool Begin(const char* name, bool* p_open = nullptr, ImGuiWindowFlags flags = 0); diff --git a/glass/src/lib/native/include/glass/ContextInternal.h b/glass/src/lib/native/include/glass/ContextInternal.h index 39e54f3925..7556c70efd 100644 --- a/glass/src/lib/native/include/glass/ContextInternal.h +++ b/glass/src/lib/native/include/glass/ContextInternal.h @@ -6,42 +6,40 @@ #include +#include #include +#include +#include -#include -#include #include #include #include "glass/Context.h" -#include "glass/support/IniSaverInfo.h" -#include "glass/support/IniSaverString.h" +#include "glass/Storage.h" namespace glass { class DataSource; -class DataSourceName { +class Context { public: - DataSourceName() = default; - explicit DataSourceName(DataSource* source) : source{source} {} + Context(); + Context(const Context&) = delete; + Context& operator=(const Context&) = delete; + ~Context(); - bool ReadIni(std::string_view name_, std::string_view value) { - return name->ReadIni(name_, value); - } - void WriteIni(ImGuiTextBuffer* out) { name->WriteIni(out); } - - std::unique_ptr name{new NameInfo}; - DataSource* source = nullptr; -}; - -struct Context { - wpi::SmallString<128> curId; - wpi::SmallVector idStack; - wpi::StringMap> storage; + std::vector> workspaceInit; + std::vector> workspaceReset; + std::string storageLoadDir = "."; + std::string storageAutoSaveDir = "."; + std::string storageName = "imgui"; + wpi::SmallVector storageStack; + wpi::StringMap> storageRoots; wpi::StringMap deviceHidden; - IniSaverString sources{"Data Sources"}; + wpi::StringMap sources; + Storage& sourceNameStorage; uint64_t zeroTime = 0; + bool isPlatformSaveDir = false; }; extern Context* gContext; diff --git a/glass/src/lib/native/include/glass/DataSource.h b/glass/src/lib/native/include/glass/DataSource.h index 1d5c37b0d8..5eebb3c8a1 100644 --- a/glass/src/lib/native/include/glass/DataSource.h +++ b/glass/src/lib/native/include/glass/DataSource.h @@ -16,8 +16,6 @@ namespace glass { -class NameInfo; - /** * A data source for numeric/boolean data. */ @@ -33,15 +31,9 @@ class DataSource { const char* GetId() const { return m_id.c_str(); } - void SetName(std::string_view name); - const char* GetName() const; - NameInfo& GetNameInfo() { return *m_name; } - - void PushEditNameId(int index); - void PushEditNameId(const char* name); - bool PopupEditName(int index); - bool PopupEditName(const char* name); - bool InputTextName(const char* label_id, ImGuiInputTextFlags flags = 0); + void SetName(std::string_view name) { m_name = name; } + std::string& GetName() { return m_name; } + const std::string& GetName() const { return m_name; } void SetDigital(bool digital) { m_digital = digital; } bool IsDigital() const { return m_digital; } @@ -53,8 +45,9 @@ class DataSource { double GetValue() const { return m_value; } // drag source helpers - void LabelText(const char* label, const char* fmt, ...) const; - void LabelTextV(const char* label, const char* fmt, va_list args) const; + void LabelText(const char* label, const char* fmt, ...) const IM_FMTARGS(3); + void LabelTextV(const char* label, const char* fmt, va_list args) const + IM_FMTLIST(3); bool Combo(const char* label, int* current_item, const char* const items[], int items_count, int popup_max_height_in_items = -1) const; bool SliderFloat(const char* label, float* v, float v_min, float v_max, @@ -74,7 +67,7 @@ class DataSource { private: std::string m_id; - NameInfo* m_name; + std::string& m_name; bool m_digital = false; std::atomic m_value = 0; }; diff --git a/glass/src/lib/native/include/glass/MainMenuBar.h b/glass/src/lib/native/include/glass/MainMenuBar.h index 7a6a2fc409..914153636e 100644 --- a/glass/src/lib/native/include/glass/MainMenuBar.h +++ b/glass/src/lib/native/include/glass/MainMenuBar.h @@ -4,13 +4,14 @@ #pragma once +#include + #include +#include #include namespace glass { -class WindowManager; - /** * GUI main menu bar. */ @@ -21,6 +22,11 @@ class MainMenuBar { */ void Display(); + /** + * Displays workspace menu. Called by Display(). + */ + void WorkspaceMenu(); + /** * Adds to GUI's main menu bar. The menu function is called from within a * ImGui::BeginMainMenuBar()/EndMainMenuBar() block. Usually it's only @@ -43,6 +49,8 @@ class MainMenuBar { private: std::vector> m_optionMenus; std::vector> m_menus; + std::unique_ptr m_openFolder; + std::unique_ptr m_saveFolder; }; } // namespace glass diff --git a/glass/src/lib/native/include/glass/Provider.h b/glass/src/lib/native/include/glass/Provider.h index 53b1e75f2d..f620d523b8 100644 --- a/glass/src/lib/native/include/glass/Provider.h +++ b/glass/src/lib/native/include/glass/Provider.h @@ -19,6 +19,8 @@ namespace glass { +class Storage; + namespace detail { struct ProviderFunctions { using Exists = std::function; @@ -49,9 +51,9 @@ class Provider : public WindowManager { /** * Constructor. * - * @param iniName Group name to use in ini file + * @param storage Storage */ - explicit Provider(std::string_view iniName) : WindowManager{iniName} {} + explicit Provider(Storage& storage) : WindowManager{storage} {} Provider(const Provider&) = delete; Provider& operator=(const Provider&) = delete; @@ -133,6 +135,7 @@ class Provider : public WindowManager { ModelEntry* modelEntry; ViewExistsFunc exists; CreateViewFunc createView; + bool showDefault = false; Window* window = nullptr; }; diff --git a/glass/src/lib/native/include/glass/Provider.inc b/glass/src/lib/native/include/glass/Provider.inc index 33bb6e031d..7370cca13b 100644 --- a/glass/src/lib/native/include/glass/Provider.inc +++ b/glass/src/lib/native/include/glass/Provider.inc @@ -26,7 +26,7 @@ void Provider::ShowDefault(std::string_view name) { if (it == m_viewEntries.end() || (*it)->name != name) { return; } - this->Show(it->get(), (*it)->window); + (*it)->showDefault = true; } template diff --git a/glass/src/lib/native/include/glass/Storage.h b/glass/src/lib/native/include/glass/Storage.h new file mode 100644 index 0000000000..004b8b4252 --- /dev/null +++ b/glass/src/lib/native/include/glass/Storage.h @@ -0,0 +1,293 @@ +// 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. + +#pragma once + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace wpi { +class json; +} // namespace wpi + +namespace glass { + +namespace detail { +template +class ChildIterator; +} // namespace detail + +/** + * Storage provides both persistent and non-persistent nested key/value storage + * for widgets. + * + * Keys are always strings. The storage also provides non-persistent arbitrary + * data storage (via std::shared_ptr). + * + * Storage is automatically indexed internally by the ID stack. Note it is + * necessary to use the glass wrappers for PushID et al to preserve naming in + * the save file (unnamed values are still stored, but this is non-ideal for + * users trying to hand-edit the save file). + */ +class Storage { + public: + struct Value { + enum Type { + kNone, + kInt, + kInt64, + kBool, + kFloat, + kDouble, + kString, + kChild, + kIntArray, + kInt64Array, + kBoolArray, + kFloatArray, + kDoubleArray, + kStringArray, + kChildArray + }; + + Value() = default; + explicit Value(Type type) : type{type} {} + Value(const Value&) = delete; + Value& operator=(const Value&) = delete; + ~Value() { Reset(kNone); } + + Type type = kNone; + union { + int intVal; + int64_t int64Val; + bool boolVal; + float floatVal; + double doubleVal; + Storage* child; + std::vector* intArray; + std::vector* int64Array; + std::vector* boolArray; + std::vector* floatArray; + std::vector* doubleArray; + std::vector* stringArray; + std::vector>* childArray; + }; + std::string stringVal; + + union { + int intDefault; + int64_t int64Default; + bool boolDefault; + float floatDefault; + double doubleDefault; + // pointers may be nullptr to indicate empty + std::vector* intArrayDefault; + std::vector* int64ArrayDefault; + std::vector* boolArrayDefault; + std::vector* floatArrayDefault; + std::vector* doubleArrayDefault; + std::vector* stringArrayDefault; + }; + std::string stringDefault; + + bool hasDefault = false; + + void Reset(Type newType); + }; + + using ValueMap = wpi::StringMap>; + template + using ChildIterator = detail::ChildIterator; + + // The "Read" functions don't create or overwrite the value + int ReadInt(std::string_view key, int defaultVal = 0) const; + int64_t ReadInt64(std::string_view key, int64_t defaultVal = 0) const; + bool ReadBool(std::string_view key, bool defaultVal = false) const; + float ReadFloat(std::string_view key, float defaultVal = 0.0f) const; + double ReadDouble(std::string_view key, double defaultVal = 0.0) const; + std::string ReadString(std::string_view key, + std::string_view defaultVal = {}) const; + + void SetInt(std::string_view key, int val); + void SetInt64(std::string_view key, int64_t val); + void SetBool(std::string_view key, bool val); + void SetFloat(std::string_view key, float val); + void SetDouble(std::string_view key, double val); + void SetString(std::string_view key, std::string_view val); + + // The "Get" functions create or override the current value type. + // If the value is not set, it is set to the provided default. + int& GetInt(std::string_view key, int defaultVal = 0); + int64_t& GetInt64(std::string_view key, int64_t defaultVal = 0); + bool& GetBool(std::string_view key, bool defaultVal = false); + float& GetFloat(std::string_view key, float defaultVal = 0.0f); + double& GetDouble(std::string_view key, double defaultVal = 0.0); + std::string& GetString(std::string_view key, + std::string_view defaultVal = {}); + + std::vector& GetIntArray(std::string_view key, + wpi::span defaultVal = {}); + std::vector& GetInt64Array(std::string_view key, + wpi::span defaultVal = {}); + std::vector& GetBoolArray(std::string_view key, + wpi::span defaultVal = {}); + std::vector& GetFloatArray(std::string_view key, + wpi::span defaultVal = {}); + std::vector& GetDoubleArray(std::string_view key, + wpi::span defaultVal = {}); + std::vector& GetStringArray( + std::string_view key, wpi::span defaultVal = {}); + std::vector>& GetChildArray(std::string_view key); + + Value* FindValue(std::string_view key); + Value& GetValue(std::string_view key); + Storage& GetChild(std::string_view label_id); + + void SetData(std::shared_ptr&& data) { m_data = std::move(data); } + + template + T* GetData() const { + return static_cast(m_data.get()); + } + + Storage() = default; + Storage(const Storage&) = delete; + Storage& operator=(const Storage&) = delete; + + void Insert(std::string_view key, std::unique_ptr value) { + m_values[key] = std::move(value); + } + + std::unique_ptr Erase(std::string_view key); + + void EraseAll() { m_values.clear(); } + + ValueMap& GetValues() { return m_values; } + const ValueMap& GetValues() const { return m_values; } + + wpi::iterator_range> GetChildren(); + + /** + * Erases all children (at top level). + */ + void EraseChildren(); + + bool FromJson(const wpi::json& json, const char* filename); + wpi::json ToJson() const; + + /** + * Clear settings (set to default). Calls custom clear function (if set), + * otherwise calls ClearValues(). + */ + void Clear(); + + /** + * Clear values (and values of children) only (set to default). Does not + * call custom clear function. + */ + void ClearValues(); + + /** + * Apply settings (called after all settings have been loaded). Calls + * custom apply function (if set), otherwise calls ApplyChildren(). + */ + void Apply(); + + /** + * Apply settings to children. Does not call custom apply function. + */ + void ApplyChildren(); + + /** + * Sets custom JSON handlers (replaces FromJson and ToJson). + * + * @param fromJson replacement for FromJson() + * @param toJson replacement for ToJson() + */ + void SetCustomJson( + std::function fromJson, + std::function toJson) { + m_fromJson = std::move(fromJson); + m_toJson = std::move(toJson); + } + + void SetCustomClear(std::function clear) { + m_clear = std::move(clear); + } + + void SetCustomApply(std::function apply) { + m_apply = std::move(apply); + } + + private: + mutable ValueMap m_values; + std::shared_ptr m_data; + std::function m_fromJson; + std::function m_toJson; + std::function m_clear; + std::function m_apply; +}; + +namespace detail { + +/// proxy class for the GetChildren() function +template +class ChildIterator { + private: + /// the iterator + IteratorType anchor; + IteratorType end; + + public: + ChildIterator(IteratorType it, IteratorType end) noexcept + : anchor(it), end(end) { + while (anchor != end && + anchor->getValue()->type != Storage::Value::kChild) { + ++anchor; + } + } + + /// dereference operator (needed for range-based for) + ChildIterator& operator*() { return *this; } + + /// increment operator (needed for range-based for) + ChildIterator& operator++() { + ++anchor; + while (anchor != end && + anchor->getValue()->type != Storage::Value::kChild) { + ++anchor; + } + return *this; + } + + /// inequality operator (needed for range-based for) + bool operator!=(const ChildIterator& o) const noexcept { + return anchor != o.anchor; + } + + /// return key of the iterator + std::string_view key() const { return anchor->getKey(); } + + /// return value of the iterator + Storage& value() const { return *anchor->getValue()->child; } +}; + +} // namespace detail + +inline auto Storage::GetChildren() + -> wpi::iterator_range> { + return {{m_values.begin(), m_values.end()}, {m_values.end(), m_values.end()}}; +} + +} // namespace glass diff --git a/glass/src/lib/native/include/glass/Window.h b/glass/src/lib/native/include/glass/Window.h index 780479a105..0a37f9ae88 100644 --- a/glass/src/lib/native/include/glass/Window.h +++ b/glass/src/lib/native/include/glass/Window.h @@ -15,19 +15,21 @@ namespace glass { +class Storage; + /** * Managed window information. * A Window owns the View that displays the window's contents. */ class Window { public: - Window() = default; - explicit Window(std::string_view id) : m_id{id}, m_defaultName{id} {} + enum Visibility { kHide = 0, kShow, kDisabled }; + + Window(Storage& storage, std::string_view id, + Visibility defaultVisibility = kShow); std::string_view GetId() const { return m_id; } - enum Visibility { kHide = 0, kShow, kDisabled }; - bool HasView() { return static_cast(m_view); } void SetView(std::unique_ptr view) { m_view = std::move(view); } @@ -59,6 +61,13 @@ class Window { */ void SetVisibility(Visibility visibility); + /** + * Sets default visibility of window. + * + * @param visibility 0=hide, 1=show, 2=disabled (force-hide) + */ + void SetDefaultVisibility(Visibility visibility); + /** * Sets default position of window. * @@ -109,17 +118,16 @@ class Window { */ void ScaleDefault(float scale); - void IniReadLine(const char* lineStr); - void IniWriteAll(const char* typeName, ImGuiTextBuffer* out_buf); - private: std::string m_id; - std::string m_name; + std::string& m_name; std::string m_defaultName; std::unique_ptr m_view; ImGuiWindowFlags m_flags = 0; - bool m_visible = true; - bool m_enabled = true; + bool& m_visible; + bool& m_enabled; + bool& m_defaultVisible; + bool& m_defaultEnabled; bool m_renamePopupEnabled = true; ImGuiCond m_posCond = 0; ImGuiCond m_sizeCond = 0; diff --git a/glass/src/lib/native/include/glass/WindowManager.h b/glass/src/lib/native/include/glass/WindowManager.h index 4024e1561f..0ccace2d68 100644 --- a/glass/src/lib/native/include/glass/WindowManager.h +++ b/glass/src/lib/native/include/glass/WindowManager.h @@ -4,20 +4,18 @@ #pragma once -#include #include #include -#include #include -#include #include #include "glass/Window.h" -#include "glass/support/IniSaverBase.h" namespace glass { +class Storage; + /** * Window manager. * @@ -31,9 +29,9 @@ class WindowManager { /** * Constructor. * - * @param iniName Group name to use in ini file + * @param storage Storage for window information */ - explicit WindowManager(std::string_view iniName); + explicit WindowManager(Storage& storage); virtual ~WindowManager() = default; WindowManager(const WindowManager&) = delete; @@ -65,8 +63,10 @@ class WindowManager { * * @param id unique identifier of the window (title bar) * @param display window contents display function + * @param defaultVisibility default window visibility */ - Window* AddWindow(std::string_view id, wpi::unique_function display); + Window* AddWindow(std::string_view id, wpi::unique_function display, + Window::Visibility defaultVisibility = Window::kShow); /** * Adds window to GUI. The view's display function is called from within a @@ -82,9 +82,11 @@ class WindowManager { * * @param id unique identifier of the window (title bar) * @param view view object + * @param defaultVisibility default window visibility * @return Window, or nullptr on duplicate window */ - Window* AddWindow(std::string_view id, std::unique_ptr view); + Window* AddWindow(std::string_view id, std::unique_ptr view, + Window::Visibility defaultVisibility = Window::kShow); /** * Adds window to GUI. A View must be assigned to the returned Window @@ -99,9 +101,12 @@ class WindowManager { * every frame in the gui::AddExecute() function. * * @param id unique identifier of the window (default title bar) + * @param duplicateOk if false, warn on duplicates + * @param defaultVisibility default window visibility * @return Window, or nullptr on duplicate window */ - Window* GetOrAddWindow(std::string_view id, bool duplicateOk = false); + Window* GetOrAddWindow(std::string_view id, bool duplicateOk = false, + Window::Visibility defaultVisibility = Window::kShow); /** * Gets existing window. If none exists, returns nullptr. @@ -111,27 +116,26 @@ class WindowManager { */ Window* GetWindow(std::string_view id); + /** + * Erases all windows. + */ + void EraseWindows() { m_windows.clear(); } + protected: - virtual void DisplayWindows(); + /** + * Removes existing window (by index) + * + * @param index index of window in m_windows + */ + void RemoveWindow(size_t index); // kept sorted by id std::vector> m_windows; + Storage& m_storage; + private: - class IniSaver : public IniSaverBase { - public: - explicit IniSaver(std::string_view typeName, WindowManager* manager) - : IniSaverBase{typeName}, m_manager{manager} {} - - void* IniReadOpen(const char* name) override; - void IniReadLine(void* entry, const char* lineStr) override; - void IniWriteAll(ImGuiTextBuffer* out_buf) override; - - private: - WindowManager* m_manager; - }; - - IniSaver m_iniSaver; + void DisplayWindows(); }; } // namespace glass diff --git a/glass/src/lib/native/include/glass/other/Plot.h b/glass/src/lib/native/include/glass/other/Plot.h index f7b196dcc2..3e27b9db74 100644 --- a/glass/src/lib/native/include/glass/other/Plot.h +++ b/glass/src/lib/native/include/glass/other/Plot.h @@ -4,19 +4,16 @@ #pragma once -#include - #include "glass/WindowManager.h" -#include "glass/support/IniSaverBase.h" namespace glass { class PlotProvider : private WindowManager { public: - explicit PlotProvider(std::string_view iniName); + explicit PlotProvider(Storage& storage); ~PlotProvider() override; - void GlobalInit() override; + using WindowManager::GlobalInit; /** * Pauses or unpauses all plots. @@ -33,24 +30,6 @@ class PlotProvider : private WindowManager { void DisplayMenu() override; private: - void DisplayWindows() override; - - class IniSaver : public IniSaverBase { - public: - explicit IniSaver(std::string_view typeName, PlotProvider* provider, - bool forSeries); - - void* IniReadOpen(const char* name) override; - void IniReadLine(void* entry, const char* lineStr) override; - void IniWriteAll(ImGuiTextBuffer* out_buf) override; - - private: - PlotProvider* m_provider; - bool m_forSeries; - }; - - IniSaver m_plotSaver; - IniSaver m_seriesSaver; bool m_paused = false; }; diff --git a/glass/src/lib/native/include/glass/support/ColorSetting.h b/glass/src/lib/native/include/glass/support/ColorSetting.h new file mode 100644 index 0000000000..a6604dd747 --- /dev/null +++ b/glass/src/lib/native/include/glass/support/ColorSetting.h @@ -0,0 +1,53 @@ +// 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. + +#pragma once + +#include + +#include + +namespace glass { + +class ColorSetting { + public: + explicit ColorSetting(std::vector& color); + + ImVec4 GetColor() const { + return {m_color[0], m_color[1], m_color[2], m_color[3]}; + } + + float* GetColorFloat() { return m_color.data(); } + const float* GetColorFloat() const { return m_color.data(); } + + void SetColor(const ImVec4& color) { + m_color[0] = color.x; + m_color[1] = color.y; + m_color[2] = color.z; + m_color[3] = color.w; + } + + // updates internal value, returns true on change + bool ColorEdit3(const char* label, ImGuiColorEditFlags flags = 0) { + return ImGui::ColorEdit3(label, m_color.data(), flags); + } + + bool ColorEdit4(const char* label, ImGuiColorEditFlags flags = 0) { + return ImGui::ColorEdit4(label, m_color.data(), flags); + } + + bool ColorPicker3(const char* label, ImGuiColorEditFlags flags = 0) { + return ImGui::ColorPicker3(label, m_color.data(), flags); + } + + bool ColorPicker4(const char* label, ImGuiColorEditFlags flags = 0, + const float* ref_col = nullptr) { + return ImGui::ColorPicker4(label, m_color.data(), flags, ref_col); + } + + private: + std::vector& m_color; +}; + +} // namespace glass diff --git a/glass/src/lib/native/include/glass/support/EnumSetting.h b/glass/src/lib/native/include/glass/support/EnumSetting.h new file mode 100644 index 0000000000..c4f0c26eb6 --- /dev/null +++ b/glass/src/lib/native/include/glass/support/EnumSetting.h @@ -0,0 +1,31 @@ +// 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. + +#pragma once + +#include + +#include + +namespace glass { + +class EnumSetting { + public: + EnumSetting(std::string& str, int defaultValue, + std::initializer_list choices); + + int GetValue() const { return m_value; } + void SetValue(int value); + + // updates internal value, returns true on change + bool Combo(const char* label, int numOptions = -1, + int popup_max_height_in_items = -1); + + private: + std::string& m_str; + wpi::SmallVector m_choices; + int m_value; +}; + +} // namespace glass diff --git a/glass/src/lib/native/include/glass/support/IniSaver.h b/glass/src/lib/native/include/glass/support/IniSaver.h deleted file mode 100644 index a3aa3d0997..0000000000 --- a/glass/src/lib/native/include/glass/support/IniSaver.h +++ /dev/null @@ -1,44 +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. - -#pragma once - -#include - -#include -#include - -#include "glass/support/IniSaverBase.h" - -namespace glass { - -template -class IniSaver : public IniSaverBase { - public: - explicit IniSaver(std::string_view typeName, - IniSaverBackend* backend = nullptr) - : IniSaverBase(typeName, backend) {} - - // pass through useful functions to map - Info& operator[](int index) { return m_map[index]; } - - auto begin() { return m_map.begin(); } - auto end() { return m_map.end(); } - auto find(int index) { return m_map.find(index); } - - auto begin() const { return m_map.begin(); } - auto end() const { return m_map.end(); } - auto find(int index) const { return m_map.find(index); } - - private: - void* IniReadOpen(const char* name) override; - void IniReadLine(void* entry, const char* lineStr) override; - void IniWriteAll(ImGuiTextBuffer* out_buf) override; - - wpi::DenseMap m_map; -}; - -} // namespace glass - -#include "IniSaver.inc" diff --git a/glass/src/lib/native/include/glass/support/IniSaver.inc b/glass/src/lib/native/include/glass/support/IniSaver.inc deleted file mode 100644 index 42efb85ba2..0000000000 --- a/glass/src/lib/native/include/glass/support/IniSaver.inc +++ /dev/null @@ -1,40 +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. - -#pragma once - -#include - -#include - -#include "glass/support/IniSaver.h" - -namespace glass { - -template -void* IniSaver::IniReadOpen(const char* name) { - if (auto num = wpi::parse_integer(name, 10)) { - return &m_map[num.value()]; - } else { - return nullptr; - } -} - -template -void IniSaver::IniReadLine(void* entry, const char* line) { - auto element = static_cast(entry); - auto [name, value] = wpi::split(line, '='); - element->ReadIni(wpi::trim(name), wpi::trim(value)); -} - -template -void IniSaver::IniWriteAll(ImGuiTextBuffer* out_buf) { - for (auto&& it : m_map) { - out_buf->appendf("[%s][%d]\n", GetTypeName(), it.first); - it.second.WriteIni(out_buf); - out_buf->append("\n"); - } -} - -} // namespace glass diff --git a/glass/src/lib/native/include/glass/support/IniSaverBase.h b/glass/src/lib/native/include/glass/support/IniSaverBase.h deleted file mode 100644 index 85ae1e3973..0000000000 --- a/glass/src/lib/native/include/glass/support/IniSaverBase.h +++ /dev/null @@ -1,46 +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. - -#pragma once - -#include -#include - -#include - -namespace glass { - -class IniSaverBase; - -class IniSaverBackend { - public: - virtual ~IniSaverBackend() = default; - virtual void Register(IniSaverBase* iniSaver) = 0; - virtual void Unregister(IniSaverBase* iniSaver) = 0; -}; - -class IniSaverBase { - public: - explicit IniSaverBase(std::string_view typeName, - IniSaverBackend* backend = nullptr); - virtual ~IniSaverBase(); - - void Initialize() { m_backend->Register(this); } - - const char* GetTypeName() const { return m_typeName.c_str(); } - IniSaverBackend* GetBackend() const { return m_backend; } - - IniSaverBase(const IniSaverBase&) = delete; - IniSaverBase& operator=(const IniSaverBase&) = delete; - - virtual void* IniReadOpen(const char* name) = 0; - virtual void IniReadLine(void* entry, const char* lineStr) = 0; - virtual void IniWriteAll(ImGuiTextBuffer* out_buf) = 0; - - private: - std::string m_typeName; - IniSaverBackend* m_backend; -}; - -} // namespace glass diff --git a/glass/src/lib/native/include/glass/support/IniSaverString.h b/glass/src/lib/native/include/glass/support/IniSaverString.h deleted file mode 100644 index 4134219433..0000000000 --- a/glass/src/lib/native/include/glass/support/IniSaverString.h +++ /dev/null @@ -1,55 +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. - -#pragma once - -#include -#include - -#include -#include - -#include "glass/support/IniSaverBase.h" - -namespace glass { - -template -class IniSaverString : public IniSaverBase { - public: - explicit IniSaverString(std::string_view typeName, - IniSaverBackend* backend = nullptr) - : IniSaverBase(typeName, backend) {} - - // pass through useful functions to map - Info& operator[](std::string_view key) { return m_map[key]; } - - template - auto try_emplace(std::string_view key, ArgsTy&&... args) { - return m_map.try_emplace(key, std::forward(args)...); - } - - void erase(typename wpi::StringMap::iterator it) { m_map.erase(it); } - auto erase(std::string_view key) { return m_map.erase(key); } - - auto begin() { return m_map.begin(); } - auto end() { return m_map.end(); } - auto find(std::string_view key) { return m_map.find(key); } - - auto begin() const { return m_map.begin(); } - auto end() const { return m_map.end(); } - auto find(std::string_view key) const { return m_map.find(key); } - - bool empty() const { return m_map.empty(); } - - private: - void* IniReadOpen(const char* name) override; - void IniReadLine(void* entry, const char* lineStr) override; - void IniWriteAll(ImGuiTextBuffer* out_buf) override; - - wpi::StringMap m_map; -}; - -} // namespace glass - -#include "IniSaverString.inc" diff --git a/glass/src/lib/native/include/glass/support/IniSaverString.inc b/glass/src/lib/native/include/glass/support/IniSaverString.inc deleted file mode 100644 index 0d18d29011..0000000000 --- a/glass/src/lib/native/include/glass/support/IniSaverString.inc +++ /dev/null @@ -1,36 +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. - -#pragma once - -#include - -#include - -#include "glass/support/IniSaverString.h" - -namespace glass { - -template -void* IniSaverString::IniReadOpen(const char* name) { - return &m_map[name]; -} - -template -void IniSaverString::IniReadLine(void* entry, const char* line) { - auto element = static_cast(entry); - auto [name, value] = wpi::split(line, '='); - element->ReadIni(wpi::trim(name), wpi::trim(value)); -} - -template -void IniSaverString::IniWriteAll(ImGuiTextBuffer* out_buf) { - for (auto&& it : m_map) { - out_buf->appendf("[%s][%s]\n", GetTypeName(), it.getKey().data()); - it.second.WriteIni(out_buf); - out_buf->append("\n"); - } -} - -} // namespace glass diff --git a/glass/src/lib/native/include/glass/support/IniSaverVector.h b/glass/src/lib/native/include/glass/support/IniSaverVector.h deleted file mode 100644 index e2e57cec39..0000000000 --- a/glass/src/lib/native/include/glass/support/IniSaverVector.h +++ /dev/null @@ -1,31 +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. - -#pragma once - -#include -#include - -#include - -#include "glass/support/IniSaverBase.h" - -namespace glass { - -template -class IniSaverVector : public std::vector, public IniSaverBase { - public: - explicit IniSaverVector(std::string_view typeName, - IniSaverBackend* backend = nullptr) - : IniSaverBase(typeName, backend) {} - - private: - void* IniReadOpen(const char* name) override; - void IniReadLine(void* entry, const char* lineStr) override; - void IniWriteAll(ImGuiTextBuffer* out_buf) override; -}; - -} // namespace glass - -#include "IniSaverVector.inc" diff --git a/glass/src/lib/native/include/glass/support/IniSaverVector.inc b/glass/src/lib/native/include/glass/support/IniSaverVector.inc deleted file mode 100644 index a86b1161d2..0000000000 --- a/glass/src/lib/native/include/glass/support/IniSaverVector.inc +++ /dev/null @@ -1,43 +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. - -#pragma once - -#include - -#include - -#include "glass/support/IniSaverVector.h" - -namespace glass { - -template -void* IniSaverVector::IniReadOpen(const char* name) { - if (auto num = wpi::parse_integer(name, 10)) { - if (num.value() >= this->size()) { - this->resize(num.value() + 1); - } - return &(*this)[num.value()]; - } else { - return nullptr; - } -} - -template -void IniSaverVector::IniReadLine(void* entry, const char* line) { - auto element = static_cast(entry); - auto [name, value] = wpi::split(line, '='); - element->ReadIni(wpi::trim(name), wpi::trim(value)); -} - -template -void IniSaverVector::IniWriteAll(ImGuiTextBuffer* out_buf) { - for (size_t i = 0; i < this->size(); ++i) { - out_buf->appendf("[%s][%d]\n", GetTypeName(), static_cast(i)); - (*this)[i].WriteIni(out_buf); - out_buf->append("\n"); - } -} - -} // namespace glass diff --git a/glass/src/lib/native/include/glass/support/IniSaverInfo.h b/glass/src/lib/native/include/glass/support/NameSetting.h similarity index 57% rename from glass/src/lib/native/include/glass/support/IniSaverInfo.h rename to glass/src/lib/native/include/glass/support/NameSetting.h index 2014e9d28b..e1444e302c 100644 --- a/glass/src/lib/native/include/glass/support/IniSaverInfo.h +++ b/glass/src/lib/native/include/glass/support/NameSetting.h @@ -4,19 +4,21 @@ #pragma once +#include #include #include namespace glass { -class NameInfo { +class NameSetting { public: - NameInfo() { m_name[0] = '\0'; } + explicit NameSetting(std::string& str) : m_name{str} {} - bool HasName() const { return m_name[0] != '\0'; } - void SetName(std::string_view name); - const char* GetName() const { return m_name; } + bool HasName() const { return !m_name.empty(); } + void SetName(std::string_view name) { m_name = name; } + std::string& GetName() { return m_name; } + const std::string& GetName() const { return m_name; } void GetName(char* buf, size_t size, const char* defaultName) const; void GetName(char* buf, size_t size, const char* defaultName, int index) const; @@ -28,8 +30,6 @@ class NameInfo { void GetLabel(char* buf, size_t size, const char* defaultName, int index, int index2) const; - bool ReadIni(std::string_view name, std::string_view value); - void WriteIni(ImGuiTextBuffer* out); void PushEditNameId(int index); void PushEditNameId(const char* name); bool PopupEditName(int index); @@ -37,27 +37,7 @@ class NameInfo { bool InputTextName(const char* label_id, ImGuiInputTextFlags flags = 0); private: - char m_name[64]; -}; - -class OpenInfo { - public: - OpenInfo() = default; - explicit OpenInfo(bool open) : m_open(open) {} - - bool IsOpen() const { return m_open; } - void SetOpen(bool open) { m_open = open; } - bool ReadIni(std::string_view name, std::string_view value); - void WriteIni(ImGuiTextBuffer* out); - - private: - bool m_open = false; -}; - -class NameOpenInfo : public NameInfo, public OpenInfo { - public: - bool ReadIni(std::string_view name, std::string_view value); - void WriteIni(ImGuiTextBuffer* out); + std::string& m_name; }; } // namespace glass diff --git a/glass/src/libnt/native/cpp/NetworkTables.cpp b/glass/src/libnt/native/cpp/NetworkTables.cpp index 596ef0a0ca..bce2b0a306 100644 --- a/glass/src/libnt/native/cpp/NetworkTables.cpp +++ b/glass/src/libnt/native/cpp/NetworkTables.cpp @@ -25,6 +25,7 @@ #include "glass/Context.h" #include "glass/DataSource.h" +#include "glass/Storage.h" using namespace glass; @@ -731,15 +732,15 @@ void glass::DisplayNetworkTables(NetworkTablesModel* model, void NetworkTablesFlagsSettings::Update() { if (!m_pTreeView) { auto& storage = GetStorage(); - m_pTreeView = storage.GetBoolRef( - "tree", m_defaultFlags & NetworkTablesFlags_TreeView); - m_pShowConnections = storage.GetBoolRef( + m_pTreeView = + &storage.GetBool("tree", m_defaultFlags & NetworkTablesFlags_TreeView); + m_pShowConnections = &storage.GetBool( "connections", m_defaultFlags & NetworkTablesFlags_ShowConnections); - m_pShowFlags = storage.GetBoolRef( + m_pShowFlags = &storage.GetBool( "flags", m_defaultFlags & NetworkTablesFlags_ShowFlags); - m_pShowTimestamp = storage.GetBoolRef( + m_pShowTimestamp = &storage.GetBool( "timestamp", m_defaultFlags & NetworkTablesFlags_ShowTimestamp); - m_pCreateNoncanonicalKeys = storage.GetBoolRef( + m_pCreateNoncanonicalKeys = &storage.GetBool( "createNonCanonical", m_defaultFlags & NetworkTablesFlags_CreateNoncanonicalKeys); } diff --git a/glass/src/libnt/native/cpp/NetworkTablesProvider.cpp b/glass/src/libnt/native/cpp/NetworkTablesProvider.cpp index 8d991cb78d..9ccbb2e904 100644 --- a/glass/src/libnt/native/cpp/NetworkTablesProvider.cpp +++ b/glass/src/libnt/native/cpp/NetworkTablesProvider.cpp @@ -12,23 +12,53 @@ #include #include +#include "glass/Storage.h" + using namespace glass; -NetworkTablesProvider::NetworkTablesProvider(std::string_view iniName) - : NetworkTablesProvider{iniName, nt::GetDefaultInstance()} {} +NetworkTablesProvider::NetworkTablesProvider(Storage& storage) + : NetworkTablesProvider{storage, nt::GetDefaultInstance()} {} -NetworkTablesProvider::NetworkTablesProvider(std::string_view iniName, - NT_Inst inst) - : Provider{fmt::format("{}Window", iniName)}, +NetworkTablesProvider::NetworkTablesProvider(Storage& storage, NT_Inst inst) + : Provider{storage.GetChild("windows")}, m_nt{inst}, - m_typeCache{iniName} { - m_nt.AddListener("", NT_NOTIFY_LOCAL | NT_NOTIFY_NEW | NT_NOTIFY_DELETE | - NT_NOTIFY_IMMEDIATE); -} + m_typeCache{storage.GetChild("types")} { + storage.SetCustomApply([this] { + m_listener = + m_nt.AddListener("", NT_NOTIFY_LOCAL | NT_NOTIFY_NEW | + NT_NOTIFY_DELETE | NT_NOTIFY_IMMEDIATE); + for (auto&& childIt : m_storage.GetChildren()) { + auto id = childIt.key(); + auto typePtr = m_typeCache.FindValue(id); + if (!typePtr || typePtr->type != Storage::Value::kString) { + continue; + } -void NetworkTablesProvider::GlobalInit() { - Provider::GlobalInit(); - wpi::gui::AddInit([this] { m_typeCache.Initialize(); }); + // only handle ones where we have a builder + auto builderIt = m_typeMap.find(typePtr->stringVal); + if (builderIt == m_typeMap.end()) { + continue; + } + + auto entry = GetOrCreateView( + builderIt->second, + nt::GetEntry(m_nt.GetInstance(), fmt::format("{}/.type", id)), id); + if (entry) { + Show(entry, nullptr); + } + } + }); + storage.SetCustomClear([this, &storage] { + nt::RemoveEntryListener(m_listener); + m_listener = 0; + for (auto&& modelEntry : m_modelEntries) { + modelEntry->model.reset(); + } + m_viewEntries.clear(); + m_windows.clear(); + m_typeCache.EraseAll(); + storage.ClearValues(); + }); } void NetworkTablesProvider::DisplayMenu() { @@ -98,33 +128,7 @@ void NetworkTablesProvider::Update() { } else if (event.flags & NT_NOTIFY_NEW) { GetOrCreateView(builderIt->second, event.entry, tableName); // cache the type - m_typeCache[tableName].SetName(event.value->GetString()); - } - } - - // check for visible windows that need displays (typically this is due to - // file loading) - for (auto&& window : m_windows) { - if (!window->IsVisible() || window->HasView()) { - continue; - } - auto id = window->GetId(); - auto typeIt = m_typeCache.find(id); - if (typeIt == m_typeCache.end()) { - continue; - } - - // only handle ones where we have a builder - auto builderIt = m_typeMap.find(typeIt->second.GetName()); - if (builderIt == m_typeMap.end()) { - continue; - } - - auto entry = GetOrCreateView( - builderIt->second, - nt::GetEntry(m_nt.GetInstance(), fmt::format("{}/.type", id)), id); - if (entry) { - Show(entry, window.get()); + m_typeCache.SetString(tableName, event.value->GetString()); } } } @@ -153,7 +157,7 @@ void NetworkTablesProvider::Show(ViewEntry* entry, Window* window) { // the window might exist and we're just not associated to it yet if (!window) { - window = GetOrAddWindow(entry->name, true); + window = GetOrAddWindow(entry->name, true, Window::kHide); } if (!window) { return; diff --git a/glass/src/libnt/native/cpp/NetworkTablesSettings.cpp b/glass/src/libnt/native/cpp/NetworkTablesSettings.cpp index 28f4de4bcd..d1fb341bcf 100644 --- a/glass/src/libnt/native/cpp/NetworkTablesSettings.cpp +++ b/glass/src/libnt/native/cpp/NetworkTablesSettings.cpp @@ -15,6 +15,7 @@ #include #include "glass/Context.h" +#include "glass/Storage.h" using namespace glass; @@ -81,15 +82,12 @@ void NetworkTablesSettings::Thread::Main() { } } -NetworkTablesSettings::NetworkTablesSettings(NT_Inst inst, - const char* storageName) { - auto& storage = glass::GetStorage(storageName); - m_pMode = storage.GetIntRef("mode"); - m_pIniName = storage.GetStringRef("iniName", "networktables.ini"); - m_pServerTeam = storage.GetStringRef("serverTeam"); - m_pListenAddress = storage.GetStringRef("listenAddress"); - m_pDsClient = storage.GetBoolRef("dsClient", true); - +NetworkTablesSettings::NetworkTablesSettings(Storage& storage, NT_Inst inst) + : m_mode{storage.GetString("mode"), 0, {"Disabled", "Client", "Server"}}, + m_iniName{storage.GetString("iniName", "networktables.ini")}, + m_serverTeam{storage.GetString("serverTeam")}, + m_listenAddress{storage.GetString("listenAddress")}, + m_dsClient{storage.GetBool("dsClient", true)} { m_thread.Start(inst); } @@ -102,25 +100,24 @@ void NetworkTablesSettings::Update() { // do actual operation on thread auto thr = m_thread.GetThread(); thr->m_restart = true; - thr->m_mode = *m_pMode; - thr->m_iniName = *m_pIniName; - thr->m_serverTeam = *m_pServerTeam; - thr->m_listenAddress = *m_pListenAddress; - thr->m_dsClient = *m_pDsClient; + thr->m_mode = m_mode.GetValue(); + thr->m_iniName = m_iniName; + thr->m_serverTeam = m_serverTeam; + thr->m_listenAddress = m_listenAddress; + thr->m_dsClient = m_dsClient; thr->m_cond.notify_one(); } bool NetworkTablesSettings::Display() { - static const char* modeOptions[] = {"Disabled", "Client", "Server"}; - ImGui::Combo("Mode", m_pMode, modeOptions, m_serverOption ? 3 : 2); - switch (*m_pMode) { + m_mode.Combo("Mode", m_serverOption ? 3 : 2); + switch (m_mode.GetValue()) { case 1: - ImGui::InputText("Team/IP", m_pServerTeam); - ImGui::Checkbox("Get Address from DS", m_pDsClient); + ImGui::InputText("Team/IP", &m_serverTeam); + ImGui::Checkbox("Get Address from DS", &m_dsClient); break; case 2: - ImGui::InputText("Listen Address", m_pListenAddress); - ImGui::InputText("ini Filename", m_pIniName); + ImGui::InputText("Listen Address", &m_listenAddress); + ImGui::InputText("ini Filename", &m_iniName); break; default: break; diff --git a/glass/src/libnt/native/include/glass/networktables/NetworkTablesProvider.h b/glass/src/libnt/native/include/glass/networktables/NetworkTablesProvider.h index 17374cae9e..a8f0f9b4e1 100644 --- a/glass/src/libnt/native/include/glass/networktables/NetworkTablesProvider.h +++ b/glass/src/libnt/native/include/glass/networktables/NetworkTablesProvider.h @@ -9,14 +9,13 @@ #include #include +#include #include #include #include "glass/Model.h" #include "glass/Provider.h" #include "glass/networktables/NetworkTablesHelper.h" -#include "glass/support/IniSaverInfo.h" -#include "glass/support/IniSaverString.h" namespace glass { @@ -41,8 +40,8 @@ class NetworkTablesProvider : private Provider { using Provider::CreateModelFunc; using Provider::CreateViewFunc; - explicit NetworkTablesProvider(std::string_view iniName); - NetworkTablesProvider(std::string_view iniName, NT_Inst inst); + explicit NetworkTablesProvider(Storage& storage); + NetworkTablesProvider(Storage& storage, NT_Inst inst); /** * Get the NetworkTables instance being used for this provider. @@ -55,7 +54,7 @@ class NetworkTablesProvider : private Provider { * Perform global initialization. This should be called prior to * wpi::gui::Initialize(). */ - void GlobalInit() override; + void GlobalInit() override { Provider::GlobalInit(); } /** * Displays menu contents as a tree of available NetworkTables views. @@ -72,15 +71,14 @@ class NetworkTablesProvider : private Provider { void Register(std::string_view typeName, CreateModelFunc createModel, CreateViewFunc createView); - using WindowManager::AddWindow; - private: void Update() override; NetworkTablesHelper m_nt; + NT_EntryListener m_listener{0}; // cached mapping from table name to type string - IniSaverString m_typeCache; + Storage& m_typeCache; struct Builder { CreateModelFunc createModel; diff --git a/glass/src/libnt/native/include/glass/networktables/NetworkTablesSettings.h b/glass/src/libnt/native/include/glass/networktables/NetworkTablesSettings.h index 7738541d79..a1a1c9334f 100644 --- a/glass/src/libnt/native/include/glass/networktables/NetworkTablesSettings.h +++ b/glass/src/libnt/native/include/glass/networktables/NetworkTablesSettings.h @@ -9,6 +9,8 @@ #include #include +#include "glass/support/EnumSetting.h" + namespace wpi { template class SmallVectorImpl; @@ -16,11 +18,12 @@ class SmallVectorImpl; namespace glass { +class Storage; + class NetworkTablesSettings { public: - explicit NetworkTablesSettings( - NT_Inst inst = nt::GetDefaultInstance(), - const char* storageName = "NetworkTables Settings"); + explicit NetworkTablesSettings(Storage& storage, + NT_Inst inst = nt::GetDefaultInstance()); /** * Enables or disables the server option. Default is enabled. @@ -33,11 +36,11 @@ class NetworkTablesSettings { private: bool m_restart = true; bool m_serverOption = true; - int* m_pMode; - std::string* m_pIniName; - std::string* m_pServerTeam; - std::string* m_pListenAddress; - bool* m_pDsClient; + EnumSetting m_mode; + std::string& m_iniName; + std::string& m_serverTeam; + std::string& m_listenAddress; + bool& m_dsClient; class Thread : public wpi::SafeThread { public: diff --git a/outlineviewer/src/main/native/cpp/main.cpp b/outlineviewer/src/main/native/cpp/main.cpp index c67cb5cb07..2c2debc197 100644 --- a/outlineviewer/src/main/native/cpp/main.cpp +++ b/outlineviewer/src/main/native/cpp/main.cpp @@ -11,7 +11,9 @@ #include #include "glass/Context.h" +#include "glass/MainMenuBar.h" #include "glass/Model.h" +#include "glass/Storage.h" #include "glass/networktables/NetworkTables.h" #include "glass/networktables/NetworkTablesSettings.h" #include "glass/other/Log.h" @@ -34,6 +36,7 @@ static std::unique_ptr gModel; static std::unique_ptr gSettings; static glass::LogData gLog; static glass::NetworkTablesFlagsSettings gFlagsSettings; +static glass::MainMenuBar gMainMenu; static void NtInitialize() { // update window title when connection status changes @@ -87,7 +90,8 @@ static void NtInitialize() { gui::AddEarlyExecute([] { gModel->Update(); }); // NetworkTables settings window - gSettings = std::make_unique(); + gSettings = std::make_unique( + glass::GetStorageRoot().GetChild("NetworkTables Settings")); gui::AddEarlyExecute([] { gSettings->Update(); }); } @@ -114,6 +118,7 @@ static void DisplayGui() { // main menu ImGui::BeginMenuBar(); + gMainMenu.WorkspaceMenu(); gui::EmitViewMenu(); if (ImGui::BeginMenu("View")) { gFlagsSettings.DisplayMenu(); @@ -179,6 +184,8 @@ static void DisplayGui() { ImGui::Text("OutlineViewer"); ImGui::Separator(); ImGui::Text("v%s", GetWPILibVersion()); + ImGui::Separator(); + ImGui::Text("Save location: %s", glass::GetStorageDir().c_str()); if (ImGui::Button("Close")) { ImGui::CloseCurrentPopup(); } @@ -194,9 +201,16 @@ static void DisplayGui() { #ifdef _WIN32 int __stdcall WinMain(void* hInstance, void* hPrevInstance, char* pCmdLine, int nCmdShow) { + int argc = __argc; + char** argv = __argv; #else -int main() { +int main(int argc, char** argv) { #endif + std::string_view saveDir; + if (argc == 2) { + saveDir = argv[1]; + } + gui::CreateContext(); glass::CreateContext(); @@ -208,7 +222,10 @@ int main() { gui::AddIcon(ov::GetResource_ov_256_png()); gui::AddIcon(ov::GetResource_ov_512_png()); - gui::ConfigurePlatformSaveFile("outlineviewer.ini"); + glass::SetStorageName("outlineviewer"); + glass::SetStorageDir(saveDir.empty() ? gui::GetPlatformSaveFileDir() + : saveDir); + gui::AddInit(NtInitialize); gui::AddLateExecute(DisplayGui); diff --git a/simulation/halsim_gui/src/main/native/cpp/AddressableLEDGui.cpp b/simulation/halsim_gui/src/main/native/cpp/AddressableLEDGui.cpp index 23a52381f2..d6409cdba6 100644 --- a/simulation/halsim_gui/src/main/native/cpp/AddressableLEDGui.cpp +++ b/simulation/halsim_gui/src/main/native/cpp/AddressableLEDGui.cpp @@ -100,7 +100,7 @@ static bool AddressableLEDsExists() { } void AddressableLEDGui::Initialize() { - HALSimGui::halProvider.Register( + HALSimGui::halProvider->Register( "Addressable LEDs", [] { return AddressableLEDsExists(); }, [] { return std::make_unique(); }, [](glass::Window* win, glass::Model* model) { diff --git a/simulation/halsim_gui/src/main/native/cpp/AnalogInputSimGui.cpp b/simulation/halsim_gui/src/main/native/cpp/AnalogInputSimGui.cpp index 5a3b2d8108..e100d5dd32 100644 --- a/simulation/halsim_gui/src/main/native/cpp/AnalogInputSimGui.cpp +++ b/simulation/halsim_gui/src/main/native/cpp/AnalogInputSimGui.cpp @@ -109,7 +109,7 @@ static bool AnalogInputsAnyInitialized() { } void AnalogInputSimGui::Initialize() { - HALSimGui::halProvider.Register( + HALSimGui::halProvider->Register( "Analog Inputs", AnalogInputsAnyInitialized, [] { return std::make_unique(); }, [](glass::Window* win, glass::Model* model) { diff --git a/simulation/halsim_gui/src/main/native/cpp/DIOSimGui.cpp b/simulation/halsim_gui/src/main/native/cpp/DIOSimGui.cpp index 22c06dd400..150e4bf436 100644 --- a/simulation/halsim_gui/src/main/native/cpp/DIOSimGui.cpp +++ b/simulation/halsim_gui/src/main/native/cpp/DIOSimGui.cpp @@ -232,14 +232,14 @@ static bool DIOAnyInitialized() { } void DIOSimGui::Initialize() { - HALSimGui::halProvider.Register( + HALSimGui::halProvider->Register( "DIO", DIOAnyInitialized, [] { return std::make_unique(); }, [](glass::Window* win, glass::Model* model) { win->SetFlags(ImGuiWindowFlags_AlwaysAutoResize); win->SetDefaultPos(470, 20); return glass::MakeFunctionView([=] { glass::DisplayDIOs(static_cast(model), - HALSimGui::halProvider.AreOutputsEnabled()); + HALSimGui::halProvider->AreOutputsEnabled()); }); }); } diff --git a/simulation/halsim_gui/src/main/native/cpp/DriverStationGui.cpp b/simulation/halsim_gui/src/main/native/cpp/DriverStationGui.cpp index 42ec8a35b7..78dbb676a5 100644 --- a/simulation/halsim_gui/src/main/native/cpp/DriverStationGui.cpp +++ b/simulation/halsim_gui/src/main/native/cpp/DriverStationGui.cpp @@ -4,9 +4,11 @@ #include "DriverStationGui.h" +#include +#include #include #include -#include +#include #include #include @@ -93,7 +95,7 @@ class GlfwSystemJoystick : public SystemJoystick { class KeyboardJoystick : public SystemJoystick { public: - explicit KeyboardJoystick(int index); + KeyboardJoystick(glass::Storage& storage, int index); void SettingsDisplay() override; void Update() override; @@ -107,9 +109,6 @@ class KeyboardJoystick : public SystemJoystick { void ClearKey(int key); virtual const char* GetKeyName(int key) const = 0; - void ReadIni(std::string_view name, std::string_view value); - void WriteIni(ImGuiTextBuffer* out_buf) const; - protected: void EditKey(const char* label, int* key); @@ -121,44 +120,57 @@ class KeyboardJoystick : public SystemJoystick { HALJoystickData m_data; + int& m_axisCount; + int& m_buttonCount; + int& m_povCount; + struct AxisConfig { - int incKey = -1; - int decKey = -1; - float keyRate = 0.05f; - float decayRate = 0.05f; - float maxAbsValue = 1.0f; + explicit AxisConfig(glass::Storage& storage); + + int& incKey; + int& decKey; + float& keyRate; + float& decayRate; + float& maxAbsValue; }; - AxisConfig m_axisConfig[HAL_kMaxJoystickAxes]; + + std::vector>& m_axisStorage; + std::vector m_axisConfig; static constexpr int kMaxButtonCount = 32; - int m_buttonKey[kMaxButtonCount]; + std::vector& m_buttonKey; struct PovConfig { - int key0 = -1; - int key45 = -1; - int key90 = -1; - int key135 = -1; - int key180 = -1; - int key225 = -1; - int key270 = -1; - int key315 = -1; + explicit PovConfig(glass::Storage& storage); + + int& key0; + int& key45; + int& key90; + int& key135; + int& key180; + int& key225; + int& key270; + int& key315; }; - PovConfig m_povConfig[HAL_kMaxJoystickPOVs]; + std::vector>& m_povStorage; + std::vector m_povConfig; }; class GlfwKeyboardJoystick : public KeyboardJoystick { public: - explicit GlfwKeyboardJoystick(int index, bool noDefaults = false); + GlfwKeyboardJoystick(glass::Storage& storage, int index); const char* GetKeyName(int key) const override; }; struct RobotJoystick { - glass::NameInfo name; - std::string guid; + explicit RobotJoystick(glass::Storage& storage); + + glass::NameSetting name; + std::string& guid; const SystemJoystick* sys = nullptr; - bool useGamepad = false; + bool& useGamepad; // = false; HALJoystickData data; @@ -277,23 +289,24 @@ static int gNumGlfwJoysticks = 0; static std::vector> gKeyboardJoysticks; // robot joysticks -static RobotJoystick gRobotJoysticks[HAL_kMaxJoysticks]; +static std::vector gRobotJoysticks; static std::unique_ptr gJoystickSources[HAL_kMaxJoysticks]; // FMS static std::unique_ptr gFMSModel; // Window management -DSManager DriverStationGui::dsManager{"DSManager"}; +std::unique_ptr DriverStationGui::dsManager; -static bool gDisableDS = false; -static bool gZeroDisconnectedJoysticks = true; -static bool gUseEnableDisableHotkeys = false; -static bool gUseEstopHotkey = false; -static std::atomic* gDSSocketConnected = nullptr; +static bool* gpDisableDS = nullptr; +static bool* gpZeroDisconnectedJoysticks = nullptr; +static bool* gpUseEnableDisableHotkeys = nullptr; +static bool* gpUseEstopHotkey = nullptr; +static std::atomic* gpDSSocketConnected = nullptr; static inline bool IsDSDisabled() { - return gDisableDS || (gDSSocketConnected && *gDSSocketConnected); + return (gpDisableDS != nullptr && *gpDisableDS) || + (gpDSSocketConnected && *gpDSSocketConnected); } JoystickModel::JoystickModel(int index) : m_index{index} { @@ -357,133 +370,6 @@ void JoystickModel::CallbackFunc(const char*, void* param, const HAL_Value*) { } } -// read/write joystick mapping to ini file -static void* JoystickReadOpen(ImGuiContext* ctx, ImGuiSettingsHandler* handler, - const char* name) { - int num = wpi::parse_integer(name, 10).value_or(-1); - if (num < 0 || num >= HAL_kMaxJoysticks) { - return nullptr; - } - return &gRobotJoysticks[num]; -} - -static void JoystickReadLine(ImGuiContext* ctx, ImGuiSettingsHandler* handler, - void* entry, const char* line) { - RobotJoystick* joy = static_cast(entry); - // format: guid=guid or useGamepad=0/1 - auto [name, value] = wpi::split(line, '='); - name = wpi::trim(name); - value = wpi::trim(value); - if (name == "guid") { - joy->guid = value; - } else if (name == "useGamepad") { - if (auto num = wpi::parse_integer(value, 10)) { - joy->useGamepad = num.value(); - } - } else { - joy->name.ReadIni(name, value); - } -} - -static void JoystickWriteAll(ImGuiContext* ctx, ImGuiSettingsHandler* handler, - ImGuiTextBuffer* out_buf) { - for (int i = 0; i < HAL_kMaxJoysticks; ++i) { - auto& joy = gRobotJoysticks[i]; - if (!joy.name.HasName() && !joy.sys) { - continue; - } - out_buf->appendf("[Joystick][%d]\nuseGamepad=%d\n", i, - joy.useGamepad ? 1 : 0); - if (joy.name.HasName()) { - joy.name.WriteIni(out_buf); - } - if (joy.sys) { - const char* guid = joy.sys->GetGUID(); - if (guid) { - out_buf->appendf("guid=%s\n", guid); - } - } - out_buf->append("\n"); - } -} - -// read/write keyboard joystick mapping to ini file -static void* KeyboardJoystickReadOpen(ImGuiContext* ctx, - ImGuiSettingsHandler* handler, - const char* name) { - int num = wpi::parse_integer(name, 10).value_or(-1); - if (num < 0 || num >= static_cast(gKeyboardJoysticks.size())) { - return nullptr; - } - auto joy = gKeyboardJoysticks[num].get(); - *joy = GlfwKeyboardJoystick(num, true); - return joy; -} - -static void KeyboardJoystickReadLine(ImGuiContext* ctx, - ImGuiSettingsHandler* handler, void* entry, - const char* line) { - auto joy = static_cast(entry); - // format: guid=guid or useGamepad=0/1 - auto [name, value] = wpi::split(line, '='); - joy->ReadIni(wpi::trim(name), wpi::trim(value)); -} - -static void KeyboardJoystickWriteAll(ImGuiContext* ctx, - ImGuiSettingsHandler* handler, - ImGuiTextBuffer* out_buf) { - for (unsigned int i = 0; i < gKeyboardJoysticks.size(); ++i) { - out_buf->appendf("[KeyboardJoystick][%u]\n", i); - gKeyboardJoysticks[i]->WriteIni(out_buf); - out_buf->append("\n"); - } -} - -// read/write DS settings to ini file -static void* DriverStationReadOpen(ImGuiContext* ctx, - ImGuiSettingsHandler* handler, - const char* name) { - if (name == std::string_view{"Main"}) { - return &gDisableDS; - } - return nullptr; -} - -static void DriverStationReadLine(ImGuiContext* ctx, - ImGuiSettingsHandler* handler, void* entry, - const char* line) { - auto [name, value] = wpi::split(line, '='); - name = wpi::trim(name); - value = wpi::trim(value); - if (name == "disable") { - if (auto num = wpi::parse_integer(value, 10)) { - gDisableDS = num.value(); - } - } else if (name == "zeroDisconnectedJoysticks") { - if (auto num = wpi::parse_integer(value, 10)) { - gZeroDisconnectedJoysticks = num.value(); - } - } else if (name == "enableDisableKeys") { - if (auto num = wpi::parse_integer(value, 10)) { - gUseEnableDisableHotkeys = num.value(); - } - } else if (name == "estopKey") { - if (auto num = wpi::parse_integer(value, 10)) { - gUseEstopHotkey = num.value(); - } - } -} - -static void DriverStationWriteAll(ImGuiContext* ctx, - ImGuiSettingsHandler* handler, - ImGuiTextBuffer* out_buf) { - out_buf->appendf( - "[DriverStation][Main]\ndisable=%d\nzeroDisconnectedJoysticks=%d\n" - "enableDisableKeys=%d\nestopKey=%d\n\n", - gDisableDS ? 1 : 0, gZeroDisconnectedJoysticks ? 1 : 0, - gUseEnableDisableHotkeys ? 1 : 0, gUseEstopHotkey ? 1 : 0); -} - void GlfwSystemJoystick::Update() { bool wasPresent = m_present; m_present = glfwJoystickPresent(m_index); @@ -520,7 +406,6 @@ void GlfwSystemJoystick::Update() { for (auto&& joy : gRobotJoysticks) { if (guid == joy.guid) { joy.sys = this; - joy.guid.clear(); break; } } @@ -616,21 +501,84 @@ void GlfwSystemJoystick::GetData(HALJoystickData* data, bool mapGamepad) const { } } -KeyboardJoystick::KeyboardJoystick(int index) : m_index{index} { +KeyboardJoystick::AxisConfig::AxisConfig(glass::Storage& storage) + : incKey{storage.GetInt("incKey", -1)}, + decKey{storage.GetInt("decKey", -1)}, + keyRate{storage.GetFloat("keyRate", 0.05f)}, + decayRate{storage.GetFloat("decayRate", 0.05f)}, + maxAbsValue{storage.GetFloat("maxAbsValue", 1.0f)} { + // sanity check the key ranges + if (incKey < -1 || incKey >= IM_ARRAYSIZE(ImGuiIO::KeysDown)) { + incKey = -1; + } + if (decKey < -1 || decKey >= IM_ARRAYSIZE(ImGuiIO::KeysDown)) { + decKey = -1; + } +} + +KeyboardJoystick::PovConfig::PovConfig(glass::Storage& storage) + : key0{storage.GetInt("key0", -1)}, + key45{storage.GetInt("key45", -1)}, + key90{storage.GetInt("key90", -1)}, + key135{storage.GetInt("key135", -1)}, + key180{storage.GetInt("key180", -1)}, + key225{storage.GetInt("key225", -1)}, + key270{storage.GetInt("key270", -1)}, + key315{storage.GetInt("key315", -1)} { + // sanity check the key ranges + if (key0 < -1 || key0 >= IM_ARRAYSIZE(ImGuiIO::KeysDown)) { + key0 = -1; + } + if (key45 < -1 || key45 >= IM_ARRAYSIZE(ImGuiIO::KeysDown)) { + key45 = -1; + } + if (key90 < -1 || key90 >= IM_ARRAYSIZE(ImGuiIO::KeysDown)) { + key90 = -1; + } + if (key135 < -1 || key135 >= IM_ARRAYSIZE(ImGuiIO::KeysDown)) { + key135 = -1; + } + if (key180 < -1 || key180 >= IM_ARRAYSIZE(ImGuiIO::KeysDown)) { + key180 = -1; + } + if (key225 < -1 || key225 >= IM_ARRAYSIZE(ImGuiIO::KeysDown)) { + key225 = -1; + } + if (key270 < -1 || key270 >= IM_ARRAYSIZE(ImGuiIO::KeysDown)) { + key270 = -1; + } + if (key315 < -1 || key315 >= IM_ARRAYSIZE(ImGuiIO::KeysDown)) { + key315 = -1; + } +} + +KeyboardJoystick::KeyboardJoystick(glass::Storage& storage, int index) + : m_index{index}, + m_axisCount{storage.GetInt("axisCount", -1)}, + m_buttonCount{storage.GetInt("buttonCount", -1)}, + m_povCount{storage.GetInt("povCount", -1)}, + m_axisStorage{storage.GetChildArray("axisConfig")}, + m_buttonKey{storage.GetIntArray("buttonKeys")}, + m_povStorage{storage.GetChildArray("povConfig")} { std::snprintf(m_name, sizeof(m_name), "Keyboard %d", index); std::snprintf(m_guid, sizeof(m_guid), "Keyboard%d", index); // init axes - m_data.axes.count = 0; + for (auto&& axisConfig : m_axisStorage) { + m_axisConfig.emplace_back(*axisConfig); + } - // init buttons - m_data.buttons.count = 0; - for (int i = 0; i < kMaxButtonCount; ++i) { - m_buttonKey[i] = -1; + // sanity check the button key ranges + for (auto&& key : m_buttonKey) { + if (key < -1 || key >= IM_ARRAYSIZE(ImGuiIO::KeysDown)) { + key = -1; + } } // init POVs - m_data.povs.count = 0; + for (auto&& povConfig : m_povStorage) { + m_povConfig.emplace_back(*povConfig); + } // init desc structure m_data.desc.isXbox = 0; @@ -678,17 +626,18 @@ void KeyboardJoystick::SettingsDisplay() { // axes if (ImGui::CollapsingHeader("Axes", ImGuiTreeNodeFlags_DefaultOpen)) { ImGui::PushID("Axes"); - int axisCount = m_data.axes.count; - if (ImGui::InputInt("Count", &axisCount)) { - if (axisCount < 0) { - axisCount = 0; + if (ImGui::InputInt("Count", &m_axisCount)) { + if (m_axisCount < 0) { + m_axisCount = 0; + } else if (m_axisCount > HAL_kMaxJoystickAxes) { + m_axisCount = HAL_kMaxJoystickAxes; } - if (axisCount > HAL_kMaxJoystickAxes) { - axisCount = HAL_kMaxJoystickAxes; - } - m_data.axes.count = axisCount; } - for (int i = 0; i < axisCount; ++i) { + while (m_axisCount > static_cast(m_axisConfig.size())) { + m_axisStorage.emplace_back(std::make_unique()); + m_axisConfig.emplace_back(*m_axisStorage.back()); + } + for (int i = 0; i < m_axisCount; ++i) { std::snprintf(label, sizeof(label), "Axis %d", i); if (ImGui::TreeNodeEx(label, ImGuiTreeNodeFlags_DefaultOpen)) { EditKey("Increase", &m_axisConfig[i].incKey); @@ -710,17 +659,18 @@ void KeyboardJoystick::SettingsDisplay() { // buttons if (ImGui::CollapsingHeader("Buttons", ImGuiTreeNodeFlags_DefaultOpen)) { ImGui::PushID("Buttons"); - int buttonCount = m_data.buttons.count; - if (ImGui::InputInt("Count", &buttonCount)) { - if (buttonCount < 0) { - buttonCount = 0; + if (ImGui::InputInt("Count", &m_buttonCount)) { + if (m_buttonCount < 0) { + m_buttonCount = 0; } - if (buttonCount > kMaxButtonCount) { - buttonCount = kMaxButtonCount; + if (m_buttonCount > kMaxButtonCount) { + m_buttonCount = kMaxButtonCount; } - m_data.buttons.count = buttonCount; } - for (int i = 0; i < buttonCount; ++i) { + while (m_buttonCount > static_cast(m_buttonKey.size())) { + m_buttonKey.emplace_back(-1); + } + for (int i = 0; i < m_buttonCount; ++i) { std::snprintf(label, sizeof(label), "Button %d", i + 1); EditKey(label, &m_buttonKey[i]); } @@ -730,17 +680,19 @@ void KeyboardJoystick::SettingsDisplay() { // povs if (ImGui::CollapsingHeader("POVs", ImGuiTreeNodeFlags_DefaultOpen)) { ImGui::PushID("POVs"); - int povCount = m_data.povs.count; - if (ImGui::InputInt("Count", &povCount)) { - if (povCount < 0) { - povCount = 0; + if (ImGui::InputInt("Count", &m_povCount)) { + if (m_povCount < 0) { + m_povCount = 0; } - if (povCount > HAL_kMaxJoystickPOVs) { - povCount = HAL_kMaxJoystickPOVs; + if (m_povCount > HAL_kMaxJoystickPOVs) { + m_povCount = HAL_kMaxJoystickPOVs; } - m_data.povs.count = povCount; } - for (int i = 0; i < povCount; ++i) { + while (m_povCount > static_cast(m_povConfig.size())) { + m_povStorage.emplace_back(std::make_unique()); + m_povConfig.emplace_back(*m_povStorage.back()); + } + for (int i = 0; i < m_povCount; ++i) { std::snprintf(label, sizeof(label), "POV %d", i); if (ImGui::TreeNodeEx(label, ImGuiTreeNodeFlags_DefaultOpen)) { EditKey(" 0 deg", &m_povConfig[i].key0); @@ -767,6 +719,23 @@ static inline bool IsKeyDown(ImGuiIO& io, int key) { void KeyboardJoystick::Update() { ImGuiIO& io = ImGui::GetIO(); + if (m_axisCount < 0) { + m_axisCount = 0; + } + if (m_buttonCount < 0) { + m_buttonCount = 0; + } + if (m_povCount < 0) { + m_povCount = 0; + } + + m_data.axes.count = + (std::min)(m_axisCount, static_cast(m_axisConfig.size())); + m_data.buttons.count = + (std::min)(m_buttonCount, static_cast(m_buttonKey.size())); + m_data.povs.count = + (std::min)(m_povCount, static_cast(m_povConfig.size())); + if (m_data.axes.count > 0 || m_data.buttons.count > 0 || m_data.povs.count > 0) { m_present = true; @@ -842,7 +811,6 @@ void KeyboardJoystick::Update() { for (auto&& joy : gRobotJoysticks) { if (m_guid == joy.guid) { joy.sys = this; - joy.guid.clear(); break; } } @@ -895,177 +863,88 @@ void KeyboardJoystick::ClearKey(int key) { } } -void KeyboardJoystick::ReadIni(std::string_view name, std::string_view value) { - if (wpi::starts_with(name, "axis")) { - name.remove_prefix(4); - if (name == "Count") { - if (auto v = wpi::parse_integer(value, 10)) { - m_data.axes.count = (std::min)(v.value(), HAL_kMaxJoystickAxes); - } - return; - } - - auto index = wpi::consume_integer(&name, 10).value_or( - HAL_kMaxJoystickAxes); - if (index >= HAL_kMaxJoystickAxes) { - return; - } - if (name == "incKey") { - if (auto v = wpi::parse_integer(value, 10)) { - m_axisConfig[index].incKey = v.value(); - } - } else if (name == "decKey") { - if (auto v = wpi::parse_integer(value, 10)) { - m_axisConfig[index].decKey = v.value(); - } - } else if (name == "keyRate") { - if (auto v = wpi::parse_float(value)) { - m_axisConfig[index].keyRate = v.value(); - } - } else if (name == "decayRate") { - if (auto v = wpi::parse_float(value)) { - m_axisConfig[index].decayRate = v.value(); - } - } else if (name == "maxAbsValue") { - if (auto v = wpi::parse_float(value)) { - m_axisConfig[index].maxAbsValue = v.value(); - } - } - } else if (wpi::starts_with(name, "button")) { - name.remove_prefix(6); - if (name == "Count") { - if (auto v = wpi::parse_integer(value, 10)) { - m_data.buttons.count = (std::min)(v.value(), kMaxButtonCount); - } - return; - } - - auto index = - wpi::parse_integer(name, 10).value_or(kMaxButtonCount); - if (index >= kMaxButtonCount) { - return; - } - int v = wpi::parse_integer(value, 10).value_or(-1); - if (v < 0 || v >= IM_ARRAYSIZE(ImGuiIO::KeysDown)) { - return; - } - m_buttonKey[index] = v; - } else if (wpi::starts_with(name, "pov")) { - name.remove_prefix(3); - if (name == "Count") { - if (auto v = wpi::parse_integer(value, 10)) { - m_data.povs.count = (std::min)(v.value(), HAL_kMaxJoystickPOVs); - } - return; - } - - auto index = wpi::consume_integer(&name, 10).value_or( - HAL_kMaxJoystickPOVs); - if (index >= HAL_kMaxJoystickPOVs) { - return; - } - int v = wpi::parse_integer(value, 10).value_or(-1); - if (v < 0 || v >= IM_ARRAYSIZE(ImGuiIO::KeysDown)) { - return; - } - if (name == "key0") { - m_povConfig[index].key0 = v; - } else if (name == "key45") { - m_povConfig[index].key45 = v; - } else if (name == "key90") { - m_povConfig[index].key90 = v; - } else if (name == "key135") { - m_povConfig[index].key135 = v; - } else if (name == "key180") { - m_povConfig[index].key180 = v; - } else if (name == "key225") { - m_povConfig[index].key225 = v; - } else if (name == "key270") { - m_povConfig[index].key270 = v; - } else if (name == "key315") { - m_povConfig[index].key315 = v; - } - } -} - -void KeyboardJoystick::WriteIni(ImGuiTextBuffer* out_buf) const { - out_buf->appendf("axisCount=%d\nbuttonCount=%d\npovCount=%d\n", - m_data.axes.count, m_data.buttons.count, m_data.povs.count); - for (int i = 0; i < m_data.axes.count; ++i) { - auto& c = m_axisConfig[i]; - out_buf->appendf( - "axis%dincKey=%d\naxis%ddecKey=%d\naxis%dkeyRate=%f\n" - "axis%ddecayRate=%f\naxis%dmaxAbsValue=%f\n", - i, c.incKey, i, c.decKey, i, c.keyRate, i, c.decayRate, i, - c.maxAbsValue); - } - for (int i = 0; i < m_data.buttons.count; ++i) { - out_buf->appendf("button%d=%d\n", i, m_buttonKey[i]); - } - for (int i = 0; i < m_data.povs.count; ++i) { - auto& c = m_povConfig[i]; - out_buf->appendf( - "pov%dkey0=%d\npov%dkey45=%d\npov%dkey90=%d\npov%dkey135=%d\n" - "pov%dkey180=%d\npov%dkey225=%d\npov%dkey270=%d\npov%dkey315=%d\n", - i, c.key0, i, c.key45, i, c.key90, i, c.key135, i, c.key180, i, - c.key225, i, c.key270, i, c.key315); - } -} - -GlfwKeyboardJoystick::GlfwKeyboardJoystick(int index, bool noDefaults) - : KeyboardJoystick{index} { - if (noDefaults) { - return; - } +GlfwKeyboardJoystick::GlfwKeyboardJoystick(glass::Storage& storage, int index) + : KeyboardJoystick{storage, index} { // set up a default keyboard config for 0, 1, and 2 if (index == 0) { - m_data.axes.count = 3; - m_axisConfig[0].incKey = GLFW_KEY_D; - m_axisConfig[0].decKey = GLFW_KEY_A; - m_axisConfig[1].incKey = GLFW_KEY_S; - m_axisConfig[1].decKey = GLFW_KEY_W; - m_axisConfig[2].incKey = GLFW_KEY_R; - m_axisConfig[2].decKey = GLFW_KEY_E; - m_axisConfig[2].keyRate = 0.01f; - m_axisConfig[2].decayRate = 0; // works like a throttle - m_data.buttons.count = 4; - m_buttonKey[0] = GLFW_KEY_Z; - m_buttonKey[1] = GLFW_KEY_X; - m_buttonKey[2] = GLFW_KEY_C; - m_buttonKey[3] = GLFW_KEY_V; - m_data.povs.count = 1; - m_povConfig[0].key0 = GLFW_KEY_KP_8; - m_povConfig[0].key45 = GLFW_KEY_KP_9; - m_povConfig[0].key90 = GLFW_KEY_KP_6; - m_povConfig[0].key135 = GLFW_KEY_KP_3; - m_povConfig[0].key180 = GLFW_KEY_KP_2; - m_povConfig[0].key225 = GLFW_KEY_KP_1; - m_povConfig[0].key270 = GLFW_KEY_KP_4; - m_povConfig[0].key315 = GLFW_KEY_KP_7; + if (m_axisCount == -1 && m_axisStorage.empty()) { + m_axisCount = 3; + for (int i = 0; i < 3; ++i) { + m_axisStorage.emplace_back(std::make_unique()); + m_axisConfig.emplace_back(*m_axisStorage.back()); + } + m_axisConfig[0].incKey = GLFW_KEY_D; + m_axisConfig[0].decKey = GLFW_KEY_A; + m_axisConfig[1].incKey = GLFW_KEY_S; + m_axisConfig[1].decKey = GLFW_KEY_W; + m_axisConfig[2].incKey = GLFW_KEY_R; + m_axisConfig[2].decKey = GLFW_KEY_E; + m_axisConfig[2].keyRate = 0.01f; + m_axisConfig[2].decayRate = 0; // works like a throttle + } + if (m_buttonCount == -1 && m_buttonKey.empty()) { + m_buttonCount = 4; + m_buttonKey.resize(4); + m_buttonKey[0] = GLFW_KEY_Z; + m_buttonKey[1] = GLFW_KEY_X; + m_buttonKey[2] = GLFW_KEY_C; + m_buttonKey[3] = GLFW_KEY_V; + } + if (m_povCount == -1 && m_povStorage.empty()) { + m_povCount = 1; + m_povStorage.emplace_back(std::make_unique()); + m_povConfig.emplace_back(*m_povStorage.back()); + m_povConfig[0].key0 = GLFW_KEY_KP_8; + m_povConfig[0].key45 = GLFW_KEY_KP_9; + m_povConfig[0].key90 = GLFW_KEY_KP_6; + m_povConfig[0].key135 = GLFW_KEY_KP_3; + m_povConfig[0].key180 = GLFW_KEY_KP_2; + m_povConfig[0].key225 = GLFW_KEY_KP_1; + m_povConfig[0].key270 = GLFW_KEY_KP_4; + m_povConfig[0].key315 = GLFW_KEY_KP_7; + } } else if (index == 1) { - m_data.axes.count = 2; - m_axisConfig[0].incKey = GLFW_KEY_L; - m_axisConfig[0].decKey = GLFW_KEY_J; - m_axisConfig[1].incKey = GLFW_KEY_K; - m_axisConfig[1].decKey = GLFW_KEY_I; - m_data.buttons.count = 4; - m_buttonKey[0] = GLFW_KEY_M; - m_buttonKey[1] = GLFW_KEY_COMMA; - m_buttonKey[2] = GLFW_KEY_PERIOD; - m_buttonKey[3] = GLFW_KEY_SLASH; + if (m_axisCount == -1 && m_axisStorage.empty()) { + m_axisCount = 2; + for (int i = 0; i < 2; ++i) { + m_axisStorage.emplace_back(std::make_unique()); + m_axisConfig.emplace_back(*m_axisStorage.back()); + } + m_axisConfig[0].incKey = GLFW_KEY_L; + m_axisConfig[0].decKey = GLFW_KEY_J; + m_axisConfig[1].incKey = GLFW_KEY_K; + m_axisConfig[1].decKey = GLFW_KEY_I; + } + if (m_buttonCount == -1 && m_buttonKey.empty()) { + m_buttonCount = 4; + m_buttonKey.resize(4); + m_buttonKey[0] = GLFW_KEY_M; + m_buttonKey[1] = GLFW_KEY_COMMA; + m_buttonKey[2] = GLFW_KEY_PERIOD; + m_buttonKey[3] = GLFW_KEY_SLASH; + } } else if (index == 2) { - m_data.axes.count = 2; - m_axisConfig[0].incKey = GLFW_KEY_RIGHT; - m_axisConfig[0].decKey = GLFW_KEY_LEFT; - m_axisConfig[1].incKey = GLFW_KEY_DOWN; - m_axisConfig[1].decKey = GLFW_KEY_UP; - m_data.buttons.count = 6; - m_buttonKey[0] = GLFW_KEY_INSERT; - m_buttonKey[1] = GLFW_KEY_HOME; - m_buttonKey[2] = GLFW_KEY_PAGE_UP; - m_buttonKey[3] = GLFW_KEY_DELETE; - m_buttonKey[4] = GLFW_KEY_END; - m_buttonKey[5] = GLFW_KEY_PAGE_DOWN; + if (m_axisCount == -1 && m_axisStorage.empty()) { + m_axisCount = 2; + for (int i = 0; i < 2; ++i) { + m_axisStorage.emplace_back(std::make_unique()); + m_axisConfig.emplace_back(*m_axisStorage.back()); + } + m_axisConfig[0].incKey = GLFW_KEY_RIGHT; + m_axisConfig[0].decKey = GLFW_KEY_LEFT; + m_axisConfig[1].incKey = GLFW_KEY_DOWN; + m_axisConfig[1].decKey = GLFW_KEY_UP; + } + if (m_buttonCount == -1 && m_buttonKey.empty()) { + m_buttonCount = 6; + m_buttonKey.resize(6); + m_buttonKey[0] = GLFW_KEY_INSERT; + m_buttonKey[1] = GLFW_KEY_HOME; + m_buttonKey[2] = GLFW_KEY_PAGE_UP; + m_buttonKey[3] = GLFW_KEY_DELETE; + m_buttonKey[4] = GLFW_KEY_END; + m_buttonKey[5] = GLFW_KEY_PAGE_DOWN; + } } } @@ -1103,6 +982,11 @@ const char* GlfwKeyboardJoystick::GetKeyName(int key) const { return "(Unknown)"; } +RobotJoystick::RobotJoystick(glass::Storage& storage) + : name{storage.GetString("name")}, + guid{storage.GetString("guid")}, + useGamepad{storage.GetBool("useGamepad")} {} + void RobotJoystick::Update() { Clear(); if (sys) { @@ -1111,7 +995,9 @@ void RobotJoystick::Update() { } void RobotJoystick::SetHAL(int i) { - if (!gZeroDisconnectedJoysticks && (!sys || !sys->IsPresent())) { + if ((gpZeroDisconnectedJoysticks != nullptr && + !gpZeroDisconnectedJoysticks) && + (!sys || !sys->IsPresent())) { return; } // set at HAL level @@ -1149,11 +1035,11 @@ static void DriverStationExecute() { bool disableDS = IsDSDisabled(); if (disableDS && !prevDisableDS) { - if (auto win = HALSimGui::manager.GetWindow("System Joysticks")) { + if (auto win = HALSimGui::manager->GetWindow("System Joysticks")) { win->SetVisibility(glass::Window::kDisabled); } } else if (!disableDS && prevDisableDS) { - if (auto win = HALSimGui::manager.GetWindow("System Joysticks")) { + if (auto win = HALSimGui::manager->GetWindow("System Joysticks")) { win->SetVisibility(glass::Window::kShow); } } @@ -1190,7 +1076,7 @@ static void DriverStationExecute() { // DS hotkeys bool enableHotkey = false; bool disableHotkey = false; - if (gUseEnableDisableHotkeys) { + if (gpUseEnableDisableHotkeys != nullptr && *gpUseEnableDisableHotkeys) { ImGuiIO& io = ImGui::GetIO(); if (io.KeysDown[GLFW_KEY_ENTER] || io.KeysDown[GLFW_KEY_KP_ENTER]) { disableHotkey = true; @@ -1200,7 +1086,7 @@ static void DriverStationExecute() { enableHotkey = true; } } - if (gUseEstopHotkey) { + if (gpUseEstopHotkey != nullptr && *gpUseEstopHotkey) { ImGuiIO& io = ImGui::GetIO(); if (io.KeysDown[GLFW_KEY_SPACE]) { HALSIM_SetDriverStationEnabled(false); @@ -1232,7 +1118,8 @@ static void DriverStationExecute() { } // Update HAL - for (int i = 0; i < HAL_kMaxJoysticks; ++i) { + for (int i = 0, end = gRobotJoysticks.size(); + i < end && i < HAL_kMaxJoysticks; ++i) { gRobotJoysticks[i].SetHAL(i); } @@ -1329,7 +1216,7 @@ static void DisplaySystemJoysticks() { char buf[64]; std::snprintf(buf, sizeof(buf), "%s Settings", joy->GetName()); if (ImGui::MenuItem(buf)) { - if (auto win = DriverStationGui::dsManager.GetWindow(buf)) { + if (auto win = DriverStationGui::dsManager->GetWindow(buf)) { win->SetVisible(true); } ImGui::CloseCurrentPopup(); @@ -1370,10 +1257,11 @@ static void DisplayJoysticks() { for (auto&& joy2 : gRobotJoysticks) { if (joy2.sys == payload_sys) { joy2.sys = nullptr; + joy2.guid.clear(); } } joy.sys = payload_sys; - joy.guid.clear(); + joy.guid = payload_sys->GetGUID(); std::string_view name{payload_sys->GetName()}; joy.useGamepad = wpi::starts_with(name, "Xbox") || wpi::contains(name, "pad"); @@ -1453,15 +1341,23 @@ static void DisplayJoysticks() { } void DSManager::DisplayMenu() { - if (gDSSocketConnected && *gDSSocketConnected) { + if (gpDSSocketConnected && *gpDSSocketConnected) { ImGui::MenuItem("Turn off DS (real DS connected)", nullptr, true, false); } else { - ImGui::MenuItem("Turn off DS", nullptr, &gDisableDS); - ImGui::MenuItem("Zero disconnected joysticks", nullptr, - &gZeroDisconnectedJoysticks); - ImGui::MenuItem("Enable on []\\ combo, Disable on Enter", nullptr, - &gUseEnableDisableHotkeys); - ImGui::MenuItem("Disable on Spacebar", nullptr, &gUseEstopHotkey); + if (gpDisableDS != nullptr) { + ImGui::MenuItem("Turn off DS", nullptr, gpDisableDS); + } + if (gpZeroDisconnectedJoysticks != nullptr) { + ImGui::MenuItem("Zero disconnected joysticks", nullptr, + gpZeroDisconnectedJoysticks); + } + if (gpUseEnableDisableHotkeys != nullptr) { + ImGui::MenuItem("Enable on []\\ combo, Disable on Enter", nullptr, + gpUseEnableDisableHotkeys); + } + if (gpUseEstopHotkey != nullptr) { + ImGui::MenuItem("Disable on Spacebar", nullptr, gpUseEstopHotkey); + } } ImGui::Separator(); @@ -1470,85 +1366,94 @@ void DSManager::DisplayMenu() { } } -static void DriverStationInitialize() { - // hook ini handler to save joystick settings - ImGuiSettingsHandler iniHandler; - iniHandler.TypeName = "Joystick"; - iniHandler.TypeHash = ImHashStr(iniHandler.TypeName); - iniHandler.ReadOpenFn = JoystickReadOpen; - iniHandler.ReadLineFn = JoystickReadLine; - iniHandler.WriteAllFn = JoystickWriteAll; - ImGui::GetCurrentContext()->SettingsHandlers.push_back(iniHandler); - - // hook ini handler to save keyboard settings - iniHandler.TypeName = "KeyboardJoystick"; - iniHandler.TypeHash = ImHashStr(iniHandler.TypeName); - iniHandler.ReadOpenFn = KeyboardJoystickReadOpen; - iniHandler.ReadLineFn = KeyboardJoystickReadLine; - iniHandler.WriteAllFn = KeyboardJoystickWriteAll; - ImGui::GetCurrentContext()->SettingsHandlers.push_back(iniHandler); - - // hook ini handler to save DS settings - iniHandler.TypeName = "DriverStation"; - iniHandler.TypeHash = ImHashStr(iniHandler.TypeName); - iniHandler.ReadOpenFn = DriverStationReadOpen; - iniHandler.ReadLineFn = DriverStationReadLine; - iniHandler.WriteAllFn = DriverStationWriteAll; - ImGui::GetCurrentContext()->SettingsHandlers.push_back(iniHandler); -} - void DriverStationGui::GlobalInit() { + auto& storageRoot = glass::GetStorageRoot("ds"); + dsManager = std::make_unique(storageRoot); + // set up system joysticks (both GLFW and keyboard) for (int i = 0; i <= GLFW_JOYSTICK_LAST; ++i) { gGlfwJoysticks.emplace_back(std::make_unique(i)); } - for (int i = 0; i < 4; ++i) { - gKeyboardJoysticks.emplace_back(std::make_unique(i)); - } - dsManager.GlobalInit(); - - wpi::gui::AddInit(DriverStationInitialize); + dsManager->GlobalInit(); gFMSModel = std::make_unique(); wpi::gui::AddEarlyExecute(DriverStationExecute); wpi::gui::AddEarlyExecute([] { gFMSModel->Update(); }); - if (auto win = dsManager.AddWindow("FMS", [] { - DisplayFMS(gFMSModel.get(), &gFMSModel->m_matchTimeEnabled); - })) { - win->DisableRenamePopup(); - win->SetFlags(ImGuiWindowFlags_AlwaysAutoResize); - win->SetDefaultPos(5, 540); - } - if (auto win = - dsManager.AddWindow("System Joysticks", DisplaySystemJoysticks)) { - win->DisableRenamePopup(); - win->SetFlags(ImGuiWindowFlags_AlwaysAutoResize); - win->SetDefaultPos(5, 350); - } - if (auto win = dsManager.AddWindow("Joysticks", DisplayJoysticks)) { - win->DisableRenamePopup(); - win->SetFlags(ImGuiWindowFlags_AlwaysAutoResize); - win->SetDefaultPos(250, 465); - } - int i = 0; - for (auto&& joy : gKeyboardJoysticks) { - char label[64]; - std::snprintf(label, sizeof(label), "%s Settings", joy->GetName()); - if (auto win = dsManager.AddWindow( - label, [j = joy.get()] { j->SettingsDisplay(); })) { - win->SetVisible(false); - win->DisableRenamePopup(); - win->SetDefaultPos(10 + 310 * i++, 50); - if (i > 3) { - i = 0; + + storageRoot.SetCustomApply([&storageRoot] { + gpDisableDS = &storageRoot.GetBool("disable", false); + gpZeroDisconnectedJoysticks = + &storageRoot.GetBool("zeroDisconnectedJoysticks", true); + gpUseEnableDisableHotkeys = + &storageRoot.GetBool("useEnableDisableHotkeys", false); + gpUseEstopHotkey = &storageRoot.GetBool("useEstopHotkey", false); + + auto& keyboardStorage = storageRoot.GetChildArray("keyboardJoysticks"); + keyboardStorage.resize(4); + for (int i = 0; i < 4; ++i) { + if (!keyboardStorage[i]) { + keyboardStorage[i] = std::make_unique(); } - win->SetDefaultSize(300, 560); + gKeyboardJoysticks.emplace_back( + std::make_unique(*keyboardStorage[i], i)); } - } + + auto& robotJoystickStorage = storageRoot.GetChildArray("robotJoysticks"); + robotJoystickStorage.resize(HAL_kMaxJoysticks); + for (int i = 0; i < HAL_kMaxJoysticks; ++i) { + if (!robotJoystickStorage[i]) { + robotJoystickStorage[i] = std::make_unique(); + } + gRobotJoysticks.emplace_back(*robotJoystickStorage[i]); + } + + int i = 0; + for (auto&& joy : gKeyboardJoysticks) { + char label[64]; + std::snprintf(label, sizeof(label), "%s Settings", joy->GetName()); + if (auto win = dsManager->AddWindow( + label, [j = joy.get()] { j->SettingsDisplay(); }, + glass::Window::kHide)) { + win->DisableRenamePopup(); + win->SetDefaultPos(10 + 310 * i++, 50); + if (i > 3) { + i = 0; + } + win->SetDefaultSize(300, 560); + } + } + if (auto win = dsManager->AddWindow("FMS", [] { + DisplayFMS(gFMSModel.get(), &gFMSModel->m_matchTimeEnabled); + })) { + win->DisableRenamePopup(); + win->SetFlags(ImGuiWindowFlags_AlwaysAutoResize); + win->SetDefaultPos(5, 540); + } + if (auto win = + dsManager->AddWindow("System Joysticks", DisplaySystemJoysticks)) { + win->DisableRenamePopup(); + win->SetFlags(ImGuiWindowFlags_AlwaysAutoResize); + win->SetDefaultPos(5, 350); + } + if (auto win = dsManager->AddWindow("Joysticks", DisplayJoysticks)) { + win->DisableRenamePopup(); + win->SetFlags(ImGuiWindowFlags_AlwaysAutoResize); + win->SetDefaultPos(250, 465); + } + }); + + storageRoot.SetCustomClear([&storageRoot] { + dsManager->EraseWindows(); + gKeyboardJoysticks.clear(); + gRobotJoysticks.clear(); + storageRoot.GetChildArray("keyboardJoysticks").clear(); + storageRoot.GetChildArray("robotJoysticks").clear(); + storageRoot.ClearValues(); + }); } void DriverStationGui::SetDSSocketExtension(void* data) { - gDSSocketConnected = static_cast*>(data); + gpDSSocketConnected = static_cast*>(data); } diff --git a/simulation/halsim_gui/src/main/native/cpp/DriverStationGui.h b/simulation/halsim_gui/src/main/native/cpp/DriverStationGui.h index 51f7554620..cb086c839d 100644 --- a/simulation/halsim_gui/src/main/native/cpp/DriverStationGui.h +++ b/simulation/halsim_gui/src/main/native/cpp/DriverStationGui.h @@ -6,13 +6,14 @@ #include +#include #include namespace halsimgui { class DSManager : public glass::WindowManager { public: - explicit DSManager(std::string_view iniName) : WindowManager{iniName} {} + explicit DSManager(glass::Storage& storage) : WindowManager{storage} {} void DisplayMenu() override; }; @@ -22,7 +23,7 @@ class DriverStationGui { static void GlobalInit(); static void SetDSSocketExtension(void* data); - static DSManager dsManager; + static std::unique_ptr dsManager; }; } // namespace halsimgui diff --git a/simulation/halsim_gui/src/main/native/cpp/EncoderSimGui.cpp b/simulation/halsim_gui/src/main/native/cpp/EncoderSimGui.cpp index 56e8ea2334..6a1be270ce 100644 --- a/simulation/halsim_gui/src/main/native/cpp/EncoderSimGui.cpp +++ b/simulation/halsim_gui/src/main/native/cpp/EncoderSimGui.cpp @@ -246,7 +246,7 @@ static bool EncodersAnyInitialized() { } void EncoderSimGui::Initialize() { - HALSimGui::halProvider.Register( + HALSimGui::halProvider->Register( "Encoders", EncodersAnyInitialized, [] { return std::make_unique(); }, [](glass::Window* win, glass::Model* model) { @@ -258,7 +258,7 @@ void EncoderSimGui::Initialize() { } glass::EncodersModel& EncoderSimGui::GetEncodersModel() { - static auto model = HALSimGui::halProvider.GetModel("Encoders"); + static auto model = HALSimGui::halProvider->GetModel("Encoders"); assert(model); return *static_cast(model); } diff --git a/simulation/halsim_gui/src/main/native/cpp/HALProvider.cpp b/simulation/halsim_gui/src/main/native/cpp/HALProvider.cpp index 72b9c29db4..7385770422 100644 --- a/simulation/halsim_gui/src/main/native/cpp/HALProvider.cpp +++ b/simulation/halsim_gui/src/main/native/cpp/HALProvider.cpp @@ -5,6 +5,7 @@ #include "HALProvider.h" #include +#include #include #include @@ -15,6 +16,32 @@ using namespace halsimgui; static bool gDisableOutputsOnDSDisable = true; +HALProvider::HALProvider(glass::Storage& storage) : Provider{storage} { + storage.SetCustomApply([this] { + for (auto&& childIt : m_storage.GetChildren()) { + auto it = FindViewEntry(childIt.key()); + if (it != m_viewEntries.end() && (*it)->name == childIt.key()) { + Show(it->get(), nullptr); + } + } + for (auto&& entry : m_viewEntries) { + if (entry->showDefault) { + Show(entry.get(), entry->window); + if (entry->window) { + entry->window->SetDefaultVisibility(glass::Window::kShow); + } + } + } + }); + storage.SetCustomClear([this, &storage] { + for (auto&& entry : m_viewEntries) { + entry->window = nullptr; + } + m_windows.clear(); + storage.ClearValues(); + }); +} + bool HALProvider::AreOutputsDisabled() { return gDisableOutputsOnDSDisable && !HALSIM_GetDriverStationEnabled(); } @@ -28,34 +55,17 @@ void HALProvider::DisplayMenu() { bool visible = viewEntry->window && viewEntry->window->IsVisible(); bool wasVisible = visible; bool exists = viewEntry->modelEntry->exists(); - ImGui::MenuItem(viewEntry->name.c_str(), nullptr, &visible, - visible || exists); - if (!wasVisible && visible) { - Show(viewEntry.get(), viewEntry->window); - } else if (wasVisible && !visible && viewEntry->window) { - viewEntry->window->SetVisible(false); + if (ImGui::MenuItem(viewEntry->name.c_str(), nullptr, &visible, + visible || exists)) { + if (!wasVisible && visible) { + Show(viewEntry.get(), viewEntry->window); + } else if (wasVisible && !visible && viewEntry->window) { + viewEntry->window->SetVisible(false); + } } } } -void HALProvider::Update() { - Provider::Update(); - - // check for visible windows that need displays (typically this is due to - // file loading) - for (auto&& window : m_windows) { - if (!window->IsVisible() || window->HasView()) { - continue; - } - auto id = window->GetId(); - auto it = FindViewEntry(id); - if (it == m_viewEntries.end() || (*it)->name != id) { - continue; - } - Show(it->get(), window.get()); - } -} - glass::Model* HALProvider::GetModel(std::string_view name) { auto it = FindModelEntry(name); if (it == m_modelEntries.end() || (*it)->name != name) { @@ -87,7 +97,7 @@ void HALProvider::Show(ViewEntry* entry, glass::Window* window) { // the window might exist and we're just not associated to it yet if (!window) { - window = GetOrAddWindow(entry->name, true); + window = GetOrAddWindow(entry->name, true, glass::Window::kHide); } if (!window) { return; diff --git a/simulation/halsim_gui/src/main/native/cpp/HALSimGui.cpp b/simulation/halsim_gui/src/main/native/cpp/HALSimGui.cpp index 8d66ac7b7b..ac4ff4f0eb 100644 --- a/simulation/halsim_gui/src/main/native/cpp/HALSimGui.cpp +++ b/simulation/halsim_gui/src/main/native/cpp/HALSimGui.cpp @@ -4,22 +4,31 @@ #include "HALSimGui.h" +#include +#include + #include #include using namespace halsimgui; glass::MainMenuBar HALSimGui::mainMenu; -glass::WindowManager HALSimGui::manager{"SimWindow"}; -HALProvider HALSimGui::halProvider{"HALProvider"}; -glass::NetworkTablesProvider HALSimGui::ntProvider{"NTProvider"}; +std::unique_ptr HALSimGui::manager; +std::unique_ptr HALSimGui::halProvider; +std::unique_ptr HALSimGui::ntProvider; void HALSimGui::GlobalInit() { - manager.GlobalInit(); - halProvider.GlobalInit(); - ntProvider.GlobalInit(); + manager = std::make_unique( + glass::GetStorageRoot().GetChild("SimWindow")); + manager->GlobalInit(); + halProvider = std::make_unique( + glass::GetStorageRoot().GetChild("HALProvider")); + halProvider->GlobalInit(); + ntProvider = std::make_unique( + glass::GetStorageRoot().GetChild("NTProvider")); + ntProvider->GlobalInit(); wpi::gui::AddLateExecute([] { mainMenu.Display(); }); - glass::AddStandardNetworkTablesViews(ntProvider); + glass::AddStandardNetworkTablesViews(*ntProvider); } diff --git a/simulation/halsim_gui/src/main/native/cpp/NetworkTablesSimGui.cpp b/simulation/halsim_gui/src/main/native/cpp/NetworkTablesSimGui.cpp index c17a4f8239..325673ac4e 100644 --- a/simulation/halsim_gui/src/main/native/cpp/NetworkTablesSimGui.cpp +++ b/simulation/halsim_gui/src/main/native/cpp/NetworkTablesSimGui.cpp @@ -4,6 +4,8 @@ #include "NetworkTablesSimGui.h" +#include +#include #include #include @@ -13,21 +15,24 @@ using namespace halsimgui; static std::unique_ptr gNetworkTablesModel; -static std::unique_ptr gNetworkTablesView; -static glass::Window* gNetworkTablesWindow; +static std::unique_ptr gNetworkTablesWindow; void NetworkTablesSimGui::Initialize() { gNetworkTablesModel = std::make_unique(); - gNetworkTablesView = - std::make_unique(gNetworkTablesModel.get()); wpi::gui::AddEarlyExecute([] { gNetworkTablesModel->Update(); }); - gNetworkTablesWindow = HALSimGui::ntProvider.AddWindow( - "NetworkTables", [] { gNetworkTablesView->Display(); }); - if (gNetworkTablesWindow) { - gNetworkTablesWindow->SetDefaultPos(250, 277); - gNetworkTablesWindow->SetDefaultSize(750, 185); - gNetworkTablesWindow->DisableRenamePopup(); - } + gNetworkTablesWindow = std::make_unique( + glass::GetStorageRoot().GetChild("NetworkTables View"), "NetworkTables"); + gNetworkTablesWindow->SetView( + std::make_unique(gNetworkTablesModel.get())); + gNetworkTablesWindow->SetDefaultPos(250, 277); + gNetworkTablesWindow->SetDefaultSize(750, 185); + gNetworkTablesWindow->DisableRenamePopup(); + wpi::gui::AddLateExecute([] { gNetworkTablesWindow->Display(); }); + + wpi::gui::AddWindowScaler([](float scale) { + // scale default window positions + gNetworkTablesWindow->ScaleDefault(scale); + }); } void NetworkTablesSimGui::DisplayMenu() { diff --git a/simulation/halsim_gui/src/main/native/cpp/PCMSimGui.cpp b/simulation/halsim_gui/src/main/native/cpp/PCMSimGui.cpp index bcc510a41a..869c5b102c 100644 --- a/simulation/halsim_gui/src/main/native/cpp/PCMSimGui.cpp +++ b/simulation/halsim_gui/src/main/native/cpp/PCMSimGui.cpp @@ -197,10 +197,10 @@ static bool PCMsAnyInitialized() { } void PCMSimGui::Initialize() { - HALSimGui::halProvider.RegisterModel("CTREPCMs", PCMsAnyInitialized, [] { + HALSimGui::halProvider->RegisterModel("CTREPCMs", PCMsAnyInitialized, [] { return std::make_unique(); }); - HALSimGui::halProvider.RegisterView( + HALSimGui::halProvider->RegisterView( "Solenoids", "CTREPCMs", [](glass::Model* model) { bool any = false; @@ -218,14 +218,14 @@ void PCMSimGui::Initialize() { return glass::MakeFunctionView([=] { glass::DisplayPCMsSolenoids( static_cast(model), - HALSimGui::halProvider.AreOutputsEnabled()); + HALSimGui::halProvider->AreOutputsEnabled()); }); }); SimDeviceGui::GetDeviceTree().Add( - HALSimGui::halProvider.GetModel("CTREPCMs"), [](glass::Model* model) { + HALSimGui::halProvider->GetModel("CTREPCMs"), [](glass::Model* model) { glass::DisplayCompressorsDevice( static_cast(model), - HALSimGui::halProvider.AreOutputsEnabled()); + HALSimGui::halProvider->AreOutputsEnabled()); }); } diff --git a/simulation/halsim_gui/src/main/native/cpp/PWMSimGui.cpp b/simulation/halsim_gui/src/main/native/cpp/PWMSimGui.cpp index 3cb5e1a4ee..9eceabdc1c 100644 --- a/simulation/halsim_gui/src/main/native/cpp/PWMSimGui.cpp +++ b/simulation/halsim_gui/src/main/native/cpp/PWMSimGui.cpp @@ -105,7 +105,7 @@ static bool PWMsAnyInitialized() { } void PWMSimGui::Initialize() { - HALSimGui::halProvider.Register( + HALSimGui::halProvider->Register( "PWM Outputs", PWMsAnyInitialized, [] { return std::make_unique(); }, [](glass::Window* win, glass::Model* model) { @@ -113,7 +113,7 @@ void PWMSimGui::Initialize() { win->SetDefaultPos(910, 20); return glass::MakeFunctionView([=] { glass::DisplayPWMs(static_cast(model), - HALSimGui::halProvider.AreOutputsEnabled()); + HALSimGui::halProvider->AreOutputsEnabled()); }); }); } diff --git a/simulation/halsim_gui/src/main/native/cpp/PowerDistributionSimGui.cpp b/simulation/halsim_gui/src/main/native/cpp/PowerDistributionSimGui.cpp index c511345cf3..c136c7edde 100644 --- a/simulation/halsim_gui/src/main/native/cpp/PowerDistributionSimGui.cpp +++ b/simulation/halsim_gui/src/main/native/cpp/PowerDistributionSimGui.cpp @@ -124,7 +124,7 @@ static bool PowerDistributionsAnyInitialized() { } void PowerDistributionSimGui::Initialize() { - HALSimGui::halProvider.Register( + HALSimGui::halProvider->Register( "PowerDistributions", PowerDistributionsAnyInitialized, [] { return std::make_unique(); }, [](glass::Window* win, glass::Model* model) { diff --git a/simulation/halsim_gui/src/main/native/cpp/RelaySimGui.cpp b/simulation/halsim_gui/src/main/native/cpp/RelaySimGui.cpp index 3815617588..badb4f9a32 100644 --- a/simulation/halsim_gui/src/main/native/cpp/RelaySimGui.cpp +++ b/simulation/halsim_gui/src/main/native/cpp/RelaySimGui.cpp @@ -104,7 +104,7 @@ static bool RelayAnyInitialized() { } void RelaySimGui::Initialize() { - HALSimGui::halProvider.Register( + HALSimGui::halProvider->Register( "Relays", RelayAnyInitialized, [] { return std::make_unique(); }, [](glass::Window* win, glass::Model* model) { @@ -112,7 +112,7 @@ void RelaySimGui::Initialize() { win->SetDefaultPos(180, 20); return glass::MakeFunctionView([=] { glass::DisplayRelays(static_cast(model), - HALSimGui::halProvider.AreOutputsEnabled()); + HALSimGui::halProvider->AreOutputsEnabled()); }); }); } diff --git a/simulation/halsim_gui/src/main/native/cpp/RoboRioSimGui.cpp b/simulation/halsim_gui/src/main/native/cpp/RoboRioSimGui.cpp index ba13a2accb..b693569571 100644 --- a/simulation/halsim_gui/src/main/native/cpp/RoboRioSimGui.cpp +++ b/simulation/halsim_gui/src/main/native/cpp/RoboRioSimGui.cpp @@ -132,7 +132,7 @@ class RoboRioSimModel : public glass::RoboRioModel { } // namespace void RoboRioSimGui::Initialize() { - HALSimGui::halProvider.Register( + HALSimGui::halProvider->Register( "RoboRIO", [] { return true; }, [] { return std::make_unique(); }, [](glass::Window* win, glass::Model* model) { diff --git a/simulation/halsim_gui/src/main/native/cpp/SimDeviceGui.cpp b/simulation/halsim_gui/src/main/native/cpp/SimDeviceGui.cpp index 62e289632f..55b83cef25 100644 --- a/simulation/halsim_gui/src/main/native/cpp/SimDeviceGui.cpp +++ b/simulation/halsim_gui/src/main/native/cpp/SimDeviceGui.cpp @@ -155,7 +155,7 @@ static void DisplaySimDevice(const char* name, void* data, } void SimDeviceGui::Initialize() { - HALSimGui::halProvider.Register( + HALSimGui::halProvider->Register( "Other Devices", [] { return true; }, [] { return std::make_unique(); }, [](glass::Window* win, glass::Model* model) { @@ -170,7 +170,7 @@ void SimDeviceGui::Initialize() { static_cast(model)->Display(); }); }); - HALSimGui::halProvider.ShowDefault("Other Devices"); + HALSimGui::halProvider->ShowDefault("Other Devices"); auto model = std::make_unique(); gSimDevicesModel = model.get(); @@ -185,7 +185,7 @@ glass::DataSource* SimDeviceGui::GetValueSource(HAL_SimValueHandle handle) { } glass::DeviceTreeModel& SimDeviceGui::GetDeviceTree() { - static auto model = HALSimGui::halProvider.GetModel("Other Devices"); + static auto model = HALSimGui::halProvider->GetModel("Other Devices"); assert(model); return *static_cast(model); } diff --git a/simulation/halsim_gui/src/main/native/cpp/TimingGui.cpp b/simulation/halsim_gui/src/main/native/cpp/TimingGui.cpp index 1928063d8a..3ccf6306d1 100644 --- a/simulation/halsim_gui/src/main/native/cpp/TimingGui.cpp +++ b/simulation/halsim_gui/src/main/native/cpp/TimingGui.cpp @@ -71,7 +71,7 @@ static void DisplayTiming() { } void TimingGui::Initialize() { - HALSimGui::halProvider.Register( + HALSimGui::halProvider->Register( "Timing", [] { return true; }, [] { return std::make_unique(); }, [](glass::Window* win, glass::Model* model) { @@ -80,5 +80,5 @@ void TimingGui::Initialize() { win->SetDefaultPos(5, 150); return glass::MakeFunctionView(DisplayTiming); }); - HALSimGui::halProvider.ShowDefault("Timing"); + HALSimGui::halProvider->ShowDefault("Timing"); } diff --git a/simulation/halsim_gui/src/main/native/cpp/main.cpp b/simulation/halsim_gui/src/main/native/cpp/main.cpp index 6df6f35e4e..1f89929354 100644 --- a/simulation/halsim_gui/src/main/native/cpp/main.cpp +++ b/simulation/halsim_gui/src/main/native/cpp/main.cpp @@ -3,6 +3,7 @@ // the WPILib BSD license file in the root directory of this project. #include +#include #include #include @@ -35,7 +36,7 @@ using namespace halsimgui; namespace gui = wpi::gui; -static glass::PlotProvider gPlotProvider{"Plot"}; +static std::unique_ptr gPlotProvider; extern "C" { #if defined(WIN32) || defined(_WIN32) @@ -46,54 +47,59 @@ __declspec(dllexport) gui::CreateContext(); glass::CreateContext(); + + glass::SetStorageName("simgui"); + HALSimGui::GlobalInit(); DriverStationGui::GlobalInit(); - gPlotProvider.GlobalInit(); + gPlotProvider = std::make_unique( + glass::GetStorageRoot().GetChild("Plot")); + gPlotProvider->GlobalInit(); // These need to initialize first - gui::AddInit(EncoderSimGui::Initialize); - gui::AddInit(SimDeviceGui::Initialize); + EncoderSimGui::Initialize(); + SimDeviceGui::Initialize(); - gui::AddInit(AccelerometerSimGui::Initialize); - gui::AddInit(AddressableLEDGui::Initialize); - gui::AddInit(AnalogGyroSimGui::Initialize); - gui::AddInit(AnalogInputSimGui::Initialize); - gui::AddInit(AnalogOutputSimGui::Initialize); - gui::AddInit(DIOSimGui::Initialize); - gui::AddInit(NetworkTablesSimGui::Initialize); - gui::AddInit(PCMSimGui::Initialize); - gui::AddInit(PowerDistributionSimGui::Initialize); - gui::AddInit(PWMSimGui::Initialize); - gui::AddInit(RelaySimGui::Initialize); - gui::AddInit(RoboRioSimGui::Initialize); - gui::AddInit(TimingGui::Initialize); + AccelerometerSimGui::Initialize(); + AddressableLEDGui::Initialize(); + AnalogGyroSimGui::Initialize(); + AnalogInputSimGui::Initialize(); + AnalogOutputSimGui::Initialize(); + DIOSimGui::Initialize(); + NetworkTablesSimGui::Initialize(); + PCMSimGui::Initialize(); + PowerDistributionSimGui::Initialize(); + PWMSimGui::Initialize(); + RelaySimGui::Initialize(); + RoboRioSimGui::Initialize(); + TimingGui::Initialize(); HALSimGui::mainMenu.AddMainMenu([] { if (ImGui::BeginMenu("Hardware")) { - HALSimGui::halProvider.DisplayMenu(); + HALSimGui::halProvider->DisplayMenu(); ImGui::EndMenu(); } if (ImGui::BeginMenu("NetworkTables")) { NetworkTablesSimGui::DisplayMenu(); ImGui::Separator(); - HALSimGui::ntProvider.DisplayMenu(); + HALSimGui::ntProvider->DisplayMenu(); ImGui::EndMenu(); } if (ImGui::BeginMenu("DS")) { - DriverStationGui::dsManager.DisplayMenu(); + DriverStationGui::dsManager->DisplayMenu(); ImGui::EndMenu(); } if (ImGui::BeginMenu("Plot")) { - bool paused = gPlotProvider.IsPaused(); + bool paused = gPlotProvider->IsPaused(); if (ImGui::MenuItem("Pause All Plots", nullptr, &paused)) { - gPlotProvider.SetPaused(paused); + gPlotProvider->SetPaused(paused); } ImGui::Separator(); - gPlotProvider.DisplayMenu(); + gPlotProvider->DisplayMenu(); ImGui::EndMenu(); } if (ImGui::BeginMenu("Window")) { - HALSimGui::manager.DisplayMenu(); + HALSimGui::manager->DisplayMenu(); ImGui::EndMenu(); } }); diff --git a/simulation/halsim_gui/src/main/native/include/HALProvider.h b/simulation/halsim_gui/src/main/native/include/HALProvider.h index e3098db416..0a26a09cd5 100644 --- a/simulation/halsim_gui/src/main/native/include/HALProvider.h +++ b/simulation/halsim_gui/src/main/native/include/HALProvider.h @@ -15,9 +15,15 @@ namespace halsimgui { -class HALProvider : public glass::Provider<> { +class HALProvider : private glass::Provider<> { public: - explicit HALProvider(std::string_view iniName) : Provider{iniName} {} + explicit HALProvider(glass::Storage& storage); + + using Provider::GlobalInit; + using Provider::Register; + using Provider::RegisterModel; + using Provider::RegisterView; + using Provider::ShowDefault; void DisplayMenu() override; @@ -38,8 +44,6 @@ class HALProvider : public glass::Provider<> { static bool AreOutputsEnabled() { return !AreOutputsDisabled(); } private: - void Update() override; - void Show(ViewEntry* entry, glass::Window* window) override; }; diff --git a/simulation/halsim_gui/src/main/native/include/HALSimGui.h b/simulation/halsim_gui/src/main/native/include/HALSimGui.h index 33ff242829..9be56eb49b 100644 --- a/simulation/halsim_gui/src/main/native/include/HALSimGui.h +++ b/simulation/halsim_gui/src/main/native/include/HALSimGui.h @@ -8,6 +8,8 @@ #include #include +#include + #include "HALProvider.h" namespace halsimgui { @@ -17,10 +19,10 @@ class HALSimGui { static void GlobalInit(); static glass::MainMenuBar mainMenu; - static glass::WindowManager manager; + static std::unique_ptr manager; - static HALProvider halProvider; - static glass::NetworkTablesProvider ntProvider; + static std::unique_ptr halProvider; + static std::unique_ptr ntProvider; }; } // namespace halsimgui