mirror of
https://github.com/wpilibsuite/allwpilib
synced 2026-06-19 00:41:43 +00:00
[sim] Use wpigui for simulator GUI
This commit is contained in:
@@ -36,5 +36,6 @@ includeOtherLibs {
|
||||
^unsupported/
|
||||
^vision/
|
||||
^wpi/
|
||||
^wpigui
|
||||
^wpimath/
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ file(GLOB halsim_gui_src src/main/native/cpp/*.cpp)
|
||||
add_library(halsim_gui MODULE ${halsim_gui_src})
|
||||
wpilib_target_warnings(halsim_gui)
|
||||
set_target_properties(halsim_gui PROPERTIES DEBUG_POSTFIX "d")
|
||||
target_link_libraries(halsim_gui PUBLIC hal ntcore wpimath PRIVATE imgui)
|
||||
target_link_libraries(halsim_gui PUBLIC hal ntcore wpimath PRIVATE wpigui)
|
||||
|
||||
target_include_directories(halsim_gui PRIVATE src/main/native/include)
|
||||
|
||||
|
||||
@@ -24,15 +24,16 @@ if (!project.hasProperty('onlylinuxathena') && !project.hasProperty('onlylinuxra
|
||||
binaries {
|
||||
all {
|
||||
lib project: ':wpimath', library: 'wpimath', linkage: 'shared'
|
||||
lib project: ':wpigui', library: 'wpigui', linkage: 'static'
|
||||
nativeUtils.useRequiredLibrary(it, 'imgui_static')
|
||||
if (it.targetPlatform.name == nativeUtils.wpi.platforms.roborio || it.targetPlatform.name == nativeUtils.wpi.platforms.raspbian || it.targetPlatform.name == nativeUtils.wpi.platforms.aarch64bionic) {
|
||||
it.buildable = false
|
||||
return
|
||||
}
|
||||
if (it.targetPlatform.operatingSystem.isWindows()) {
|
||||
it.linker.args << 'Gdi32.lib' << 'Shell32.lib'
|
||||
it.linker.args << 'Gdi32.lib' << 'Shell32.lib' << 'd3d11.lib' << 'd3dcompiler.lib'
|
||||
} else if (it.targetPlatform.operatingSystem.isMacOsX()) {
|
||||
it.linker.args << '-framework' << 'Cocoa' << '-framework' << 'IOKit' << '-framework' << 'CoreFoundation' << '-framework' << 'CoreVideo'
|
||||
it.linker.args << '-framework' << 'Metal' << '-framework' << 'MetalKit' << '-framework' << 'Cocoa' << '-framework' << 'IOKit' << '-framework' << 'CoreFoundation' << '-framework' << 'CoreVideo' << '-framework' << 'QuartzCore'
|
||||
} else {
|
||||
it.linker.args << '-lX11'
|
||||
}
|
||||
|
||||
@@ -9,12 +9,11 @@
|
||||
|
||||
#include <cmath>
|
||||
|
||||
#include <GL/gl3w.h>
|
||||
#include <hal/SimDevice.h>
|
||||
#include <imgui.h>
|
||||
|
||||
#define IMGUI_DEFINE_MATH_OPERATORS
|
||||
#include <hal/simulation/SimDeviceData.h>
|
||||
#include <imgui.h>
|
||||
#include <imgui_internal.h>
|
||||
#include <units/angle.h>
|
||||
#include <units/length.h>
|
||||
@@ -23,6 +22,7 @@
|
||||
#include <wpi/json.h>
|
||||
#include <wpi/raw_istream.h>
|
||||
#include <wpi/raw_ostream.h>
|
||||
#include <wpigui.h>
|
||||
|
||||
#include "GuiUtil.h"
|
||||
#include "HALSimGui.h"
|
||||
@@ -31,6 +31,8 @@
|
||||
|
||||
using namespace halsimgui;
|
||||
|
||||
namespace gui = wpi::gui;
|
||||
|
||||
namespace {
|
||||
|
||||
// Per-frame field data (not persistent)
|
||||
@@ -64,10 +66,10 @@ class FieldInfo {
|
||||
void WriteIni(ImGuiTextBuffer* out) const;
|
||||
|
||||
private:
|
||||
bool LoadImageImpl(const wpi::Twine& fn);
|
||||
bool LoadImageImpl(const char* fn);
|
||||
|
||||
std::string m_filename;
|
||||
GLuint m_texture = 0;
|
||||
ImTextureID m_texture = 0;
|
||||
int m_imageWidth = 0;
|
||||
int m_imageHeight = 0;
|
||||
int m_top = 0;
|
||||
@@ -114,10 +116,10 @@ class RobotInfo {
|
||||
void WriteIni(ImGuiTextBuffer* out) const;
|
||||
|
||||
private:
|
||||
bool LoadImageImpl(const wpi::Twine& fn);
|
||||
bool LoadImageImpl(const char* fn);
|
||||
|
||||
std::string m_filename;
|
||||
GLuint m_texture = 0;
|
||||
ImTextureID m_texture = 0;
|
||||
|
||||
HAL_SimDeviceHandle m_devHandle = 0;
|
||||
hal::SimDouble m_xHandle;
|
||||
@@ -164,7 +166,7 @@ static void Field2DWriteAll(ImGuiContext* ctx, ImGuiSettingsHandler* handler,
|
||||
}
|
||||
|
||||
void FieldInfo::Reset() {
|
||||
if (m_texture != 0) glDeleteTextures(1, &m_texture);
|
||||
if (m_texture != 0) gui::DeleteTexture(m_texture);
|
||||
m_texture = 0;
|
||||
m_filename.clear();
|
||||
m_imageWidth = 0;
|
||||
@@ -182,7 +184,7 @@ void FieldInfo::LoadImage() {
|
||||
if (wpi::StringRef(result[0]).endswith(".json")) {
|
||||
LoadJson(result[0]);
|
||||
} else {
|
||||
LoadImageImpl(result[0]);
|
||||
LoadImageImpl(result[0].c_str());
|
||||
m_top = 0;
|
||||
m_left = 0;
|
||||
m_bottom = -1;
|
||||
@@ -192,7 +194,7 @@ void FieldInfo::LoadImage() {
|
||||
m_fileOpener.reset();
|
||||
}
|
||||
if (m_texture == 0 && !m_filename.empty()) {
|
||||
if (!LoadImageImpl(m_filename)) m_filename.clear();
|
||||
if (!LoadImageImpl(m_filename.c_str())) m_filename.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -274,7 +276,7 @@ void FieldInfo::LoadJson(const wpi::Twine& jsonfile) {
|
||||
wpi::sys::path::append(pathname, image);
|
||||
|
||||
// load field image
|
||||
if (!LoadImageImpl(pathname)) return;
|
||||
if (!LoadImageImpl(pathname.c_str())) return;
|
||||
|
||||
// save to field info
|
||||
m_filename = pathname.str();
|
||||
@@ -286,15 +288,16 @@ void FieldInfo::LoadJson(const wpi::Twine& jsonfile) {
|
||||
m_height = height;
|
||||
}
|
||||
|
||||
bool FieldInfo::LoadImageImpl(const wpi::Twine& fn) {
|
||||
bool FieldInfo::LoadImageImpl(const char* fn) {
|
||||
wpi::outs() << "GUI: loading field image '" << fn << "'\n";
|
||||
GLuint oldTexture = m_texture;
|
||||
if (!LoadTextureFromFile(fn, &m_texture, &m_imageWidth, &m_imageHeight)) {
|
||||
auto oldTexture = m_texture;
|
||||
if (!gui::LoadTextureFromFile(fn, &m_texture, &m_imageWidth,
|
||||
&m_imageHeight)) {
|
||||
wpi::errs() << "GUI: could not read field image\n";
|
||||
return false;
|
||||
}
|
||||
if (oldTexture != 0) glDeleteTextures(1, &oldTexture);
|
||||
m_filename = fn.str();
|
||||
if (oldTexture != 0) gui::DeleteTexture(oldTexture);
|
||||
m_filename = fn;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -332,9 +335,8 @@ FieldFrameData FieldInfo::GetFrameData() const {
|
||||
void FieldInfo::Draw(ImDrawList* drawList, const ImVec2& windowPos,
|
||||
const FieldFrameData& ffd) const {
|
||||
if (m_texture != 0 && m_imageHeight != 0 && m_imageWidth != 0) {
|
||||
drawList->AddImage(
|
||||
reinterpret_cast<ImTextureID>(static_cast<uintptr_t>(m_texture)),
|
||||
windowPos + ffd.imageMin, windowPos + ffd.imageMax);
|
||||
drawList->AddImage(m_texture, windowPos + ffd.imageMin,
|
||||
windowPos + ffd.imageMax);
|
||||
}
|
||||
|
||||
// draw the field "active area" as a yellow boundary box
|
||||
@@ -379,7 +381,7 @@ void FieldInfo::WriteIni(ImGuiTextBuffer* out) const {
|
||||
}
|
||||
|
||||
void RobotInfo::Reset() {
|
||||
if (m_texture != 0) glDeleteTextures(1, &m_texture);
|
||||
if (m_texture != 0) gui::DeleteTexture(m_texture);
|
||||
m_texture = 0;
|
||||
m_filename.clear();
|
||||
}
|
||||
@@ -387,23 +389,23 @@ void RobotInfo::Reset() {
|
||||
void RobotInfo::LoadImage() {
|
||||
if (m_fileOpener && m_fileOpener->ready(0)) {
|
||||
auto result = m_fileOpener->result();
|
||||
if (!result.empty()) LoadImageImpl(result[0]);
|
||||
if (!result.empty()) LoadImageImpl(result[0].c_str());
|
||||
m_fileOpener.reset();
|
||||
}
|
||||
if (m_texture == 0 && !m_filename.empty()) {
|
||||
if (!LoadImageImpl(m_filename)) m_filename.clear();
|
||||
if (!LoadImageImpl(m_filename.c_str())) m_filename.clear();
|
||||
}
|
||||
}
|
||||
|
||||
bool RobotInfo::LoadImageImpl(const wpi::Twine& fn) {
|
||||
bool RobotInfo::LoadImageImpl(const char* fn) {
|
||||
wpi::outs() << "GUI: loading robot image '" << fn << "'\n";
|
||||
GLuint oldTexture = m_texture;
|
||||
if (!LoadTextureFromFile(fn, &m_texture, nullptr, nullptr)) {
|
||||
auto oldTexture = m_texture;
|
||||
if (!gui::LoadTextureFromFile(fn, &m_texture, nullptr, nullptr)) {
|
||||
wpi::errs() << "GUI: could not read robot image\n";
|
||||
return false;
|
||||
}
|
||||
if (oldTexture != 0) glDeleteTextures(1, &oldTexture);
|
||||
m_filename = fn.str();
|
||||
if (oldTexture != 0) gui::DeleteTexture(oldTexture);
|
||||
m_filename = fn;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -470,8 +472,7 @@ void RobotInfo::Draw(ImDrawList* drawList, const ImVec2& windowPos,
|
||||
float hitRadius) const {
|
||||
if (m_texture != 0) {
|
||||
drawList->AddImageQuad(
|
||||
reinterpret_cast<ImTextureID>(static_cast<uintptr_t>(m_texture)),
|
||||
windowPos + rfd.corners[0], windowPos + rfd.corners[1],
|
||||
m_texture, windowPos + rfd.corners[0], windowPos + rfd.corners[1],
|
||||
windowPos + rfd.corners[2], windowPos + rfd.corners[3]);
|
||||
} else {
|
||||
drawList->AddQuad(windowPos + rfd.corners[0], windowPos + rfd.corners[1],
|
||||
|
||||
@@ -7,45 +7,6 @@
|
||||
|
||||
#include "GuiUtil.h"
|
||||
|
||||
#include <stb_image.h>
|
||||
|
||||
#include <wpi/SmallString.h>
|
||||
|
||||
bool halsimgui::LoadTextureFromFile(const wpi::Twine& filename,
|
||||
GLuint* out_texture, int* out_width,
|
||||
int* out_height) {
|
||||
wpi::SmallString<128> buf;
|
||||
|
||||
// Load from file
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
unsigned char* data =
|
||||
stbi_load(filename.toNullTerminatedStringRef(buf).data(), &width, &height,
|
||||
nullptr, 4);
|
||||
if (!data) return false;
|
||||
|
||||
// Create a OpenGL texture identifier
|
||||
GLuint texture;
|
||||
glGenTextures(1, &texture);
|
||||
glBindTexture(GL_TEXTURE_2D, texture);
|
||||
|
||||
// Setup filtering parameters for display
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
|
||||
// Upload pixels into texture
|
||||
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA,
|
||||
GL_UNSIGNED_BYTE, data);
|
||||
stbi_image_free(data);
|
||||
|
||||
*out_texture = texture;
|
||||
if (out_width) *out_width = width;
|
||||
if (out_height) *out_height = height;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void halsimgui::MaxFit(ImVec2* min, ImVec2* max, float width, float height) {
|
||||
float destWidth = max->x - min->x;
|
||||
float destHeight = max->y - min->y;
|
||||
|
||||
@@ -8,21 +8,18 @@
|
||||
#include "HALSimGui.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
|
||||
#include <GL/gl3w.h>
|
||||
#include <GLFW/glfw3.h>
|
||||
#include <hal/simulation/DriverStationData.h>
|
||||
#include <imgui.h>
|
||||
#include <imgui_ProggyDotted.h>
|
||||
#include <imgui_impl_glfw.h>
|
||||
#include <imgui_impl_opengl3.h>
|
||||
#include <imgui_internal.h>
|
||||
#include <wpi/StringMap.h>
|
||||
#include <wpi/raw_ostream.h>
|
||||
#include <wpigui.h>
|
||||
|
||||
using namespace halsimgui;
|
||||
|
||||
namespace gui = wpi::gui;
|
||||
|
||||
namespace {
|
||||
struct WindowInfo {
|
||||
WindowInfo() = default;
|
||||
@@ -45,54 +42,18 @@ struct WindowInfo {
|
||||
};
|
||||
} // namespace
|
||||
|
||||
static std::atomic_bool gExit{false};
|
||||
static GLFWwindow* gWindow;
|
||||
static bool gWindowLoadedWidthHeight = false;
|
||||
static int gWindowWidth = 1280;
|
||||
static int gWindowHeight = 720;
|
||||
static int gWindowMaximized = 0;
|
||||
static int gWindowXPos = -1;
|
||||
static int gWindowYPos = -1;
|
||||
static std::vector<std::function<void()>> gInitializers;
|
||||
static std::vector<std::function<void()>> gExecutors;
|
||||
static std::vector<WindowInfo> gWindows;
|
||||
static wpi::StringMap<int> gWindowMap; // index into gWindows
|
||||
static std::vector<int> gSortedWindows; // index into gWindows
|
||||
static std::vector<std::function<void()>> gOptionMenus;
|
||||
static std::vector<std::function<void()>> gMenus;
|
||||
static int gUserScale = 2;
|
||||
static int gStyle = 0;
|
||||
static constexpr int kScaledFontLevels = 9;
|
||||
static ImFont* gScaledFont[kScaledFontLevels];
|
||||
static bool gDisableOutputsOnDSDisable = true;
|
||||
|
||||
static void glfw_error_callback(int error, const char* description) {
|
||||
wpi::errs() << "GLFW Error " << error << ": " << description << '\n';
|
||||
}
|
||||
|
||||
static void glfw_window_size_callback(GLFWwindow*, int width, int height) {
|
||||
if (!gWindowMaximized) {
|
||||
gWindowWidth = width;
|
||||
gWindowHeight = height;
|
||||
}
|
||||
}
|
||||
|
||||
static void glfw_window_maximize_callback(GLFWwindow* window, int maximized) {
|
||||
gWindowMaximized = maximized;
|
||||
}
|
||||
|
||||
static void glfw_window_pos_callback(GLFWwindow* window, int xpos, int ypos) {
|
||||
if (!gWindowMaximized) {
|
||||
gWindowXPos = xpos;
|
||||
gWindowYPos = ypos;
|
||||
}
|
||||
}
|
||||
|
||||
// read/write open state to ini file
|
||||
static void* SimWindowsReadOpen(ImGuiContext* ctx,
|
||||
ImGuiSettingsHandler* handler,
|
||||
const char* name) {
|
||||
if (wpi::StringRef{name} == "GLOBAL") return &gWindow;
|
||||
if (wpi::StringRef{name} == "GLOBAL") return &gDisableOutputsOnDSDisable;
|
||||
|
||||
int index = gWindowMap.try_emplace(name, gWindows.size()).first->second;
|
||||
if (index == static_cast<int>(gWindows.size())) {
|
||||
@@ -111,26 +72,10 @@ static void SimWindowsReadLine(ImGuiContext* ctx, ImGuiSettingsHandler* handler,
|
||||
name = name.trim();
|
||||
value = value.trim();
|
||||
|
||||
if (entry == &gWindow) {
|
||||
if (entry == &gDisableOutputsOnDSDisable) {
|
||||
int num;
|
||||
if (value.getAsInteger(10, num)) return;
|
||||
if (name == "width") {
|
||||
gWindowWidth = num;
|
||||
gWindowLoadedWidthHeight = true;
|
||||
} else if (name == "height") {
|
||||
gWindowHeight = num;
|
||||
gWindowLoadedWidthHeight = true;
|
||||
} else if (name == "maximized") {
|
||||
gWindowMaximized = num;
|
||||
} else if (name == "xpos") {
|
||||
gWindowXPos = num;
|
||||
} else if (name == "ypos") {
|
||||
gWindowYPos = num;
|
||||
} else if (name == "userScale") {
|
||||
gUserScale = num;
|
||||
} else if (name == "style") {
|
||||
gStyle = num;
|
||||
} else if (name == "disableOutputsOnDS") {
|
||||
if (name == "disableOutputsOnDS") {
|
||||
gDisableOutputsOnDSDisable = num;
|
||||
}
|
||||
return;
|
||||
@@ -150,37 +95,20 @@ static void SimWindowsReadLine(ImGuiContext* ctx, ImGuiSettingsHandler* handler,
|
||||
|
||||
static void SimWindowsWriteAll(ImGuiContext* ctx, ImGuiSettingsHandler* handler,
|
||||
ImGuiTextBuffer* out_buf) {
|
||||
out_buf->appendf(
|
||||
"[SimWindow][GLOBAL]\nwidth=%d\nheight=%d\nmaximized=%d\n"
|
||||
"xpos=%d\nypos=%d\nuserScale=%d\nstyle=%d\ndisableOutputsOnDS=%d\n",
|
||||
gWindowWidth, gWindowHeight, gWindowMaximized, gWindowXPos, gWindowYPos,
|
||||
gUserScale, gStyle, gDisableOutputsOnDSDisable ? 1 : 0);
|
||||
out_buf->appendf("[SimWindow][GLOBAL]\ndisableOutputsOnDS=%d\n\n",
|
||||
gDisableOutputsOnDSDisable ? 1 : 0);
|
||||
for (auto&& window : gWindows)
|
||||
out_buf->appendf("[SimWindow][%s]\nvisible=%d\nenabled=%d\n\n",
|
||||
window.name.c_str(), window.visible ? 1 : 0,
|
||||
window.enabled ? 1 : 0);
|
||||
}
|
||||
|
||||
static void UpdateStyle() {
|
||||
switch (gStyle) {
|
||||
case 0:
|
||||
ImGui::StyleColorsClassic();
|
||||
break;
|
||||
case 1:
|
||||
ImGui::StyleColorsDark();
|
||||
break;
|
||||
case 2:
|
||||
ImGui::StyleColorsLight();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void HALSimGui::Add(std::function<void()> initialize) {
|
||||
if (initialize) gInitializers.emplace_back(std::move(initialize));
|
||||
gui::AddInit(std::move(initialize));
|
||||
}
|
||||
|
||||
void HALSimGui::AddExecute(std::function<void()> execute) {
|
||||
if (execute) gExecutors.emplace_back(std::move(execute));
|
||||
gui::AddEarlyExecute(std::move(execute));
|
||||
}
|
||||
|
||||
void HALSimGui::AddWindow(const char* name, std::function<void()> display,
|
||||
@@ -262,196 +190,31 @@ bool HALSimGui::AreOutputsDisabled() {
|
||||
return gDisableOutputsOnDSDisable && !HALSIM_GetDriverStationEnabled();
|
||||
}
|
||||
|
||||
void HALSimGui::GlobalInit() { gui::CreateContext(); }
|
||||
|
||||
bool HALSimGui::Initialize() {
|
||||
// Setup window
|
||||
glfwSetErrorCallback(glfw_error_callback);
|
||||
glfwInitHint(GLFW_JOYSTICK_HAT_BUTTONS, GLFW_FALSE);
|
||||
if (!glfwInit()) return false;
|
||||
gui::AddInit([] {
|
||||
// Hook ini handler to save settings
|
||||
ImGuiSettingsHandler iniHandler;
|
||||
iniHandler.TypeName = "SimWindow";
|
||||
iniHandler.TypeHash = ImHashStr(iniHandler.TypeName);
|
||||
iniHandler.ReadOpenFn = SimWindowsReadOpen;
|
||||
iniHandler.ReadLineFn = SimWindowsReadLine;
|
||||
iniHandler.WriteAllFn = SimWindowsWriteAll;
|
||||
ImGui::GetCurrentContext()->SettingsHandlers.push_back(iniHandler);
|
||||
});
|
||||
|
||||
// Decide GL+GLSL versions
|
||||
#if __APPLE__
|
||||
// GL 3.2 + GLSL 150
|
||||
const char* glsl_version = "#version 150";
|
||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
|
||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2);
|
||||
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); // 3.2+ only
|
||||
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // Required on Mac
|
||||
glfwWindowHint(GLFW_COCOA_GRAPHICS_SWITCHING, GLFW_TRUE);
|
||||
#else
|
||||
// GL 3.0 + GLSL 130
|
||||
const char* glsl_version = "#version 130";
|
||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
|
||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);
|
||||
// glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); // 3.2+
|
||||
// glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // 3.0+
|
||||
#endif
|
||||
|
||||
// Setup Dear ImGui context
|
||||
IMGUI_CHECKVERSION();
|
||||
ImGui::CreateContext();
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
(void)io;
|
||||
|
||||
// Hook ini handler to save settings
|
||||
ImGuiSettingsHandler iniHandler;
|
||||
iniHandler.TypeName = "SimWindow";
|
||||
iniHandler.TypeHash = ImHashStr(iniHandler.TypeName);
|
||||
iniHandler.ReadOpenFn = SimWindowsReadOpen;
|
||||
iniHandler.ReadLineFn = SimWindowsReadLine;
|
||||
iniHandler.WriteAllFn = SimWindowsWriteAll;
|
||||
ImGui::GetCurrentContext()->SettingsHandlers.push_back(iniHandler);
|
||||
|
||||
for (auto&& initialize : gInitializers) {
|
||||
if (initialize) initialize();
|
||||
}
|
||||
|
||||
// Load INI file
|
||||
ImGui::LoadIniSettingsFromDisk(io.IniFilename);
|
||||
|
||||
// Set initial window settings
|
||||
glfwWindowHint(GLFW_MAXIMIZED, gWindowMaximized ? GLFW_TRUE : GLFW_FALSE);
|
||||
|
||||
if (gWindowWidth == 0 || gWindowHeight == 0) {
|
||||
gWindowWidth = 1280;
|
||||
gWindowHeight = 720;
|
||||
gWindowLoadedWidthHeight = false;
|
||||
}
|
||||
|
||||
float windowScale = 1.0;
|
||||
if (!gWindowLoadedWidthHeight) {
|
||||
glfwWindowHint(GLFW_SCALE_TO_MONITOR, GLFW_TRUE);
|
||||
// get the primary monitor work area to see if we have a reasonable initial
|
||||
// window size; if not, maximize, and default scaling to smaller
|
||||
if (GLFWmonitor* primary = glfwGetPrimaryMonitor()) {
|
||||
int monWidth, monHeight;
|
||||
glfwGetMonitorWorkarea(primary, nullptr, nullptr, &monWidth, &monHeight);
|
||||
if (monWidth < gWindowWidth || monHeight < gWindowHeight) {
|
||||
glfwWindowHint(GLFW_MAXIMIZED, GLFW_TRUE);
|
||||
windowScale = (std::min)(monWidth * 1.0 / gWindowWidth,
|
||||
monHeight * 1.0 / gWindowHeight);
|
||||
gui::AddWindowScaler([](float windowScale) {
|
||||
// scale default window positions
|
||||
for (auto&& window : gWindows) {
|
||||
if ((window.posCond & ImGuiCond_FirstUseEver) != 0) {
|
||||
window.pos.x *= windowScale;
|
||||
window.pos.y *= windowScale;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (gWindowXPos != -1 && gWindowYPos != -1)
|
||||
glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE);
|
||||
|
||||
// Create window with graphics context
|
||||
gWindow = glfwCreateWindow(gWindowWidth, gWindowHeight,
|
||||
"Robot Simulation GUI", nullptr, nullptr);
|
||||
if (!gWindow) return false;
|
||||
|
||||
if (!gWindowLoadedWidthHeight) {
|
||||
if (windowScale == 1.0)
|
||||
glfwGetWindowContentScale(gWindow, &windowScale, nullptr);
|
||||
wpi::outs() << "windowScale = " << windowScale;
|
||||
// force user scale if window scale is smaller
|
||||
if (windowScale <= 0.5)
|
||||
gUserScale = 0;
|
||||
else if (windowScale <= 0.75)
|
||||
gUserScale = 1;
|
||||
if (windowScale != 1.0) {
|
||||
// scale default window positions
|
||||
for (auto&& window : gWindows) {
|
||||
if ((window.posCond & ImGuiCond_FirstUseEver) != 0) {
|
||||
window.pos.x *= windowScale;
|
||||
window.pos.y *= windowScale;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update window settings
|
||||
if (gWindowXPos != -1 && gWindowYPos != -1) {
|
||||
glfwSetWindowPos(gWindow, gWindowXPos, gWindowYPos);
|
||||
glfwShowWindow(gWindow);
|
||||
}
|
||||
|
||||
// Set window callbacks
|
||||
glfwGetWindowSize(gWindow, &gWindowWidth, &gWindowHeight);
|
||||
glfwSetWindowSizeCallback(gWindow, glfw_window_size_callback);
|
||||
glfwSetWindowMaximizeCallback(gWindow, glfw_window_maximize_callback);
|
||||
glfwSetWindowPosCallback(gWindow, glfw_window_pos_callback);
|
||||
|
||||
glfwMakeContextCurrent(gWindow);
|
||||
glfwSwapInterval(1); // Enable vsync
|
||||
|
||||
// Initialize OpenGL loader
|
||||
if (gl3wInit() != 0) {
|
||||
wpi::errs() << "Failed to initialize OpenGL loader!\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Setup Dear ImGui style
|
||||
UpdateStyle();
|
||||
|
||||
// Setup Platform/Renderer bindings
|
||||
ImGui_ImplGlfw_InitForOpenGL(gWindow, true);
|
||||
ImGui_ImplOpenGL3_Init(glsl_version);
|
||||
|
||||
// Load Fonts
|
||||
// - If no fonts are loaded, dear imgui will use the default font. You can
|
||||
// also load multiple fonts and use ImGui::PushFont()/PopFont() to select
|
||||
// them.
|
||||
// - AddFontFromFileTTF() will return the ImFont* so you can store it if you
|
||||
// need to select the font among multiple.
|
||||
// - If the file cannot be loaded, the function will return NULL. Please
|
||||
// handle those errors in your application (e.g. use an assertion, or display
|
||||
// an error and quit).
|
||||
// - The fonts will be rasterized at a given size (w/ oversampling) and stored
|
||||
// into a texture when calling ImFontAtlas::Build()/GetTexDataAsXXXX(), which
|
||||
// ImGui_ImplXXXX_NewFrame below will call.
|
||||
// - Read 'misc/fonts/README.txt' for more instructions and details.
|
||||
// - Remember that in C/C++ if you want to include a backslash \ in a string
|
||||
// literal you need to write a double backslash \\ !
|
||||
// io.Fonts->AddFontDefault();
|
||||
// io.Fonts->AddFontFromFileTTF("../../misc/fonts/Roboto-Medium.ttf", 16.0f);
|
||||
// io.Fonts->AddFontFromFileTTF("../../misc/fonts/Cousine-Regular.ttf", 15.0f);
|
||||
// io.Fonts->AddFontFromFileTTF("../../misc/fonts/DroidSans.ttf", 16.0f);
|
||||
// io.Fonts->AddFontFromFileTTF("../../misc/fonts/ProggyTiny.ttf", 10.0f);
|
||||
// ImFont* font =
|
||||
// io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 18.0f,
|
||||
// NULL, io.Fonts->GetGlyphRangesJapanese()); IM_ASSERT(font != NULL);
|
||||
|
||||
// this range is based on 13px being the "nominal" 100% size and going from
|
||||
// ~0.5x (7px) to ~2.0x (25px)
|
||||
for (int i = 0; i < kScaledFontLevels; ++i) {
|
||||
float size = 7.0f + i * 3.0f;
|
||||
ImFontConfig cfg;
|
||||
std::snprintf(cfg.Name, sizeof(cfg.Name), "ProggyDotted-%d",
|
||||
static_cast<int>(size));
|
||||
gScaledFont[i] = ImGui::AddFontProggyDotted(io, size, &cfg);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void HALSimGui::Main(void*) {
|
||||
ImVec4 clear_color = ImVec4(0.45f, 0.55f, 0.60f, 1.00f);
|
||||
|
||||
// Main loop
|
||||
while (!glfwWindowShouldClose(gWindow) && !gExit) {
|
||||
// Poll and handle events (inputs, window resize, etc.)
|
||||
glfwPollEvents();
|
||||
|
||||
// Start the Dear ImGui frame
|
||||
ImGui_ImplOpenGL3_NewFrame();
|
||||
ImGui_ImplGlfw_NewFrame();
|
||||
ImGui::NewFrame();
|
||||
|
||||
// Scale based on OS window content scaling
|
||||
float windowScale = 1.0;
|
||||
glfwGetWindowContentScale(gWindow, &windowScale, nullptr);
|
||||
// map to closest font size: 0 = 0.5x, 1 = 0.75x, 2 = 1.0x, 3 = 1.25x,
|
||||
// 4 = 1.5x, 5 = 1.75x, 6 = 2x
|
||||
int fontScale =
|
||||
std::clamp(gUserScale + static_cast<int>((windowScale - 1.0) * 4), 0,
|
||||
kScaledFontLevels - 1);
|
||||
ImGui::GetIO().FontDefault = gScaledFont[fontScale];
|
||||
|
||||
for (auto&& execute : gExecutors) {
|
||||
if (execute) execute();
|
||||
}
|
||||
});
|
||||
|
||||
gui::AddLateExecute([] {
|
||||
{
|
||||
ImGui::BeginMainMenuBar();
|
||||
|
||||
@@ -464,41 +227,7 @@ void HALSimGui::Main(void*) {
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
|
||||
if (ImGui::BeginMenu("View")) {
|
||||
if (ImGui::BeginMenu("Style")) {
|
||||
bool selected;
|
||||
selected = gStyle == 0;
|
||||
if (ImGui::MenuItem("Classic", nullptr, &selected, true)) {
|
||||
gStyle = 0;
|
||||
UpdateStyle();
|
||||
}
|
||||
selected = gStyle == 1;
|
||||
if (ImGui::MenuItem("Dark", nullptr, &selected, true)) {
|
||||
gStyle = 1;
|
||||
UpdateStyle();
|
||||
}
|
||||
selected = gStyle == 2;
|
||||
if (ImGui::MenuItem("Light", nullptr, &selected, true)) {
|
||||
gStyle = 2;
|
||||
UpdateStyle();
|
||||
}
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
|
||||
if (ImGui::BeginMenu("Zoom")) {
|
||||
for (int i = 0; i < kScaledFontLevels && (25 * (i + 2)) <= 200; ++i) {
|
||||
char label[20];
|
||||
std::snprintf(label, sizeof(label), "%d%%", 25 * (i + 2));
|
||||
bool selected = gUserScale == i;
|
||||
bool enabled = (fontScale - gUserScale + i) >= 0 &&
|
||||
(fontScale - gUserScale + i) < kScaledFontLevels;
|
||||
if (ImGui::MenuItem(label, nullptr, &selected, enabled))
|
||||
gUserScale = i;
|
||||
}
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
gui::EmitViewMenu();
|
||||
|
||||
if (ImGui::BeginMenu("Window")) {
|
||||
for (auto&& windowIndex : gSortedWindows) {
|
||||
@@ -539,29 +268,16 @@ void HALSimGui::Main(void*) {
|
||||
if (window.setPadding) ImGui::PopStyleVar();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Rendering
|
||||
ImGui::Render();
|
||||
int display_w, display_h;
|
||||
glfwGetFramebufferSize(gWindow, &display_w, &display_h);
|
||||
glViewport(0, 0, display_w, display_h);
|
||||
glClearColor(clear_color.x, clear_color.y, clear_color.z, clear_color.w);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
|
||||
if (!gui::Initialize("", 1280, 720)) return false;
|
||||
|
||||
glfwSwapBuffers(gWindow);
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
ImGui_ImplOpenGL3_Shutdown();
|
||||
ImGui_ImplGlfw_Shutdown();
|
||||
ImGui::DestroyContext();
|
||||
|
||||
glfwDestroyWindow(gWindow);
|
||||
glfwTerminate();
|
||||
return true;
|
||||
}
|
||||
|
||||
void HALSimGui::Exit(void*) { gExit = true; }
|
||||
void HALSimGui::Main(void*) { gui::Main(); }
|
||||
|
||||
void HALSimGui::Exit(void*) { gui::Exit(); }
|
||||
|
||||
extern "C" {
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@ extern "C" {
|
||||
__declspec(dllexport)
|
||||
#endif
|
||||
int HALSIM_InitExtension(void) {
|
||||
HALSimGui::GlobalInit();
|
||||
HALSimGui::Add(AccelerometerGui::Initialize);
|
||||
HALSimGui::Add(AddressableLEDGui::Initialize);
|
||||
HALSimGui::Add(AnalogGyroGui::Initialize);
|
||||
|
||||
@@ -7,15 +7,10 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <GL/gl3w.h>
|
||||
#include <imgui.h>
|
||||
#include <wpi/Twine.h>
|
||||
|
||||
namespace halsimgui {
|
||||
|
||||
bool LoadTextureFromFile(const wpi::Twine& filename, GLuint* out_texture,
|
||||
int* out_width, int* out_height);
|
||||
|
||||
// get distance^2 between two ImVec's
|
||||
inline float GetDistSquared(const ImVec2& a, const ImVec2& b) {
|
||||
float deltaX = b.x - a.x;
|
||||
|
||||
@@ -34,6 +34,7 @@ namespace halsimgui {
|
||||
|
||||
class HALSimGui {
|
||||
public:
|
||||
static void GlobalInit();
|
||||
static bool Initialize();
|
||||
static void Main(void*);
|
||||
static void Exit(void*);
|
||||
|
||||
Reference in New Issue
Block a user