[sim] Add plotting to simulation GUI

This commit is contained in:
Peter Johnson
2020-08-14 20:02:35 -07:00
parent 70ba92f917
commit 1593eb4d47
29 changed files with 2550 additions and 301 deletions

View File

@@ -1,5 +1,5 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2017-2019 FIRST. All Rights Reserved. */
/* Copyright (c) 2017-2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
@@ -11,6 +11,8 @@
namespace halsimgui {
class GuiDataSource;
/**
* DrawLEDs() configuration for 2D arrays.
*/
@@ -60,4 +62,28 @@ void DrawLEDs(const int* values, int numValues, int cols, const ImU32* colors,
float size = 0.0f, float spacing = 0.0f,
const LEDConfig& config = LEDConfig{});
/**
* Draw a 2D array of LEDs.
*
* Values are indices into colors array. Positive values are filled (lit),
* negative values are unfilled (dark / border only). The actual color index
* is the absolute value of the value - 1. 0 values are not drawn at all
* (an empty space is left).
*
* @param values values array
* @param sources sources array
* @param numValues size of values and sources arrays
* @param cols number of columns
* @param colors colors array
* @param size size of each LED (both horizontal and vertical);
* if 0, defaults to 1/2 of font size
* @param spacing spacing between each LED (both horizontal and vertical);
* if 0, defaults to 1/3 of font size
* @param config 2D array configuration
*/
void DrawLEDSources(const int* values, GuiDataSource** sources, int numValues,
int cols, const ImU32* colors, float size = 0.0f,
float spacing = 0.0f,
const LEDConfig& config = LEDConfig{});
} // namespace halsimgui

View File

@@ -0,0 +1,186 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#pragma once
#include <atomic>
#include <string>
#include <imgui.h>
#include <wpi/Signal.h>
#include <wpi/StringRef.h>
#include <wpi/Twine.h>
#include <wpi/spinlock.h>
namespace halsimgui {
/**
* A data source.
*/
class GuiDataSource {
public:
explicit GuiDataSource(const wpi::Twine& id);
GuiDataSource(const wpi::Twine& id, int index);
GuiDataSource(const wpi::Twine& id, int index, int index2);
~GuiDataSource();
GuiDataSource(const GuiDataSource&) = delete;
GuiDataSource& operator=(const GuiDataSource&) = delete;
const char* GetId() const { return m_id.c_str(); }
void SetName(const wpi::Twine& name) { m_name = name.str(); }
const char* GetName() const { return m_name.c_str(); }
void SetDigital(bool digital) { m_digital = digital; }
bool IsDigital() const { return m_digital; }
void SetValue(double value) {
m_value = value;
valueChanged(value);
}
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;
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,
const char* format = "%.3f", float power = 1.0f) const;
bool InputDouble(const char* label, double* v, double step = 0.0,
double step_fast = 0.0, const char* format = "%.6f",
ImGuiInputTextFlags flags = 0) const;
bool InputInt(const char* label, int* v, int step = 1, int step_fast = 100,
ImGuiInputTextFlags flags = 0) const;
void EmitDrag(ImGuiDragDropFlags flags = 0) const;
wpi::sig::SignalBase<wpi::spinlock, double> valueChanged;
static GuiDataSource* Find(wpi::StringRef id);
static wpi::sig::Signal<const char*, GuiDataSource*> sourceCreated;
private:
std::string m_id;
std::string m_name;
bool m_digital = false;
std::atomic<double> m_value = 0;
};
} // namespace halsimgui
#define HALSIMGUI_DATASOURCE(cbname, id, TYPE, vtype) \
class cbname##Source : public ::halsimgui::GuiDataSource { \
public: \
cbname##Source() \
: GuiDataSource(id), \
m_callback{ \
HALSIM_Register##cbname##Callback(CallbackFunc, this, true)} { \
SetDigital(HAL_##TYPE == HAL_BOOLEAN); \
} \
\
~cbname##Source() { \
if (m_callback != 0) HALSIM_Cancel##cbname##Callback(m_callback); \
} \
\
private: \
static void CallbackFunc(const char*, void* param, \
const HAL_Value* value) { \
if (value->type == HAL_##TYPE) \
static_cast<cbname##Source*>(param)->SetValue(value->data.v_##vtype); \
} \
\
int32_t m_callback; \
}
#define HALSIMGUI_DATASOURCE_BOOLEAN(cbname, id) \
HALSIMGUI_DATASOURCE(cbname, id, BOOLEAN, boolean)
#define HALSIMGUI_DATASOURCE_DOUBLE(cbname, id) \
HALSIMGUI_DATASOURCE(cbname, id, DOUBLE, double)
#define HALSIMGUI_DATASOURCE_INT(cbname, id) \
HALSIMGUI_DATASOURCE(cbname, id, INT, int)
#define HALSIMGUI_DATASOURCE_INDEXED(cbname, id, TYPE, vtype) \
class cbname##Source : public ::halsimgui::GuiDataSource { \
public: \
explicit cbname##Source(int32_t index, int channel = -1) \
: GuiDataSource(id, channel < 0 ? index : channel), \
m_index{index}, \
m_channel{channel < 0 ? index : channel}, \
m_callback{HALSIM_Register##cbname##Callback(index, CallbackFunc, \
this, true)} { \
SetDigital(HAL_##TYPE == HAL_BOOLEAN); \
} \
\
~cbname##Source() { \
if (m_callback != 0) \
HALSIM_Cancel##cbname##Callback(m_index, m_callback); \
} \
\
int32_t GetIndex() const { return m_index; } \
\
int GetChannel() const { return m_channel; } \
\
private: \
static void CallbackFunc(const char*, void* param, \
const HAL_Value* value) { \
if (value->type == HAL_##TYPE) \
static_cast<cbname##Source*>(param)->SetValue(value->data.v_##vtype); \
} \
\
int32_t m_index; \
int m_channel; \
int32_t m_callback; \
}
#define HALSIMGUI_DATASOURCE_BOOLEAN_INDEXED(cbname, id) \
HALSIMGUI_DATASOURCE_INDEXED(cbname, id, BOOLEAN, boolean)
#define HALSIMGUI_DATASOURCE_DOUBLE_INDEXED(cbname, id) \
HALSIMGUI_DATASOURCE_INDEXED(cbname, id, DOUBLE, double)
#define HALSIMGUI_DATASOURCE_INDEXED2(cbname, id, TYPE, vtype) \
class cbname##Source : public ::halsimgui::GuiDataSource { \
public: \
explicit cbname##Source(int32_t index, int32_t channel) \
: GuiDataSource(id, index, channel), \
m_index{index}, \
m_channel{channel}, \
m_callback{HALSIM_Register##cbname##Callback( \
index, channel, CallbackFunc, this, true)} { \
SetDigital(HAL_##TYPE == HAL_BOOLEAN); \
} \
\
~cbname##Source() { \
if (m_callback != 0) \
HALSIM_Cancel##cbname##Callback(m_index, m_channel, m_callback); \
} \
\
int32_t GetIndex() const { return m_index; } \
\
int32_t GetChannel() const { return m_channel; } \
\
private: \
static void CallbackFunc(const char*, void* param, \
const HAL_Value* value) { \
if (value->type == HAL_##TYPE) \
static_cast<cbname##Source*>(param)->SetValue(value->data.v_##vtype); \
} \
\
int32_t m_index; \
int32_t m_channel; \
int32_t m_callback; \
}
#define HALSIMGUI_DATASOURCE_BOOLEAN_INDEXED2(cbname, id) \
HALSIMGUI_DATASOURCE_INDEXED2(cbname, id, BOOLEAN, boolean)
#define HALSIMGUI_DATASOURCE_DOUBLE_INDEXED2(cbname, id) \
HALSIMGUI_DATASOURCE_INDEXED2(cbname, id, DOUBLE, double)

View File

@@ -18,16 +18,24 @@ class NameInfo {
bool HasName() const { return m_name[0] != '\0'; }
const char* GetName() const { return m_name; }
void GetName(char* buf, size_t size, const char* defaultName);
void GetName(char* buf, size_t size, const char* defaultName, int index);
void GetName(char* buf, size_t size, const char* defaultName) const;
void GetName(char* buf, size_t size, const char* defaultName,
int index) const;
void GetName(char* buf, size_t size, const char* defaultName, int index,
int index2);
int index2) const;
void GetLabel(char* buf, size_t size, const char* defaultName) const;
void GetLabel(char* buf, size_t size, const char* defaultName,
int index) const;
void GetLabel(char* buf, size_t size, const char* defaultName, int index,
int index2) const;
bool ReadIni(wpi::StringRef name, wpi::StringRef value);
void WriteIni(ImGuiTextBuffer* out);
void PushEditNameId(int index);
void PushEditNameId(const char* name);
void PopupEditName(int index);
void PopupEditName(const char* name);
bool PopupEditName(int index);
bool PopupEditName(const char* name);
bool InputTextName(const char* label_id, ImGuiInputTextFlags flags = 0);
private:
char m_name[64];

View File

@@ -0,0 +1,36 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#pragma once
#include <vector>
#include <imgui.h>
#include <imgui_internal.h>
namespace halsimgui {
template <typename Info>
class IniSaverVector : public std::vector<Info> {
public:
explicit IniSaverVector(const char* typeName) : m_typeName(typeName) {}
void Initialize();
private:
static void* ReadOpen(ImGuiContext* ctx, ImGuiSettingsHandler* handler,
const char* name);
static void ReadLine(ImGuiContext* ctx, ImGuiSettingsHandler* handler,
void* entry, const char* lineStr);
static void WriteAll(ImGuiContext* ctx, ImGuiSettingsHandler* handler,
ImGuiTextBuffer* out_buf);
const char* m_typeName;
};
} // namespace halsimgui
#include "IniSaverVector.inl"

View File

@@ -0,0 +1,60 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#pragma once
namespace halsimgui {
template <typename Info>
void IniSaverVector<Info>::Initialize() {
// hook ini handler to save settings
ImGuiSettingsHandler iniHandler;
iniHandler.TypeName = m_typeName;
iniHandler.TypeHash = ImHashStr(m_typeName);
iniHandler.ReadOpenFn = ReadOpen;
iniHandler.ReadLineFn = ReadLine;
iniHandler.WriteAllFn = WriteAll;
iniHandler.UserData = this;
ImGui::GetCurrentContext()->SettingsHandlers.push_back(iniHandler);
}
template <typename Info>
void* IniSaverVector<Info>::ReadOpen(ImGuiContext* ctx,
ImGuiSettingsHandler* handler,
const char* name) {
auto self = static_cast<IniSaverVector*>(handler->UserData);
unsigned int num;
if (wpi::StringRef{name}.getAsInteger(10, num)) return nullptr;
if (num >= self->size()) self->resize(num + 1);
return &(*self)[num];
}
template <typename Info>
void IniSaverVector<Info>::ReadLine(ImGuiContext* ctx,
ImGuiSettingsHandler* handler, void* entry,
const char* lineStr) {
auto element = static_cast<Info*>(entry);
wpi::StringRef line{lineStr};
auto [name, value] = line.split('=');
name = name.trim();
value = value.trim();
element->ReadIni(name, value);
}
template <typename Info>
void IniSaverVector<Info>::WriteAll(ImGuiContext* ctx,
ImGuiSettingsHandler* handler,
ImGuiTextBuffer* out_buf) {
auto self = static_cast<IniSaverVector*>(handler->UserData);
for (size_t i = 0; i < self->size(); ++i) {
out_buf->appendf("[%s][%d]\n", self->m_typeName, static_cast<int>(i));
(*self)[i].WriteIni(out_buf);
out_buf->append("\n");
}
}
} // namespace halsimgui

View File

@@ -1,5 +1,5 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2019 FIRST. All Rights Reserved. */
/* Copyright (c) 2019-2020 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
@@ -35,6 +35,8 @@ void HALSIMGUI_DeviceTreeFinishDevice(void);
namespace halsimgui {
class GuiDataSource;
class SimDeviceGui {
public:
static void Initialize();
@@ -69,6 +71,22 @@ class SimDeviceGui {
const char** options = nullptr,
int32_t numOptions = 0);
/**
* Displays device value formatted the same way as SimDevice device values.
*
* @param name value name
* @param readonly prevent value from being modified by the user
* @param value value contents (modified in place)
* @param source data source (may be nullptr)
* @param options options array for enum values
* @param numOptions size of options array for enum values
* @return True if value was modified by the user
*/
static bool DisplayValueSource(const char* name, bool readonly,
HAL_Value* value, const GuiDataSource* source,
const char** options = nullptr,
int32_t numOptions = 0);
/**
* Wraps ImGui::CollapsingHeader() to provide consistency and open
* persistence. As with the ImGui function, returns true if the tree node