mirror of
https://github.com/wpilibsuite/allwpilib
synced 2026-06-20 00:51:42 +00:00
Sim GUI: Support High DPI monitors
Add user setting for scaling on top of DPI scaling. Add user setting for visual style (light/dark/normal). Save window position, size, maximized state, scale, and style to ini file.
This commit is contained in:
@@ -56,7 +56,7 @@ static void DisplayDIO() {
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::PushItemWidth(100);
|
||||
ImGui::PushItemWidth(ImGui::GetFontSize() * 8);
|
||||
for (int i = 0; i < numDIO; ++i) {
|
||||
if (HALSIM_GetDIOInitialized(i)) {
|
||||
hasAny = true;
|
||||
|
||||
@@ -351,7 +351,7 @@ static void DisplayFMS() {
|
||||
static const char* stations[] = {"Red 1", "Red 2", "Red 3",
|
||||
"Blue 1", "Blue 2", "Blue 3"};
|
||||
int allianceStationId = HALSIM_GetDriverStationAllianceStationId();
|
||||
ImGui::SetNextItemWidth(100);
|
||||
ImGui::SetNextItemWidth(ImGui::GetFontSize() * 8);
|
||||
if (ImGui::Combo("Alliance Station", &allianceStationId, stations, 6))
|
||||
HALSIM_SetDriverStationAllianceStationId(
|
||||
static_cast<HAL_AllianceStationID>(allianceStationId));
|
||||
@@ -362,7 +362,7 @@ static void DisplayFMS() {
|
||||
|
||||
static double startMatchTime = 0.0;
|
||||
double matchTime = HALSIM_GetDriverStationMatchTime();
|
||||
ImGui::SetNextItemWidth(100);
|
||||
ImGui::SetNextItemWidth(ImGui::GetFontSize() * 8);
|
||||
if (ImGui::InputDouble("Match Time", &matchTime, 0, 0, "%.1f",
|
||||
ImGuiInputTextFlags_EnterReturnsTrue)) {
|
||||
HALSIM_SetDriverStationMatchTime(matchTime);
|
||||
@@ -380,7 +380,7 @@ static void DisplayFMS() {
|
||||
|
||||
// Game Specific Message
|
||||
static HAL_MatchInfo matchInfo;
|
||||
ImGui::SetNextItemWidth(100);
|
||||
ImGui::SetNextItemWidth(ImGui::GetFontSize() * 8);
|
||||
if (ImGui::InputText("Game Specific",
|
||||
reinterpret_cast<char*>(matchInfo.gameSpecificMessage),
|
||||
sizeof(matchInfo.gameSpecificMessage),
|
||||
@@ -420,7 +420,7 @@ static void DisplaySystemJoysticks() {
|
||||
|
||||
static void DisplayJoysticks() {
|
||||
// imgui doesn't size columns properly with autoresize, so force it
|
||||
ImGui::Dummy(ImVec2(14.0 * 9 * HAL_kMaxJoysticks, 0));
|
||||
ImGui::Dummy(ImVec2(ImGui::GetFontSize() * 10 * HAL_kMaxJoysticks, 0));
|
||||
|
||||
ImGui::Columns(HAL_kMaxJoysticks, "Joysticks", false);
|
||||
for (int i = 0; i < HAL_kMaxJoysticks; ++i) {
|
||||
|
||||
@@ -54,7 +54,7 @@ static void EncodersWriteAll(ImGuiContext* ctx, ImGuiSettingsHandler* handler,
|
||||
static void DisplayEncoders() {
|
||||
bool hasAny = false;
|
||||
static int numEncoder = HAL_GetNumEncoders();
|
||||
ImGui::PushItemWidth(100);
|
||||
ImGui::PushItemWidth(ImGui::GetFontSize() * 8);
|
||||
for (int i = 0; i < numEncoder; ++i) {
|
||||
if (HALSIM_GetEncoderInitialized(i)) {
|
||||
hasAny = true;
|
||||
|
||||
@@ -12,6 +12,8 @@ namespace halsimgui {
|
||||
void DrawLEDs(int* values, int numValues, int cols, const ImU32* colors,
|
||||
float size, float spacing) {
|
||||
if (numValues == 0) return;
|
||||
if (size == 0) size = ImGui::GetFontSize() / 2.0;
|
||||
if (spacing == 0) spacing = ImGui::GetFontSize() / 3.0;
|
||||
|
||||
ImDrawList* drawList = ImGui::GetWindowDrawList();
|
||||
const ImVec2 p = ImGui::GetCursorScreenPos();
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include <GL/gl3w.h>
|
||||
#include <GLFW/glfw3.h>
|
||||
#include <imgui.h>
|
||||
#include <imgui_ProggyDotted.h>
|
||||
#include <imgui_impl_glfw.h>
|
||||
#include <imgui_impl_opengl3.h>
|
||||
#include <imgui_internal.h>
|
||||
@@ -43,6 +44,12 @@ struct WindowInfo {
|
||||
|
||||
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;
|
||||
@@ -50,15 +57,39 @@ 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 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;
|
||||
|
||||
int index = gWindowMap.try_emplace(name, gWindows.size()).first->second;
|
||||
if (index == static_cast<int>(gWindows.size())) {
|
||||
gSortedWindows.push_back(index);
|
||||
@@ -71,11 +102,35 @@ static void* SimWindowsReadOpen(ImGuiContext* ctx,
|
||||
|
||||
static void SimWindowsReadLine(ImGuiContext* ctx, ImGuiSettingsHandler* handler,
|
||||
void* entry, const char* lineStr) {
|
||||
auto element = static_cast<WindowInfo*>(entry);
|
||||
wpi::StringRef line{lineStr};
|
||||
auto [name, value] = line.split('=');
|
||||
name = name.trim();
|
||||
value = value.trim();
|
||||
|
||||
if (entry == &gWindow) {
|
||||
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;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
auto element = static_cast<WindowInfo*>(entry);
|
||||
if (name == "visible") {
|
||||
int num;
|
||||
if (value.getAsInteger(10, num)) return;
|
||||
@@ -89,12 +144,31 @@ 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\n\n",
|
||||
gWindowWidth, gWindowHeight, gWindowMaximized, gWindowXPos, gWindowYPos,
|
||||
gUserScale, gStyle);
|
||||
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));
|
||||
}
|
||||
@@ -184,6 +258,7 @@ bool HALSimGui::Initialize() {
|
||||
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";
|
||||
@@ -193,10 +268,52 @@ bool HALSimGui::Initialize() {
|
||||
// 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 (!gWindowLoadedWidthHeight)
|
||||
glfwWindowHint(GLFW_SCALE_TO_MONITOR, GLFW_TRUE);
|
||||
if (gWindowXPos != -1 && gWindowYPos != -1)
|
||||
glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE);
|
||||
|
||||
// Create window with graphics context
|
||||
gWindow =
|
||||
glfwCreateWindow(1280, 720, "Robot Simulation GUI", nullptr, nullptr);
|
||||
gWindow = glfwCreateWindow(gWindowWidth, gWindowHeight,
|
||||
"Robot Simulation GUI", nullptr, nullptr);
|
||||
if (!gWindow) return false;
|
||||
|
||||
// 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
|
||||
|
||||
@@ -206,15 +323,8 @@ bool HALSimGui::Initialize() {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Setup Dear ImGui context
|
||||
IMGUI_CHECKVERSION();
|
||||
ImGui::CreateContext();
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
(void)io;
|
||||
|
||||
// Setup Dear ImGui style
|
||||
// ImGui::StyleColorsDark();
|
||||
ImGui::StyleColorsClassic();
|
||||
UpdateStyle();
|
||||
|
||||
// Setup Platform/Renderer bindings
|
||||
ImGui_ImplGlfw_InitForOpenGL(gWindow, true);
|
||||
@@ -244,17 +354,14 @@ bool HALSimGui::Initialize() {
|
||||
// io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 18.0f,
|
||||
// NULL, io.Fonts->GetGlyphRangesJapanese()); IM_ASSERT(font != NULL);
|
||||
|
||||
// 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();
|
||||
// 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;
|
||||
@@ -273,6 +380,16 @@ void HALSimGui::Main(void*) {
|
||||
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();
|
||||
}
|
||||
@@ -287,6 +404,42 @@ 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();
|
||||
}
|
||||
|
||||
if (ImGui::BeginMenu("Window")) {
|
||||
for (auto&& windowIndex : gSortedWindows) {
|
||||
auto& window = gWindows[windowIndex];
|
||||
|
||||
@@ -21,7 +21,7 @@ static void DisplayPDP() {
|
||||
bool hasAny = false;
|
||||
static int numPDP = HAL_GetNumPDPModules();
|
||||
static int numChannels = HAL_GetNumPDPChannels();
|
||||
ImGui::PushItemWidth(150);
|
||||
ImGui::PushItemWidth(ImGui::GetFontSize() * 13);
|
||||
for (int i = 0; i < numPDP; ++i) {
|
||||
if (HALSIM_GetPDPInitialized(i)) {
|
||||
hasAny = true;
|
||||
|
||||
@@ -18,7 +18,7 @@ static void DisplayRoboRio() {
|
||||
ImGui::Button("User Button");
|
||||
HALSIM_SetRoboRioFPGAButton(0, ImGui::IsItemActive());
|
||||
|
||||
ImGui::PushItemWidth(100);
|
||||
ImGui::PushItemWidth(ImGui::GetFontSize() * 8);
|
||||
|
||||
if (ImGui::CollapsingHeader("RoboRIO Input")) {
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user