mirror of
https://github.com/wpilibsuite/allwpilib
synced 2026-06-19 00:41:43 +00:00
[glass] Add glass: an application for display of robot data
This reuses many pieces of the current simulation GUI. The common pieces have been refactored into the libglass library. The libglass library is designed to be usable for other standalone data visualization applications (e.g. viewing data logs). The name "glass" comes from "glass cockpit", as the application features several multi-function displays that can be adjusted to display robot information as needed.
This commit is contained in:
@@ -7,44 +7,24 @@
|
||||
|
||||
#include "SimDeviceGui.h"
|
||||
|
||||
#include <glass/other/DeviceTree.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include <hal/SimDevice.h>
|
||||
#include <hal/simulation/SimDeviceData.h>
|
||||
#include <imgui.h>
|
||||
#include <wpi/DenseMap.h>
|
||||
|
||||
#include "GuiDataSource.h"
|
||||
#include "HALDataSource.h"
|
||||
#include "HALSimGui.h"
|
||||
#include "IniSaverInfo.h"
|
||||
#include "IniSaverString.h"
|
||||
|
||||
using namespace halsimgui;
|
||||
|
||||
namespace {
|
||||
|
||||
struct ElementInfo : public NameInfo, public OpenInfo {
|
||||
bool ReadIni(wpi::StringRef name, wpi::StringRef value) {
|
||||
if (NameInfo::ReadIni(name, value)) return true;
|
||||
if (OpenInfo::ReadIni(name, value)) return true;
|
||||
return false;
|
||||
}
|
||||
void WriteIni(ImGuiTextBuffer* out) {
|
||||
NameInfo::WriteIni(out);
|
||||
OpenInfo::WriteIni(out);
|
||||
}
|
||||
bool visible = true; // not saved
|
||||
};
|
||||
|
||||
class SimValueSource : public GuiDataSource {
|
||||
class SimValueSource : public glass::DataSource {
|
||||
public:
|
||||
explicit SimValueSource(HAL_SimValueHandle handle, const char* device,
|
||||
const char* name)
|
||||
: GuiDataSource(wpi::Twine{device} + wpi::Twine{'-'} + name),
|
||||
: DataSource(wpi::Twine{device} + wpi::Twine{'-'} + name),
|
||||
m_callback{HALSIM_RegisterSimValueChangedCallback(
|
||||
handle, this, CallbackFunc, true)} {}
|
||||
~SimValueSource() {
|
||||
@@ -67,226 +47,124 @@ class SimValueSource : public GuiDataSource {
|
||||
int32_t m_callback;
|
||||
};
|
||||
|
||||
class SimDevicesModel : public glass::Model {
|
||||
public:
|
||||
void Update() override;
|
||||
bool Exists() override { return true; }
|
||||
|
||||
glass::DataSource* GetSource(HAL_SimValueHandle handle) {
|
||||
return m_sources[handle].get();
|
||||
}
|
||||
|
||||
private:
|
||||
wpi::DenseMap<HAL_SimValueHandle, std::unique_ptr<SimValueSource>> m_sources;
|
||||
};
|
||||
} // namespace
|
||||
|
||||
static std::vector<std::function<void()>> gDeviceExecutors;
|
||||
static IniSaverString<ElementInfo> gElements{"Device"};
|
||||
static wpi::DenseMap<HAL_SimValueHandle, std::unique_ptr<SimValueSource>>
|
||||
gSimValueSources;
|
||||
static SimDevicesModel* gSimDevicesModel;
|
||||
|
||||
static void UpdateSimValueSources() {
|
||||
void SimDevicesModel::Update() {
|
||||
HALSIM_EnumerateSimDevices(
|
||||
"", nullptr, [](const char* name, void*, HAL_SimDeviceHandle handle) {
|
||||
"", this, [](const char* name, void* self, HAL_SimDeviceHandle handle) {
|
||||
struct Data {
|
||||
SimDevicesModel* self;
|
||||
const char* device;
|
||||
} data = {static_cast<SimDevicesModel*>(self), name};
|
||||
HALSIM_EnumerateSimValues(
|
||||
handle, const_cast<char*>(name),
|
||||
[](const char* name, void* deviceV, HAL_SimValueHandle handle,
|
||||
handle, &data,
|
||||
[](const char* name, void* dataV, HAL_SimValueHandle handle,
|
||||
HAL_Bool readonly, const HAL_Value* value) {
|
||||
auto device = static_cast<const char*>(deviceV);
|
||||
auto& source = gSimValueSources[handle];
|
||||
auto data = static_cast<Data*>(dataV);
|
||||
auto& source = data->self->m_sources[handle];
|
||||
if (!source) {
|
||||
source = std::make_unique<SimValueSource>(handle, device, name);
|
||||
source = std::make_unique<SimValueSource>(handle, data->device,
|
||||
name);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void SimDeviceGui::Hide(const char* name) { gElements[name].visible = false; }
|
||||
static void DisplaySimValue(const char* name, void* data,
|
||||
HAL_SimValueHandle handle, HAL_Bool readonly,
|
||||
const HAL_Value* value) {
|
||||
auto model = static_cast<SimDevicesModel*>(data);
|
||||
|
||||
void SimDeviceGui::Add(std::function<void()> execute) {
|
||||
if (execute) gDeviceExecutors.emplace_back(std::move(execute));
|
||||
}
|
||||
HAL_Value valueCopy = *value;
|
||||
|
||||
bool SimDeviceGui::StartDevice(const char* label, ImGuiTreeNodeFlags flags) {
|
||||
auto& element = gElements[label];
|
||||
if (!element.visible) return false;
|
||||
|
||||
char name[128];
|
||||
element.GetLabel(name, sizeof(name), label);
|
||||
|
||||
bool open = ImGui::CollapsingHeader(
|
||||
name, flags | (element.IsOpen() ? ImGuiTreeNodeFlags_DefaultOpen : 0));
|
||||
element.SetOpen(open);
|
||||
element.PopupEditName(label);
|
||||
|
||||
if (open) ImGui::PushID(label);
|
||||
return open;
|
||||
}
|
||||
|
||||
void SimDeviceGui::FinishDevice() { ImGui::PopID(); }
|
||||
|
||||
static bool DisplayValueImpl(const char* name, bool readonly, HAL_Value* value,
|
||||
const char** options, int32_t numOptions) {
|
||||
// read-only
|
||||
if (readonly) {
|
||||
switch (value->type) {
|
||||
case HAL_BOOLEAN:
|
||||
ImGui::LabelText(name, "%s", value->data.v_boolean ? "true" : "false");
|
||||
break;
|
||||
case HAL_DOUBLE:
|
||||
ImGui::LabelText(name, "%.6f", value->data.v_double);
|
||||
break;
|
||||
case HAL_ENUM: {
|
||||
int current = value->data.v_enum;
|
||||
if (current < 0 || current >= numOptions)
|
||||
ImGui::LabelText(name, "%d (unknown)", current);
|
||||
else
|
||||
ImGui::LabelText(name, "%s", options[current]);
|
||||
break;
|
||||
}
|
||||
case HAL_INT:
|
||||
ImGui::LabelText(name, "%d", static_cast<int>(value->data.v_int));
|
||||
break;
|
||||
case HAL_LONG:
|
||||
ImGui::LabelText(name, "%lld",
|
||||
static_cast<long long int>( // NOLINT(runtime/int)
|
||||
value->data.v_long));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// writable
|
||||
switch (value->type) {
|
||||
case HAL_BOOLEAN: {
|
||||
static const char* boolOptions[] = {"false", "true"};
|
||||
int val = value->data.v_boolean ? 1 : 0;
|
||||
if (ImGui::Combo(name, &val, boolOptions, 2)) {
|
||||
value->data.v_boolean = val;
|
||||
return true;
|
||||
bool v = value->data.v_boolean;
|
||||
if (glass::DeviceBoolean(name, readonly, &v, model->GetSource(handle))) {
|
||||
valueCopy.data.v_boolean = v ? 1 : 0;
|
||||
HAL_SetSimValue(handle, valueCopy);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case HAL_DOUBLE: {
|
||||
if (ImGui::InputDouble(name, &value->data.v_double, 0, 0, "%.6f",
|
||||
ImGuiInputTextFlags_EnterReturnsTrue))
|
||||
return true;
|
||||
case HAL_DOUBLE:
|
||||
if (glass::DeviceDouble(name, readonly, &valueCopy.data.v_double,
|
||||
model->GetSource(handle))) {
|
||||
HAL_SetSimValue(handle, valueCopy);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case HAL_ENUM: {
|
||||
int current = value->data.v_enum;
|
||||
if (ImGui::Combo(name, ¤t, options, numOptions)) {
|
||||
value->data.v_enum = current;
|
||||
return true;
|
||||
int32_t numOptions = 0;
|
||||
const char** options = HALSIM_GetSimValueEnumOptions(handle, &numOptions);
|
||||
if (glass::DeviceEnum(name, readonly, &valueCopy.data.v_enum, options,
|
||||
numOptions, model->GetSource(handle))) {
|
||||
HAL_SetSimValue(handle, valueCopy);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case HAL_INT: {
|
||||
if (ImGui::InputScalar(name, ImGuiDataType_S32, &value->data.v_int,
|
||||
nullptr, nullptr, nullptr,
|
||||
ImGuiInputTextFlags_EnterReturnsTrue))
|
||||
return true;
|
||||
case HAL_INT:
|
||||
if (glass::DeviceInt(name, readonly, &valueCopy.data.v_int,
|
||||
model->GetSource(handle))) {
|
||||
HAL_SetSimValue(handle, valueCopy);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case HAL_LONG: {
|
||||
if (ImGui::InputScalar(name, ImGuiDataType_S64, &value->data.v_long,
|
||||
nullptr, nullptr, nullptr,
|
||||
ImGuiInputTextFlags_EnterReturnsTrue))
|
||||
return true;
|
||||
case HAL_LONG:
|
||||
if (glass::DeviceLong(name, readonly, &valueCopy.data.v_long,
|
||||
model->GetSource(handle))) {
|
||||
HAL_SetSimValue(handle, valueCopy);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool DisplayValueSourceImpl(const char* name, bool readonly,
|
||||
HAL_Value* value,
|
||||
const GuiDataSource* source,
|
||||
const char** options, int32_t numOptions) {
|
||||
if (!source)
|
||||
return DisplayValueImpl(name, readonly, value, options, numOptions);
|
||||
ImGui::PushID(name);
|
||||
bool rv = DisplayValueImpl("", readonly, value, options, numOptions);
|
||||
ImGui::SameLine(0, ImGui::GetStyle().ItemInnerSpacing.x);
|
||||
ImGui::Selectable(name);
|
||||
source->EmitDrag();
|
||||
ImGui::PopID();
|
||||
return rv;
|
||||
}
|
||||
|
||||
bool SimDeviceGui::DisplayValue(const char* name, bool readonly,
|
||||
HAL_Value* value, const char** options,
|
||||
int32_t numOptions) {
|
||||
return DisplayValueSource(name, readonly, value, nullptr, options,
|
||||
numOptions);
|
||||
}
|
||||
|
||||
bool SimDeviceGui::DisplayValueSource(const char* name, bool readonly,
|
||||
HAL_Value* value,
|
||||
const GuiDataSource* source,
|
||||
const char** options,
|
||||
int32_t numOptions) {
|
||||
ImGui::SetNextItemWidth(ImGui::GetWindowWidth() * 0.5f);
|
||||
return DisplayValueSourceImpl(name, readonly, value, source, options,
|
||||
numOptions);
|
||||
}
|
||||
|
||||
static void SimDeviceDisplayValue(const char* name, void*,
|
||||
HAL_SimValueHandle handle, HAL_Bool readonly,
|
||||
const HAL_Value* value) {
|
||||
int32_t numOptions = 0;
|
||||
const char** options = nullptr;
|
||||
|
||||
if (value->type == HAL_ENUM)
|
||||
options = HALSIM_GetSimValueEnumOptions(handle, &numOptions);
|
||||
|
||||
HAL_Value valueCopy = *value;
|
||||
if (DisplayValueSourceImpl(name, readonly, &valueCopy,
|
||||
gSimValueSources[handle].get(), options,
|
||||
numOptions))
|
||||
HAL_SetSimValue(handle, valueCopy);
|
||||
}
|
||||
|
||||
static void SimDeviceDisplayDevice(const char* name, void*,
|
||||
HAL_SimDeviceHandle handle) {
|
||||
auto it = gElements.find(name);
|
||||
if (it != gElements.end() && !it->second.visible) return;
|
||||
|
||||
if (SimDeviceGui::StartDevice(name)) {
|
||||
ImGui::PushItemWidth(ImGui::GetWindowWidth() * 0.5f);
|
||||
HALSIM_EnumerateSimValues(handle, nullptr, SimDeviceDisplayValue);
|
||||
ImGui::PopItemWidth();
|
||||
SimDeviceGui::FinishDevice();
|
||||
static void DisplaySimDevice(const char* name, void* data,
|
||||
HAL_SimDeviceHandle handle) {
|
||||
if (glass::BeginDevice(name)) {
|
||||
HALSIM_EnumerateSimValues(handle, data, DisplaySimValue);
|
||||
glass::EndDevice();
|
||||
}
|
||||
}
|
||||
|
||||
static void DisplayDeviceTree() {
|
||||
for (auto&& execute : gDeviceExecutors) {
|
||||
if (execute) execute();
|
||||
}
|
||||
HALSIM_EnumerateSimDevices("", nullptr, SimDeviceDisplayDevice);
|
||||
}
|
||||
|
||||
void SimDeviceGui::Initialize() {
|
||||
gElements.Initialize();
|
||||
HALSimGui::AddExecute(UpdateSimValueSources);
|
||||
HALSimGui::AddWindow("Other Devices", DisplayDeviceTree);
|
||||
HALSimGui::SetDefaultWindowPos("Other Devices", 1025, 20);
|
||||
HALSimGui::SetDefaultWindowSize("Other Devices", 250, 695);
|
||||
HALSimGui::halProvider.Register(
|
||||
"Other Devices", [] { return true; },
|
||||
[] { return std::make_unique<glass::DeviceTreeModel>(); },
|
||||
[](glass::Window* win, glass::Model* model) {
|
||||
win->SetDefaultPos(1025, 20);
|
||||
win->SetDefaultSize(250, 695);
|
||||
return glass::MakeFunctionView(
|
||||
[=] { static_cast<glass::DeviceTreeModel*>(model)->Display(); });
|
||||
});
|
||||
|
||||
auto model = std::make_unique<SimDevicesModel>();
|
||||
gSimDevicesModel = model.get();
|
||||
GetDeviceTree().Add(std::move(model), [](glass::Model* model) {
|
||||
HALSIM_EnumerateSimDevices("", static_cast<SimDevicesModel*>(model),
|
||||
DisplaySimDevice);
|
||||
});
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
|
||||
void HALSIMGUI_DeviceTreeAdd(void* param, void (*execute)(void*)) {
|
||||
if (execute) SimDeviceGui::Add([=] { execute(param); });
|
||||
glass::DataSource* SimDeviceGui::GetValueSource(HAL_SimValueHandle handle) {
|
||||
return gSimDevicesModel->GetSource(handle);
|
||||
}
|
||||
|
||||
void HALSIMGUI_DeviceTreeHide(const char* name) { SimDeviceGui::Hide(name); }
|
||||
|
||||
HAL_Bool HALSIMGUI_DeviceTreeDisplayValue(const char* name, HAL_Bool readonly,
|
||||
struct HAL_Value* value,
|
||||
const char** options,
|
||||
int32_t numOptions) {
|
||||
return SimDeviceGui::DisplayValue(name, readonly, value, options, numOptions);
|
||||
glass::DeviceTreeModel& SimDeviceGui::GetDeviceTree() {
|
||||
static auto model = HALSimGui::halProvider.GetModel("Other Devices");
|
||||
assert(model);
|
||||
return *static_cast<glass::DeviceTreeModel*>(model);
|
||||
}
|
||||
|
||||
HAL_Bool HALSIMGUI_DeviceTreeStartDevice(const char* label, int32_t flags) {
|
||||
return SimDeviceGui::StartDevice(label, flags);
|
||||
}
|
||||
|
||||
void HALSIMGUI_DeviceTreeFinishDevice(void) { SimDeviceGui::FinishDevice(); }
|
||||
|
||||
} // extern "C"
|
||||
|
||||
Reference in New Issue
Block a user