diff --git a/glass/src/lib/native/cpp/other/DeviceTree.cpp b/glass/src/lib/native/cpp/other/DeviceTree.cpp index 47e80af54d0..996cb3a161e 100644 --- a/glass/src/lib/native/cpp/other/DeviceTree.cpp +++ b/glass/src/lib/native/cpp/other/DeviceTree.cpp @@ -6,6 +6,7 @@ #include #include +#include #include @@ -45,7 +46,16 @@ void wpi::glass::HideDevice(const char* id) { gContext->deviceHidden[id] = true; } +bool wpi::glass::IsDeviceHidden(std::string_view id) { + return gContext->deviceHidden[id]; +} + bool wpi::glass::BeginDevice(const char* id, ImGuiTreeNodeFlags flags) { + return BeginDevice(std::string_view{id}, std::string_view{id}, flags); +} + +bool wpi::glass::BeginDevice(std::string_view id, std::string_view labelText, + ImGuiTreeNodeFlags flags) { if (gContext->deviceHidden[id]) { return false; } @@ -56,7 +66,8 @@ bool wpi::glass::BeginDevice(const char* id, ImGuiTreeNodeFlags flags) { std::string& name = GetStorage().GetString("name"); char label[128]; if (name.empty()) { - wpi::util::format_to_n_c_str(label, sizeof(label), "{}###header", id); + wpi::util::format_to_n_c_str(label, sizeof(label), "{}###header", + labelText); } else { wpi::util::format_to_n_c_str(label, sizeof(label), "{}###header", name); } diff --git a/glass/src/lib/native/include/wpi/glass/other/DeviceTree.hpp b/glass/src/lib/native/include/wpi/glass/other/DeviceTree.hpp index dcc66487342..abf5e8b9e86 100644 --- a/glass/src/lib/native/include/wpi/glass/other/DeviceTree.hpp +++ b/glass/src/lib/native/include/wpi/glass/other/DeviceTree.hpp @@ -5,6 +5,7 @@ #pragma once #include +#include #include #include @@ -57,6 +58,14 @@ class DeviceTreeModel : public Model { */ void HideDevice(const char* id); +/** + * Returns true if a device is hidden on the tree. + * + * @param id device name + * @return True if hidden + */ +bool IsDeviceHidden(std::string_view id); + /** * Wraps CollapsingHeader() to provide both hiding functionality and open * persistence. As with the ImGui function, returns true if the tree node @@ -69,6 +78,20 @@ void HideDevice(const char* id); */ bool BeginDevice(const char* id, ImGuiTreeNodeFlags flags = 0); +/** + * Wraps CollapsingHeader() to provide both hiding functionality and open + * persistence while using a different visible label from the storage ID. As + * with the ImGui function, returns true if the tree node is visible and + * expanded. If returns true, call EndDevice() to finish the block. + * + * @param id storage and hide device name + * @param label visible label + * @param flags ImGuiTreeNodeFlags flags + * @return True if expanded + */ +bool BeginDevice(std::string_view id, std::string_view label, + ImGuiTreeNodeFlags flags = 0); + /** * Finish a device block started with BeginDevice(). */ diff --git a/simulation/halsim_gui/src/main/native/cpp/SimDeviceGui.cpp b/simulation/halsim_gui/src/main/native/cpp/SimDeviceGui.cpp index 987e1df96a5..5927ffcb07e 100644 --- a/simulation/halsim_gui/src/main/native/cpp/SimDeviceGui.cpp +++ b/simulation/halsim_gui/src/main/native/cpp/SimDeviceGui.cpp @@ -6,10 +6,16 @@ #include +#include #include #include +#include +#include #include +#include +#include "wpi/glass/Context.hpp" +#include "wpi/glass/Storage.hpp" #include "wpi/glass/other/DeviceTree.hpp" #include "wpi/hal/SimDevice.hpp" #include "wpi/hal/simulation/SimDeviceData.h" @@ -93,6 +99,21 @@ class SimDevicesModel : public wpi::glass::Model { std::unique_ptr> m_sources; }; + +struct SimDeviceTreeDevice { + std::string id; + HAL_SimDeviceHandle handle; +}; + +struct SimDeviceTreeNode { + SimDeviceTreeNode(std::string_view name, std::string_view id) + : name{name}, id{id} {} + + std::string name; + std::string id; + std::vector devices; + std::vector children; +}; } // namespace static SimDevicesModel* gSimDevicesModel; @@ -193,8 +214,7 @@ static void DisplaySimValue(const char* name, void* data, } } -static void DisplaySimDevice(const char* name, void* data, - HAL_SimDeviceHandle handle) { +static std::string GetDisplaySimDeviceId(const char* name) { std::string_view id{name}; if (!gSimDevicesShowPrefix) { // only show "Foo" portion of "Accel:Foo" @@ -204,12 +224,117 @@ static void DisplaySimDevice(const char* name, void* data, id = type; } } - if (wpi::glass::BeginDevice(id.data())) { - HALSIM_EnumerateSimValues(handle, data, DisplaySimValue); + + return std::string{id}; +} + +static SimDeviceTreeNode& GetOrAddChild(SimDeviceTreeNode& node, + std::string_view name) { + auto childIt = + std::find_if(node.children.begin(), node.children.end(), + [&](const auto& child) { return child.name == name; }); + if (childIt == node.children.end()) { + std::string id = node.id; + if (!id.empty()) { + id += '/'; + } + id += name; + node.children.emplace_back(name, id); + return node.children.back(); + } + return *childIt; +} + +static void AddSimDevice(SimDeviceTreeNode& root, std::string id, + HAL_SimDeviceHandle handle) { + if (wpi::glass::IsDeviceHidden(id)) { + return; + } + + SimDeviceTreeNode* node = &root; + wpi::util::split(id, '/', -1, false, [&](std::string_view segment) { + node = &GetOrAddChild(*node, segment); + }); + if (node == &root) { + node = &GetOrAddChild(root, id); + } + node->devices.push_back({std::move(id), handle}); +} + +static void SortSimDeviceTree(SimDeviceTreeNode& node) { + std::sort( + node.children.begin(), node.children.end(), + [](const auto& lhs, const auto& rhs) { return lhs.name < rhs.name; }); + + for (auto&& child : node.children) { + SortSimDeviceTree(child); + } +} + +static bool BeginSimDeviceTreeNode(const SimDeviceTreeNode& node) { + auto flags = ImGuiTreeNodeFlags_DefaultOpen; + bool& open = wpi::glass::GetStorage() + .GetChild("deviceTree") + .GetChild(node.id) + .GetBool("open", true); + ImGui::SetNextItemOpen(open); + + std::string label = std::format("{}###{}", node.name, node.id); + open = ImGui::CollapsingHeader(label.c_str(), flags); + if (open) { + ImGui::Indent(); + } + return open; +} + +static void EndSimDeviceTreeNode() { + ImGui::Unindent(); +} + +static void DisplaySimDevice(SimDevicesModel* model, + const SimDeviceTreeDevice& device, + std::string_view label) { + if (wpi::glass::BeginDevice(device.id, label)) { + HALSIM_EnumerateSimValues(device.handle, model, DisplaySimValue); wpi::glass::EndDevice(); } } +static void DisplaySimDeviceTree(SimDevicesModel* model, + const SimDeviceTreeNode& node) { + if (node.children.empty()) { + for (auto&& device : node.devices) { + DisplaySimDevice(model, device, node.name); + } + return; + } + + if (BeginSimDeviceTreeNode(node)) { + for (auto&& device : node.devices) { + DisplaySimDevice(model, device, node.name); + } + for (auto&& child : node.children) { + DisplaySimDeviceTree(model, child); + } + EndSimDeviceTreeNode(); + } +} + +static void DisplaySimDevices(SimDevicesModel* model) { + SimDeviceTreeNode root{"", ""}; + HALSIM_EnumerateSimDevices( + "", &root, [](const char* name, void* data, HAL_SimDeviceHandle handle) { + AddSimDevice(*static_cast(data), + GetDisplaySimDeviceId(name), handle); + }); + + SortSimDeviceTree(root); + + for (auto&& child : root.children) { + DisplaySimDeviceTree(model, child); + } +} + void SimDeviceGui::Initialize() { HALSimGui::halProvider->Register( "Other Devices", [] { return true; }, @@ -231,8 +356,7 @@ void SimDeviceGui::Initialize() { auto model = std::make_unique(); gSimDevicesModel = model.get(); GetDeviceTree().Add(std::move(model), [](wpi::glass::Model* model) { - HALSIM_EnumerateSimDevices("", static_cast(model), - DisplaySimDevice); + DisplaySimDevices(static_cast(model)); }); }