Files
StarPilot/openpilot/tools/cabana/ui/app.cc
T
Trey Moen 44914a7cf4 cabana: remove custom focus-loss handling (#38884)
* cabana: fix focus handling across docked and floating windows

* cabana: remove custom focus-loss handling
2026-09-12 22:45:32 -07:00

204 lines
6.0 KiB
C++

#include "tools/cabana/ui/app.h"
#include <atomic>
#include <chrono>
#include <cstdio>
#include <stdexcept>
#include <thread>
#include <utility>
#include "imgui.h"
#include "imgui_impl_glfw.h"
#include "imgui_impl_opengl3.h"
#include "imgui_impl_opengl3_loader.h"
#include "implot.h"
#include <GLFW/glfw3.h>
#include "tools/cabana/settings.h"
#include "tools/cabana/ui/inistate.h"
#include "tools/cabana/ui/util.h"
#include "tools/cabana/ui/mainwin.h"
#include "tools/cabana/utils/util.h"
namespace {
std::atomic<bool> g_signal_exit{false};
std::vector<KeyEvent> g_key_events;
void keyCallback(GLFWwindow *window, int key, int scancode, int action, int mods) {
ImGui_ImplGlfw_KeyCallback(window, key, scancode, action, mods);
if (action == GLFW_PRESS) g_key_events.push_back({key, mods});
}
void hookViewportCallbacks() {
for (ImGuiViewport *viewport : ImGui::GetPlatformIO().Viewports) {
if (viewport->PlatformHandle == nullptr || viewport == ImGui::GetMainViewport()) continue;
glfwSetKeyCallback((GLFWwindow *)viewport->PlatformHandle, keyCallback);
}
}
void glfwErrorCallback(int error, const char *description) {
fprintf(stderr, "GLFW error %d: %s\n", error, description);
}
void paceFrame() {
using clock = std::chrono::steady_clock;
static clock::duration period = [] {
const GLFWvidmode *mode = glfwGetVideoMode(glfwGetPrimaryMonitor());
int hz = (mode != nullptr && mode->refreshRate > 0) ? mode->refreshRate : 60;
return std::chrono::duration_cast<clock::duration>(std::chrono::duration<double>(1.0 / hz));
}();
static clock::time_point next = clock::now();
next += period;
auto now = clock::now();
if (next < now) {
next = now; // fell behind (slow frame or hidden window): don't try to catch up
return;
}
std::this_thread::sleep_until(next);
}
void renderFrame(GLFWwindow *window, MainWindow *win) {
glfwPollEvents();
utils::drainMainThreadQueue();
int fb_w = 0, fb_h = 0;
glfwGetFramebufferSize(window, &fb_w, &fb_h);
ImGui_ImplOpenGL3_NewFrame();
ImGui_ImplGlfw_NewFrame();
ImGui::NewFrame();
win->draw();
ImGui::Render();
const ImVec4 &bg = ImGui::GetStyle().Colors[ImGuiCol_WindowBg];
glViewport(0, 0, fb_w, fb_h);
glClearColor(bg.x, bg.y, bg.z, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
if (ImGui::GetIO().ConfigFlags & ImGuiConfigFlags_ViewportsEnable) {
GLFWwindow *backup_context = glfwGetCurrentContext();
ImGui::UpdatePlatformWindows();
hookViewportCallbacks();
ImGui::RenderPlatformWindowsDefault();
glfwMakeContextCurrent(backup_context);
}
glfwSwapBuffers(window);
paceFrame();
}
class GlfwRuntime {
public:
GlfwRuntime() {
glfwSetErrorCallback(glfwErrorCallback);
#ifdef __APPLE__
setMacAppName("Cabana");
#endif
if (!glfwInit()) throw std::runtime_error("glfwInit failed");
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
#ifdef __APPLE__
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GLFW_TRUE);
#endif
// Restore geometry and render the initial layout before mapping the window.
glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE);
window_ = glfwCreateWindow(1600, 900, "Cabana", nullptr, nullptr);
if (window_ == nullptr) {
glfwTerminate();
throw std::runtime_error("glfwCreateWindow failed");
}
glfwMakeContextCurrent(window_);
glfwSwapInterval(0);
}
~GlfwRuntime() {
if (window_ != nullptr) glfwDestroyWindow(window_);
glfwTerminate();
}
GlfwRuntime(const GlfwRuntime &) = delete;
GlfwRuntime &operator=(const GlfwRuntime &) = delete;
GLFWwindow *window() const { return window_; }
private:
GLFWwindow *window_ = nullptr;
};
class ImGuiRuntime {
public:
explicit ImGuiRuntime(GLFWwindow *window) {
IMGUI_CHECKVERSION();
ImGui::CreateContext();
ImPlot::CreateContext();
ImGuiIO &io = ImGui::GetIO();
io.ConfigFlags |= ImGuiConfigFlags_DockingEnable;
io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable;
io.ConfigViewportsNoDecoration = true;
io.ConfigDockingTransparentPayload = true;
io.IniFilename = nullptr;
io.LogFilename = nullptr;
if (!ImGui_ImplGlfw_InitForOpenGL(window, true)) {
ImPlot::DestroyContext();
ImGui::DestroyContext();
throw std::runtime_error("ImGui_ImplGlfw_InitForOpenGL failed");
}
glfwSetKeyCallback(window, keyCallback);
if (!ImGui_ImplOpenGL3_Init("#version 330")) {
ImGui_ImplGlfw_Shutdown();
ImPlot::DestroyContext();
ImGui::DestroyContext();
throw std::runtime_error("ImGui_ImplOpenGL3_Init failed");
}
}
~ImGuiRuntime() {
ImGui_ImplOpenGL3_Shutdown();
ImGui_ImplGlfw_Shutdown();
ImPlot::DestroyContext();
ImGui::DestroyContext();
}
ImGuiRuntime(const ImGuiRuntime &) = delete;
ImGuiRuntime &operator=(const ImGuiRuntime &) = delete;
};
} // namespace
std::vector<KeyEvent> takeKeyEvents() {
return std::exchange(g_key_events, {});
}
int run(std::unique_ptr<AbstractStream> stream, StreamLoader stream_loader, const std::string &dbc_file) {
try {
// SIGINT/SIGTERM close all windows (which may ask about unsaved changes), then exit
UnixSignalHandler signal_handler([]() { g_signal_exit = true; });
GlfwRuntime glfw;
ImGuiRuntime imgui(glfw.window());
loadFonts();
applyTheme(settings.theme);
inistate::addSettingsHandler();
inistate::load();
inistate::applyWindowGeometry(glfw.window());
MainWindow win(glfw.window(), std::move(stream), std::move(stream_loader), dbc_file);
renderFrame(glfw.window(), &win);
glfwShowWindow(glfw.window());
while (!win.exited()) {
if (g_signal_exit.exchange(false)) {
printf("\nexiting...\n");
win.close();
} else if (glfwWindowShouldClose(glfw.window())) {
glfwSetWindowShouldClose(glfw.window(), GLFW_FALSE);
win.close();
}
renderFrame(glfw.window(), &win);
}
return 0;
} catch (const std::exception &e) {
fprintf(stderr, "%s\n", e.what());
return 1;
}
}