Files
imhex/main/gui/source/window/window.cpp
paxcut 5c4cf7379f feat: Added Subpixel Font rendering (#2092)
Proof of concept for implementing subpixel processing in ImGui. This is
work in progress, and it is bound to have problems.

What it does:
1) Uses freetype own subpixel processing implementation to build a
32-bit color atlas for the default font only (no icons, no unifont) . 2)
Avoids pixel perfect font conversion when possible. 3) Self contained,
no ImGui source code changes.
4) Results in much improved legibility of fonts rendered on low dpi LCD
screens that use horizontal RGB pixel layouts (no BRG or OLED or CRT if
they even exist anymore)

What it doesn't:
1) Fancy class based interface. The code is barely the minimum needed to
show it can work. 2) Dual source color blending. That needs to be
implemented in shader code, so it needs to change ImGui source code
although minimally. This will result in some characters appearing dimmer
than others. Easily fixed with small fragment and vertex shaders. 3)
subpixel positioning. If characters are very thin they will look
colored, or they can be moved to improve legibility. 4) deal with
detection of fringe cases including rare pixel layouts, non LCD screens,
Mac-OS not handling subpixel rendering and any other deviation from the
standard LCD. 5) tries to be efficient in speed or memory use. Font
Atlases will be 4 times the size they were before, but there are no
noticeable delays in font loading in the examples I have tried.

Any comments and code improvements are welcome.

---------

Co-authored-by: Nik <werwolv98@gmail.com>
2025-05-11 15:36:32 +02:00

1366 lines
54 KiB
C++

#include "window.hpp"
#include <hex.hpp>
#include <hex/api/plugin_manager.hpp>
#include <hex/api/content_registry.hpp>
#include <hex/api/imhex_api.hpp>
#include <hex/api/layout_manager.hpp>
#include <hex/api/shortcut_manager.hpp>
#include <hex/api/workspace_manager.hpp>
#include <hex/api/tutorial_manager.hpp>
#include <hex/api/events/requests_lifecycle.hpp>
#include <hex/api/events/events_lifecycle.hpp>
#include <hex/api/events/requests_gui.hpp>
#include <hex/api/events/events_gui.hpp>
#include <hex/helpers/utils.hpp>
#include <hex/helpers/logger.hpp>
#include <hex/helpers/default_paths.hpp>
#include <hex/ui/view.hpp>
#include <hex/ui/popup.hpp>
#include <hex/ui/banner.hpp>
#include <chrono>
#include <csignal>
#include <numbers>
#include <romfs/romfs.hpp>
#include <imgui.h>
#include <imgui_internal.h>
#include <imgui_impl_glfw.h>
#include <imgui_impl_opengl3.h>
#include <hex/ui/imgui_imhex_extensions.h>
#include <implot.h>
#include <implot_internal.h>
#include <implot3d.h>
#include <implot3d_internal.h>
#include <imnodes.h>
#include <imnodes_internal.h>
#include <wolv/utils/string.hpp>
#include <GLFW/glfw3.h>
#include <hex/ui/toast.hpp>
#include <wolv/utils/guards.hpp>
#include <fmt/printf.h>
#include <fmt/chrono.h>
#include <hex/helpers/opengl.hpp>
namespace hex {
using namespace std::literals::chrono_literals;
Window::Window() {
const static auto openEmergencyPopup = [this](const std::string &title){
TaskManager::doLater([this, title] {
for (const auto &provider : ImHexApi::Provider::getProviders())
ImHexApi::Provider::remove(provider, false);
ImGui::OpenPopup(title.c_str());
m_emergencyPopupOpen = true;
});
};
// Handle fatal error popups for errors detected during initialization
{
for (const auto &[argument, value] : ImHexApi::System::getInitArguments()) {
if (argument == "no-plugins") {
openEmergencyPopup("No Plugins");
} else if (argument == "duplicate-plugins") {
openEmergencyPopup("Duplicate Plugins loaded");
}
}
}
// Initialize the window
this->initGLFW();
this->initImGui();
this->setupNativeWindow();
this->registerEventHandlers();
#if !defined(OS_WEB)
this->loadPostProcessingShader();
#endif
ContentRegistry::Settings::impl::store();
ContentRegistry::Settings::impl::load();
EventWindowInitialized::post();
EventImHexStartupFinished::post();
RequestStartMigration::post();
TutorialManager::init();
#if defined(OS_MACOS)
ShortcutManager::enableMacOSMode();
#endif
}
Window::~Window() {
m_frameRateThread.request_stop();
m_frameRateThread.join();
EventProviderDeleted::unsubscribe(this);
RequestCloseImHex::unsubscribe(this);
RequestUpdateWindowTitle::unsubscribe(this);
EventAbnormalTermination::unsubscribe(this);
RequestOpenPopup::unsubscribe(this);
EventWindowDeinitializing::post(m_window);
ContentRegistry::Settings::impl::store();
this->exitImGui();
this->exitGLFW();
}
void Window::registerEventHandlers() {
// Initialize default theme
RequestChangeTheme::post("Dark");
// Handle the close window request by telling GLFW to shut down
RequestCloseImHex::subscribe(this, [this](bool noQuestions) {
glfwSetWindowShouldClose(m_window, GLFW_TRUE);
if (!noQuestions)
EventWindowClosing::post(m_window);
});
// Handle opening popups
RequestOpenPopup::subscribe(this, [this](auto name) {
std::scoped_lock lock(m_popupMutex);
m_popupsToOpen.push_back(name);
});
EventDPIChanged::subscribe([this](float oldScaling, float newScaling) {
if (oldScaling == newScaling || oldScaling == 0 || newScaling == 0)
return;
int width, height;
glfwGetWindowSize(m_window, &width, &height);
width = float(width) * newScaling / oldScaling;
height = float(height) * newScaling / oldScaling;
ImHexApi::System::impl::setMainWindowSize(width, height);
glfwSetWindowSize(m_window, width, height);
});
LayoutManager::registerLoadCallback([this](std::string_view line) {
int width = 0, height = 0;
sscanf(line.data(), "MainWindowSize=%d,%d", &width, &height);
if (width > 0 && height > 0) {
TaskManager::doLater([width, height, this]{
glfwSetWindowSize(m_window, width, height);
});
}
});
}
void Window::loadPostProcessingShader() {
for (const auto &folder : paths::Resources.all()) {
auto vertexShaderPath = folder / "shader.vert";
auto fragmentShaderPath = folder / "shader.frag";
if (!wolv::io::fs::exists(vertexShaderPath))
continue;
if (!wolv::io::fs::exists(fragmentShaderPath))
continue;
auto vertexShaderFile = wolv::io::File(vertexShaderPath, wolv::io::File::Mode::Read);
if (!vertexShaderFile.isValid())
continue;
auto fragmentShaderFile = wolv::io::File(fragmentShaderPath, wolv::io::File::Mode::Read);
if (!fragmentShaderFile.isValid())
continue;
const auto vertexShaderSource = vertexShaderFile.readString();
const auto fragmentShaderSource = fragmentShaderFile.readString();
m_postProcessingShader = gl::Shader(vertexShaderSource, fragmentShaderSource);
if (!m_postProcessingShader.isValid())
continue;
break;
}
}
void handleException() {
try {
throw;
} catch (const std::exception &e) {
log::fatal("Unhandled exception: {}", e.what());
EventCrashRecovered::post(e);
} catch (...) {
log::fatal("Unhandled exception: Unknown exception");
}
}
void errorRecoverLogCallback(void*, const char* fmt, ...) {
va_list args;
std::string message;
va_start(args, fmt);
message.resize(std::vsnprintf(nullptr, 0, fmt, args));
va_end(args);
va_start(args, fmt);
std::vsnprintf(message.data(), message.size(), fmt, args);
va_end(args);
message.resize(message.size() - 1);
log::error("{}", message);
}
void Window::fullFrame() {
[[maybe_unused]] static u32 crashWatchdog = 0;
if (auto g = ImGui::GetCurrentContext(); g == nullptr || g->WithinFrameScope) {
return;
}
#if !defined(DEBUG)
try {
#endif
// Render an entire frame
this->frameBegin();
this->frame();
this->frameEnd();
#if !defined(DEBUG)
// Feed the watchdog
crashWatchdog = 0;
} catch (...) {
// If an exception keeps being thrown, abort the application after 10 frames
// This is done to avoid the application getting stuck in an infinite loop of exceptions
crashWatchdog += 1;
if (crashWatchdog > 10) {
log::fatal("Crash watchdog triggered, aborting");
std::abort();
}
// Try to recover from the exception by bringing ImGui back into a working state
ImGui::EndFrame();
ImGui::UpdatePlatformWindows();
// Handle the exception
handleException();
}
#endif
}
void Window::loop() {
glfwShowWindow(m_window);
while (!glfwWindowShouldClose(m_window)) {
m_lastStartFrameTime = glfwGetTime();
{
int x = 0, y = 0;
int width = 0, height = 0;
glfwGetWindowPos(m_window, &x, &y);
glfwGetWindowSize(m_window, &width, &height);
ImHexApi::System::impl::setMainWindowPosition(x, y);
ImHexApi::System::impl::setMainWindowSize(width, height);
}
if (!glfwGetWindowAttrib(m_window, GLFW_VISIBLE) || glfwGetWindowAttrib(m_window, GLFW_ICONIFIED)) {
// If the application is minimized or not visible, don't render anything
glfwWaitEvents();
}
m_lastStartFrameTime = glfwGetTime();
static ImVec2 lastWindowSize = ImHexApi::System::getMainWindowSize();
if (ImHexApi::System::impl::isWindowResizable()) {
glfwSetWindowSizeLimits(m_window, 480_scaled, 360_scaled, GLFW_DONT_CARE, GLFW_DONT_CARE);
lastWindowSize = ImHexApi::System::getMainWindowSize();
} else {
glfwSetWindowSizeLimits(m_window, lastWindowSize.x, lastWindowSize.y, lastWindowSize.x, lastWindowSize.y);
}
this->fullFrame();
ImHexApi::System::impl::setLastFrameTime(glfwGetTime() - m_lastStartFrameTime);
{
while (true) {
glfwPollEvents();
if (ImHexApi::System::getTargetFPS() >= 200)
break;
{
std::unique_lock lock(m_sleepMutex);
m_sleepCondVar.wait(lock);
if (m_sleepFlag.exchange(false))
break;
}
}
}
m_lastFrameTime = glfwGetTime() - m_lastStartFrameTime;
// Unlock frame rate if any mouse button is being held down to allow drag scrolling to be smooth
if (ImGui::IsAnyMouseDown())
this->unlockFrameRate();
// Unlock frame rate if any modifier key is held down since they don't generate key repeat events
if (
ImGui::IsKeyPressed(ImGuiKey_LeftCtrl) || ImGui::IsKeyPressed(ImGuiKey_RightCtrl) ||
ImGui::IsKeyPressed(ImGuiKey_LeftShift) || ImGui::IsKeyPressed(ImGuiKey_RightShift) ||
ImGui::IsKeyPressed(ImGuiKey_LeftSuper) || ImGui::IsKeyPressed(ImGuiKey_RightSuper) ||
ImGui::IsKeyPressed(ImGuiKey_LeftAlt) || ImGui::IsKeyPressed(ImGuiKey_RightAlt)
) {
this->unlockFrameRate();
}
// Unlock frame rate if there's more than one viewport since these don't call the glfw callbacks registered here
if (ImGui::GetPlatformIO().Viewports.size() > 1)
this->unlockFrameRate();
// Unlock frame rate if there's any task running that shows a loading animation
if (TaskManager::getRunningTaskCount() > 0 || TaskManager::getRunningBlockingTaskCount() > 0) {
this->unlockFrameRate();
}
}
// Hide the window as soon as the render loop exits to make the window
// disappear as soon as it's closed
glfwHideWindow(m_window);
}
void Window::frameBegin() {
// Run all deferred calls
TaskManager::runDeferredCalls();
ImGui_ImplOpenGL3_NewFrame();
ImGui_ImplGlfw_NewFrame();
// Create font textures if necessary
{
const auto &fontDefinitions = ImHexApi::Fonts::impl::getFontDefinitions();
auto &currentFont = ImGui::GetIO().Fonts;
for (const auto &[name, font] : fontDefinitions) {
// If the texture for this atlas has been built already, don't do it again
if (font == nullptr || font->ContainerAtlas == nullptr || font->ContainerAtlas->TexID != 0)
continue;
currentFont = font->ContainerAtlas;
ImGui_ImplOpenGL3_CreateFontsTexture();
currentFont->ClearInputData();
currentFont->ClearTexData();
}
{
auto font = ImHexApi::Fonts::getFont("hex.fonts.font.default");
if (font == nullptr) {
const auto &io = ImGui::GetIO();
io.Fonts->Clear();
ImFontConfig cfg;
cfg.OversampleH = cfg.OversampleV = 1, cfg.PixelSnapH = true;
cfg.SizePixels = ImHexApi::Fonts::DefaultFontSize;
font = io.Fonts->AddFontDefault(&cfg);
ImGui_ImplOpenGL3_CreateFontsTexture();
io.Fonts->ClearInputData();
io.Fonts->ClearTexData();
} else {
currentFont = font->ContainerAtlas;
}
ImGui::SetCurrentFont(font);
}
}
// Start new ImGui Frame
ImGui::NewFrame();
TutorialManager::drawTutorial();
EventFrameBegin::post();
// Handle all undocked floating windows
ImGuiViewport *viewport = ImGui::GetMainViewport();
ImGui::SetNextWindowPos(viewport->WorkPos);
ImGui::SetNextWindowSize(ImHexApi::System::getMainWindowSize() - ImVec2(0, ImGui::GetTextLineHeightWithSpacing()));
ImGui::SetNextWindowViewport(viewport->ID);
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0F);
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0F);
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0));
ImGuiWindowFlags windowFlags = ImGuiWindowFlags_NoDocking | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoNavFocus | ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse;
if (!m_emergencyPopupOpen)
windowFlags |= ImGuiWindowFlags_MenuBar;
// Render main dock space
if (ImGui::Begin("ImHexDockSpace", nullptr, windowFlags)) {
ImGui::PopStyleVar();
this->beginNativeWindowFrame();
} else {
ImGui::PopStyleVar();
}
ImGui::End();
ImGui::PopStyleVar(2);
// Plugin load error popups
// These are not translated because they should always be readable, no matter if any localization could be loaded or not
{
const static auto drawPluginFolderTable = [] {
ImGuiExt::UnderlinedText("Plugin folders");
if (ImGui::BeginTable("plugins", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_ScrollY | ImGuiTableFlags_SizingFixedFit, ImVec2(0, 100_scaled))) {
ImGui::TableSetupScrollFreeze(0, 1);
ImGui::TableSetupColumn("Path", ImGuiTableColumnFlags_WidthStretch, 0.2F);
ImGui::TableSetupColumn("Exists", ImGuiTableColumnFlags_WidthFixed, ImGui::GetTextLineHeight() * 3);
ImGui::TableHeadersRow();
for (const auto &path : paths::Plugins.all()) {
const auto filePath = path / "builtin.hexplug";
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::TextUnformatted(wolv::util::toUTF8String(filePath).c_str());
ImGui::TableNextColumn();
ImGui::TextUnformatted(wolv::io::fs::exists(filePath) ? "Yes" : "No");
}
ImGui::EndTable();
}
};
if (m_emergencyPopupOpen) {
const auto pos = ImHexApi::System::getMainWindowPosition();
const auto size = ImHexApi::System::getMainWindowSize();
ImGui::GetBackgroundDrawList()->AddRectFilled(pos, pos + size, ImGui::GetColorU32(ImGuiCol_WindowBg) | 0xFF000000);
}
ImGui::PushStyleColor(ImGuiCol_ModalWindowDimBg, 0x00);
ON_SCOPE_EXIT { ImGui::PopStyleColor(); };
// No plugins error popup
ImGui::SetNextWindowPos(ImGui::GetMainViewport()->GetCenter(), ImGuiCond_Always, ImVec2(0.5F, 0.5F));
if (ImGui::BeginPopupModal("No Plugins", nullptr, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoTitleBar)) {
ImGui::BringWindowToDisplayFront(ImGui::GetCurrentWindowRead());
ImGui::TextUnformatted("No ImHex plugins loaded (including the built-in plugin)!");
ImGui::TextUnformatted("Make sure you installed ImHex correctly.");
ImGui::TextUnformatted("There should be at least a 'builtin.hexplug' file in your plugins folder.");
ImGui::NewLine();
drawPluginFolderTable();
ImGui::NewLine();
if (ImGuiExt::DimmedButton("Close ImHex", ImVec2(ImGui::GetContentRegionAvail().x, 0)))
ImHexApi::System::closeImHex(true);
ImGui::EndPopup();
}
// Duplicate plugins error popup
ImGui::SetNextWindowPos(ImGui::GetMainViewport()->GetCenter(), ImGuiCond_Always, ImVec2(0.5F, 0.5F));
if (ImGui::BeginPopupModal("Duplicate Plugins loaded", nullptr, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoTitleBar)) {
ImGui::BringWindowToDisplayFront(ImGui::GetCurrentWindowRead());
ImGui::TextUnformatted("ImHex found and attempted to load multiple plugins with the same name!");
ImGui::TextUnformatted("Make sure you installed ImHex correctly and, if needed,");
ImGui::TextUnformatted("cleaned up older installations correctly.");
ImGui::TextUnformatted("Each plugin should only ever be loaded once.");
ImGui::NewLine();
drawPluginFolderTable();
ImGui::NewLine();
if (ImGuiExt::DimmedButton("Close ImHex", ImVec2(ImGui::GetContentRegionAvail().x, 0)))
ImHexApi::System::closeImHex(true);
ImGui::EndPopup();
}
}
// Open popups when plugins requested it
// We retry every frame until the popup actually opens
// It might not open the first time because another popup is already open
{
std::scoped_lock lock(m_popupMutex);
m_popupsToOpen.remove_if([](const auto &name) {
if (ImGui::IsPopupOpen(name.c_str()))
return true;
else
ImGui::OpenPopup(name.c_str());
return false;
});
}
// Draw popup stack
{
static bool positionSet = false;
static bool sizeSet = false;
static double popupDelay = -2.0;
static u32 displayFrameCount = 0;
static std::unique_ptr<impl::PopupBase> currPopup;
static Lang name("");
AT_FIRST_TIME {
EventImHexClosing::subscribe([] {
currPopup.reset();
});
};
if (auto &popups = impl::PopupBase::getOpenPopups(); !popups.empty()) {
if (!ImGui::IsPopupOpen(ImGuiID(0), ImGuiPopupFlags_AnyPopupId)) {
if (popupDelay <= -1.0) {
popupDelay = 0.2;
} else {
popupDelay -= m_lastFrameTime;
if (popupDelay < 0 || popups.size() == 1) {
popupDelay = -2.0;
currPopup = std::move(popups.back());
name = Lang(currPopup->getUnlocalizedName());
displayFrameCount = 0;
ImGui::OpenPopup(name);
popups.pop_back();
}
}
}
}
if (currPopup != nullptr) {
bool open = true;
const auto &minSize = currPopup->getMinSize();
const auto &maxSize = currPopup->getMaxSize();
const bool hasConstraints = minSize.x != 0 && minSize.y != 0 && maxSize.x != 0 && maxSize.y != 0;
if (hasConstraints)
ImGui::SetNextWindowSizeConstraints(minSize, maxSize);
else
ImGui::SetNextWindowSize(ImVec2(0, 0), ImGuiCond_Appearing);
auto* closeButton = currPopup->hasCloseButton() ? &open : nullptr;
const auto flags = currPopup->getFlags() | (!hasConstraints ? (ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize) : ImGuiWindowFlags_None);
if (!positionSet) {
ImGui::SetNextWindowPos(ImHexApi::System::getMainWindowPosition() + (ImHexApi::System::getMainWindowSize() / 2.0F), ImGuiCond_Always, ImVec2(0.5F, 0.5F));
if (sizeSet)
positionSet = true;
}
const auto createPopup = [&](bool displaying) {
if (displaying) {
displayFrameCount += 1;
currPopup->drawContent();
if (ImGui::GetWindowSize().x > ImGui::GetStyle().FramePadding.x * 10)
sizeSet = true;
// Reset popup position if it's outside the main window when multi-viewport is not enabled
// If not done, the popup will be stuck outside the main window and cannot be accessed anymore
if ((ImGui::GetIO().ConfigFlags & ImGuiConfigFlags_ViewportsEnable) == ImGuiConfigFlags_None) {
const auto currWindowPos = ImGui::GetWindowPos();
const auto minWindowPos = ImHexApi::System::getMainWindowPosition() - ImGui::GetWindowSize();
const auto maxWindowPos = ImHexApi::System::getMainWindowPosition() + ImHexApi::System::getMainWindowSize();
if (currWindowPos.x > maxWindowPos.x || currWindowPos.y > maxWindowPos.y || currWindowPos.x < minWindowPos.x || currWindowPos.y < minWindowPos.y) {
positionSet = false;
ImGui::GetCurrentContext()->MovingWindow = nullptr;
}
}
ImGui::EndPopup();
}
};
std::string localizedName = name.get();
if (currPopup->isModal())
createPopup(ImGui::BeginPopupModal(localizedName.c_str(), closeButton, flags));
else
createPopup(ImGui::BeginPopup(localizedName.c_str(), flags));
if (!ImGui::IsPopupOpen(localizedName.c_str()) && displayFrameCount < 5) {
ImGui::OpenPopup(localizedName.c_str());
}
if (currPopup->shouldClose() || !open) {
log::debug("Closing popup '{}'", localizedName);
positionSet = sizeSet = false;
currPopup = nullptr;
}
}
}
// Draw Toasts
{
u32 index = 0;
for (const auto &toast : impl::ToastBase::getQueuedToasts() | std::views::take(4)) {
const auto toastHeight = 60_scaled;
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 5_scaled);
ImGui::SetNextWindowSize(ImVec2(350_scaled, toastHeight));
ImGui::SetNextWindowPos((ImHexApi::System::getMainWindowPosition() + ImHexApi::System::getMainWindowSize()) - scaled({ 10, 10 }) - scaled({ 0, (10 + toastHeight) * index }), ImGuiCond_Always, ImVec2(1, 1));
if (ImGui::Begin(hex::format("##Toast_{}", index).c_str(), nullptr, ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse | ImGuiWindowFlags_NoDocking | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoFocusOnAppearing)) {
auto drawList = ImGui::GetWindowDrawList();
const auto min = ImGui::GetWindowPos();
const auto max = min + ImGui::GetWindowSize();
drawList->PushClipRect(min, min + scaled({ 5, 60 }));
drawList->AddRectFilled(min, max, toast->getColor(), 5_scaled);
drawList->PopClipRect();
ImGui::Indent();
toast->draw();
ImGui::Unindent();
if (ImGui::IsWindowHovered() || toast->getAppearTime() <= 0)
toast->setAppearTime(ImGui::GetTime());
}
ImGui::End();
ImGui::PopStyleVar();
index += 1;
}
std::erase_if(impl::ToastBase::getQueuedToasts(), [](const auto &toast){
return toast->getAppearTime() > 0 && (toast->getAppearTime() + impl::ToastBase::VisibilityTime) < ImGui::GetTime();
});
}
// Draw Banners
{
const auto currentProvider = ImHexApi::Provider::get();
const bool onWelcomeScreen = currentProvider == nullptr || !currentProvider->isAvailable();
const auto windowPos = ImHexApi::System::getMainWindowPosition();
float startY = windowPos.y + ImGui::GetTextLineHeight() + ((ImGui::GetTextLineHeight() + (ImGui::GetStyle().FramePadding.y * 2.0F)) * (onWelcomeScreen ? 1 : 2));
const auto height = ImGui::GetTextLineHeightWithSpacing() * 1.5f;
// Offset banner based on the size of the title bar. On macOS it's slightly taller
#if defined(OS_MACOS)
startY += 2 * 8_scaled;
#else
startY += 2 * ImGui::GetStyle().FramePadding.y;
#endif
for (const auto &banner : impl::BannerBase::getOpenBanners() | std::views::take(5)) {
auto &style = ImGui::GetStyle();
ImGui::SetNextWindowPos(ImVec2(windowPos.x + 1_scaled, startY));
ImGui::SetNextWindowSize(ImVec2(ImHexApi::System::getMainWindowSize().x - 2_scaled, height));
ImGui::SetNextWindowViewport(viewport->ID);
ImGui::PushStyleColor(ImGuiCol_WindowBg, banner->getColor().Value);
auto prevShadowOffset = style.WindowShadowOffsetDist;
auto prevShadowAngle = style.WindowShadowOffsetAngle;
style.WindowShadowOffsetDist = 12_scaled;
style.WindowShadowOffsetAngle = 0.5F * std::numbers::pi_v<float>;
ON_SCOPE_EXIT {
style.WindowShadowOffsetDist = prevShadowOffset;
style.WindowShadowOffsetAngle = prevShadowAngle;
};
if (ImGui::Begin(fmt::format("##Banner{}", static_cast<void*>(banner.get())).c_str(), nullptr, ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse | ImGuiWindowFlags_NoDocking | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoFocusOnAppearing)) {
if (ImGui::BeginChild("##Content", ImGui::GetContentRegionAvail() - ImVec2(20_scaled, 0))) {
banner->draw();
}
ImGui::EndChild();
ImGui::SameLine();
if (ImGui::CloseButton(ImGui::GetID("BannerCloseButton"), ImGui::GetCursorScreenPos())) {
banner->close();
}
}
ImGui::End();
ImGui::PopStyleColor();
startY += height;
}
std::erase_if(impl::BannerBase::getOpenBanners(), [](const auto &banner) {
return banner->shouldClose();
});
}
}
void Window::frame() {
auto &io = ImGui::GetIO();
ShortcutManager::resetLastActivatedMenu();
// Loop through all views and draw them
for (auto &[name, view] : ContentRegistry::Views::impl::getEntries()) {
ImGui::GetCurrentContext()->NextWindowData.ClearFlags();
// Draw always visible views
view->drawAlwaysVisibleContent();
// Skip views that shouldn't be processed currently
if (!view->shouldProcess())
continue;
const auto openViewCount = std::ranges::count_if(ContentRegistry::Views::impl::getEntries(), [](const auto &entry) {
const auto &[unlocalizedName, openView] = entry;
return openView->hasViewMenuItemEntry() && openView->shouldProcess();
});
ImGuiWindowClass windowClass = {};
windowClass.DockNodeFlagsOverrideSet |= ImGuiDockNodeFlags_NoCloseButton;
if (openViewCount <= 1 || LayoutManager::isLayoutLocked())
windowClass.DockNodeFlagsOverrideSet |= ImGuiDockNodeFlags_NoTabBar;
ImGui::SetNextWindowClass(&windowClass);
auto window = ImGui::FindWindowByName(view->getName().c_str());
if (window != nullptr && window->DockNode == nullptr)
ImGui::SetNextWindowBgAlpha(1.0F);
// Draw view
view->draw();
view->trackViewOpenState();
if (view->getWindowOpenState()) {
// Get the currently focused view
if (window != nullptr && (window->Flags & ImGuiWindowFlags_Popup) != ImGuiWindowFlags_Popup) {
auto windowName = View::toWindowName(name);
ImGui::Begin(windowName.c_str());
// Detect if the window is focused
const bool focused = ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows | ImGuiFocusedFlags_NoPopupHierarchy);
view->setFocused(focused);
// Dock the window if it's not already docked
if (view->didWindowJustOpen() && !ImGui::IsWindowDocked()) {
ImGui::DockBuilderDockWindow(windowName.c_str(), ImHexApi::System::getMainDockSpaceId());
EventViewOpened::post(view.get());
}
// Pass on currently pressed keys to the shortcut handler
for (const auto &key : m_pressedKeys) {
ShortcutManager::process(view.get(), io.ConfigMacOSXBehaviors ? io.KeySuper : io.KeyCtrl, io.KeyAlt, io.KeyShift, io.ConfigMacOSXBehaviors ? io.KeyCtrl : io.KeySuper, focused, key);
}
ImGui::End();
}
}
}
// Handle global shortcuts
for (const auto &key : m_pressedKeys) {
ShortcutManager::processGlobals(io.ConfigMacOSXBehaviors ? io.KeySuper : io.KeyCtrl, io.KeyAlt, io.KeyShift, io.ConfigMacOSXBehaviors ? io.KeyCtrl : io.KeySuper, key);
}
m_pressedKeys.clear();
}
void Window::frameEnd() {
EventFrameEnd::post();
// Clean up all tasks that are done
TaskManager::collectGarbage();
this->endNativeWindowFrame();
// Finalize ImGui frame
ImGui::Render();
// Compare the previous frame buffer to the current one to determine if the window content has changed
// If not, there's no point in sending the draw data off to the GPU and swapping buffers
// NOTE: For anybody looking at this code and thinking "why not just hash the buffer and compare the hashes",
// the reason is that hashing the buffer is significantly slower than just comparing the buffers directly.
// The buffer might become quite large if there's a lot of vertices on the screen but it's still usually less than
// 10MB (out of which only the active portion needs to actually be compared) which is worth the ~60x speedup.
bool shouldRender = false;
{
static std::vector<u8> previousVtxData;
static size_t previousVtxDataSize = 0;
size_t offset = 0;
size_t vtxDataSize = 0;
for (const auto viewPort : ImGui::GetPlatformIO().Viewports) {
auto drawData = viewPort->DrawData;
for (int n = 0; n < drawData->CmdListsCount; n++) {
vtxDataSize += drawData->CmdLists[n]->VtxBuffer.size() * sizeof(ImDrawVert);
}
}
for (const auto viewPort : ImGui::GetPlatformIO().Viewports) {
auto drawData = viewPort->DrawData;
for (int n = 0; n < drawData->CmdListsCount; n++) {
const ImDrawList *cmdList = drawData->CmdLists[n];
std::string ownerName = cmdList->_OwnerName;
if (vtxDataSize == previousVtxDataSize && (!ownerName.contains("##Popup") || !ownerName.contains("##image"))) {
shouldRender = shouldRender || std::memcmp(previousVtxData.data() + offset, cmdList->VtxBuffer.Data, cmdList->VtxBuffer.size() * sizeof(ImDrawVert)) != 0;
} else {
shouldRender = true;
}
if (previousVtxData.size() < offset + cmdList->VtxBuffer.size() * sizeof(ImDrawVert)) {
previousVtxData.resize(offset + cmdList->VtxBuffer.size() * sizeof(ImDrawVert));
}
std::memcpy(previousVtxData.data() + offset, cmdList->VtxBuffer.Data, cmdList->VtxBuffer.size() * sizeof(ImDrawVert));
offset += cmdList->VtxBuffer.size() * sizeof(ImDrawVert);
}
}
previousVtxDataSize = vtxDataSize;
}
GLFWwindow *backupContext = glfwGetCurrentContext();
ImGui::UpdatePlatformWindows();
ImGui::RenderPlatformWindowsDefault();
glfwMakeContextCurrent(backupContext);
if (shouldRender) {
#if !defined(OS_WEB)
if (m_postProcessingShader.isValid())
drawWithShader();
else
drawImGui();
#else
drawImGui();
#endif
glfwSwapBuffers(m_window);
}
// Process layout load requests
// NOTE: This needs to be done before a new frame is started, otherwise ImGui won't handle docking correctly
LayoutManager::process();
WorkspaceManager::process();
}
void Window::drawImGui() {
auto* drawData = ImGui::GetDrawData();
// Avoid accidentally clearing the viewport when the application is minimized,
// otherwise the OS will display an empty frame during deminimization on macOS
if (drawData->DisplaySize.x != 0 && drawData->DisplaySize.y != 0) {
int displayWidth, displayHeight;
glfwGetFramebufferSize(m_window, &displayWidth, &displayHeight);
glViewport(0, 0, displayWidth, displayHeight);
glClearColor(0.00F, 0.00F, 0.00F, 0.00F);
glClear(GL_COLOR_BUFFER_BIT);
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
}
}
void Window::drawWithShader() {
#if !defined(OS_WEB)
int displayWidth, displayHeight;
glfwGetFramebufferSize(m_window, &displayWidth, &displayHeight);
GLuint fbo, texture;
glGenFramebuffers(1, &fbo);
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
// Create a texture to render into
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, displayWidth, displayHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
// Attach the texture to the framebuffer
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
// Check if framebuffer is complete
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
log::error("Framebuffer is not complete!");
}
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
drawImGui();
glBindFramebuffer(GL_FRAMEBUFFER, 0);
GLuint quadVAO, quadVBO;
float quadVertices[] = {
// positions // texCoords
-1.0f, 1.0f, 0.0f, 1.0f,
-1.0f, -1.0f, 0.0f, 0.0f,
1.0f, -1.0f, 1.0f, 0.0f,
-1.0f, 1.0f, 0.0f, 1.0f,
1.0f, -1.0f, 1.0f, 0.0f,
1.0f, 1.0f, 1.0f, 1.0f
};
glGenVertexArrays(1, &quadVAO);
glGenBuffers(1, &quadVBO);
glBindVertexArray(quadVAO);
glBindBuffer(GL_ARRAY_BUFFER, quadVBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(quadVertices), quadVertices, GL_STATIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void*)0);
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void*)(2 * sizeof(float)));
glBindVertexArray(0);
m_postProcessingShader.bind();
glBindVertexArray(quadVAO);
glBindTexture(GL_TEXTURE_2D, texture);
glClearColor(0.00F, 0.00F, 0.00F, 0.00F);
glClear(GL_COLOR_BUFFER_BIT);
glDrawArrays(GL_TRIANGLES, 0, 6);
m_postProcessingShader.unbind();
glDeleteVertexArrays(1, &quadVAO);
glDeleteBuffers(1, &quadVBO);
glDeleteTextures(1, &texture);
glDeleteFramebuffers(1, &fbo);
#endif
}
void Window::unlockFrameRate() {
{
std::scoped_lock lock(m_wakeupMutex);
m_remainingUnlockedTime = std::chrono::seconds(2);
}
this->forceNewFrame();
}
void Window::forceNewFrame() {
std::scoped_lock lock(m_wakeupMutex);
m_wakeupFlag = true;
m_wakeupCondVar.notify_all();
}
void Window::initGLFW() {
auto initialWindowProperties = ImHexApi::System::getInitialWindowProperties();
glfwSetErrorCallback([](int error, const char *desc) {
bool isWaylandError = error == GLFW_PLATFORM_ERROR;
#if defined(GLFW_FEATURE_UNAVAILABLE)
isWaylandError = isWaylandError || (error == GLFW_FEATURE_UNAVAILABLE);
#endif
isWaylandError = isWaylandError && std::string_view(desc).contains("Wayland");
if (isWaylandError) {
// Ignore error spam caused by Wayland not supporting moving or resizing
// windows or querying their position and size.
return;
}
try {
log::error("GLFW Error [0x{:05X}] : {}", error, desc);
} catch (const std::system_error &) {
// Catch and ignore system error that might be thrown when too many errors are being logged to a file
}
});
configureGLFW();
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE);
glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_API);
if (initialWindowProperties.has_value()) {
glfwWindowHint(GLFW_MAXIMIZED, initialWindowProperties->maximized);
}
// Create window
m_windowTitle = "ImHex";
m_window = glfwCreateWindow(1280_scaled, 720_scaled, m_windowTitle.c_str(), nullptr, nullptr);
ImHexApi::System::impl::setMainWindowHandle(m_window);
glfwSetWindowUserPointer(m_window, this);
if (m_window == nullptr) {
log::fatal("Failed to create window!");
std::abort();
}
// Force window to be fully opaque by default
glfwSetWindowOpacity(m_window, 1.0F);
glfwMakeContextCurrent(m_window);
// Disable VSync. Not like any graphics driver actually cares
glfwSwapInterval(0);
// Center window
GLFWmonitor *monitor = glfwGetPrimaryMonitor();
if (monitor != nullptr) {
const GLFWvidmode *mode = glfwGetVideoMode(monitor);
if (mode != nullptr) {
int monitorX, monitorY;
glfwGetMonitorPos(monitor, &monitorX, &monitorY);
int windowWidth, windowHeight;
glfwGetWindowSize(m_window, &windowWidth, &windowHeight);
glfwSetWindowPos(m_window, monitorX + (mode->width - windowWidth) / 2, monitorY + (mode->height - windowHeight) / 2);
}
}
// Set up initial window position
{
int x = 0, y = 0;
glfwGetWindowPos(m_window, &x, &y);
if (initialWindowProperties.has_value()) {
x = initialWindowProperties->x;
y = initialWindowProperties->y;
}
ImHexApi::System::impl::setMainWindowPosition(x, y);
glfwSetWindowPos(m_window, x, y);
}
// Set up initial window size
{
int width = 0, height = 0;
glfwGetWindowSize(m_window, &width, &height);
if (initialWindowProperties.has_value()) {
width = initialWindowProperties->width;
height = initialWindowProperties->height;
}
ImHexApi::System::impl::setMainWindowSize(width, height);
glfwSetWindowSize(m_window, width, height);
}
static const auto unlockFrameRate = [](GLFWwindow *, auto ...) {
auto win = static_cast<Window *>(glfwGetWindowUserPointer(ImHexApi::System::getMainWindowHandle()));
if (win == nullptr)
return;
win->unlockFrameRate();
};
static const auto isMainWindow = [](GLFWwindow *window) {
return window == ImHexApi::System::getMainWindowHandle();
};
// Register window move callback
glfwSetWindowPosCallback(m_window, [](GLFWwindow *window, int x, int y) {
unlockFrameRate(window);
if (!isMainWindow(window)) return;
ImHexApi::System::impl::setMainWindowPosition(x, y);
int width = 0, height = 0;
glfwGetWindowSize(window, &width, &height);
ImHexApi::System::impl::setMainWindowPosition(x, y);
ImHexApi::System::impl::setMainWindowSize(width, height);
});
// Register window resize callback
glfwSetWindowSizeCallback(m_window, [](GLFWwindow *window, [[maybe_unused]] int width, [[maybe_unused]] int height) {
unlockFrameRate(window);
if (!isMainWindow(window)) return;
#if !defined(OS_WINDOWS)
if (!glfwGetWindowAttrib(window, GLFW_ICONIFIED)) {
int x = 0, y = 0;
glfwGetWindowPos(window, &x, &y);
ImHexApi::System::impl::setMainWindowPosition(x, y);
ImHexApi::System::impl::setMainWindowSize(width, height);
}
#endif
#if defined(OS_MACOS)
// Stop widgets registering hover effects while the window is being resized
if (macosIsWindowBeingResizedByUser(window)) {
ImGui::GetIO().MousePos = ImVec2();
}
#elif defined(OS_WEB)
auto win = static_cast<Window *>(glfwGetWindowUserPointer(ImHexApi::System::getMainWindowHandle()));
win->fullFrame();
#endif
});
glfwSetCursorPosCallback(m_window, unlockFrameRate);
glfwSetMouseButtonCallback(m_window, unlockFrameRate);
glfwSetScrollCallback(m_window, unlockFrameRate);
glfwSetWindowFocusCallback(m_window, [](GLFWwindow *window, int focused) {
unlockFrameRate(window);
EventWindowFocused::post(focused == GLFW_TRUE);
});
glfwSetWindowMaximizeCallback(m_window, [](GLFWwindow *window, int) {
glfwShowWindow(window);
});
// Register key press callback
glfwSetInputMode(m_window, GLFW_LOCK_KEY_MODS, GLFW_TRUE);
glfwSetKeyCallback(m_window, [](GLFWwindow *window, int key, int scanCode, int action, int mods) {
std::ignore = mods;
#if !defined(OS_WEB)
// Handle A-Z keys using their ASCII value instead of the keycode
if (key >= GLFW_KEY_A && key <= GLFW_KEY_Z) {
std::string_view name = glfwGetKeyName(key, scanCode);
// If the key name is only one character long, use the ASCII value instead
// Otherwise the keyboard was set to a non-English layout and the key name
// is not the same as the ASCII value
if (!name.empty()) {
const std::uint8_t byte = name[0];
if (name.length() == 1 && byte <= 0x7F) {
key = std::toupper(byte);
}
}
}
#else
std::ignore = scanCode;
// Emscripten doesn't support glfwGetKeyName. Just pass the value through.
#endif
if (key == GLFW_KEY_UNKNOWN) return;
if (action == GLFW_PRESS || action == GLFW_REPEAT) {
if (key != GLFW_KEY_LEFT_CONTROL && key != GLFW_KEY_RIGHT_CONTROL &&
key != GLFW_KEY_LEFT_ALT && key != GLFW_KEY_RIGHT_ALT &&
key != GLFW_KEY_LEFT_SHIFT && key != GLFW_KEY_RIGHT_SHIFT &&
key != GLFW_KEY_LEFT_SUPER && key != GLFW_KEY_RIGHT_SUPER
) {
unlockFrameRate(window);
// Windows and Linux use the numpad for special actions when NumLock is disabled such as arrow keys or
// the insert, home and end keys. GLFW however still returns the original numpad keys that are being pressed.
// Translate them here to the desired keys.
// macOS doesn't seem to have the concept of NumLock at all. They repurposed it as the "Clear" key so this
// conversion makes no sense there.
#if !defined(OS_MACOS)
if (!(mods & GLFW_MOD_NUM_LOCK)) {
if (key == GLFW_KEY_KP_0) key = GLFW_KEY_INSERT;
else if (key == GLFW_KEY_KP_1) key = GLFW_KEY_END;
else if (key == GLFW_KEY_KP_2) key = GLFW_KEY_DOWN;
else if (key == GLFW_KEY_KP_3) key = GLFW_KEY_PAGE_DOWN;
else if (key == GLFW_KEY_KP_4) key = GLFW_KEY_LEFT;
else if (key == GLFW_KEY_KP_6) key = GLFW_KEY_RIGHT;
else if (key == GLFW_KEY_KP_7) key = GLFW_KEY_HOME;
else if (key == GLFW_KEY_KP_8) key = GLFW_KEY_UP;
else if (key == GLFW_KEY_KP_9) key = GLFW_KEY_PAGE_UP;
}
#endif
auto win = static_cast<Window *>(glfwGetWindowUserPointer(ImHexApi::System::getMainWindowHandle()));
win->m_pressedKeys.insert(key);
}
}
});
// Register window close callback
glfwSetWindowCloseCallback(m_window, [](GLFWwindow *window) {
unlockFrameRate(window);
if (!isMainWindow(window)) return;
EventWindowClosing::post(window);
});
glfwSetWindowSizeLimits(m_window, 480_scaled, 360_scaled, GLFW_DONT_CARE, GLFW_DONT_CARE);
m_frameRateThread = std::jthread([this](const std::stop_token &stopToken) {
using Duration = std::chrono::duration<double, std::nano>;
Duration passedTime = {};
std::chrono::steady_clock::time_point startTime = {}, endTime = {};
Duration requestedFrameTime = {};
float targetFps = 0;
const auto nativeFps = []() -> float {
if (const auto monitor = glfwGetPrimaryMonitor(); monitor != nullptr) {
if (const auto videoMode = glfwGetVideoMode(monitor); videoMode != nullptr) {
return videoMode->refreshRate;
}
}
return 60;
}();
while (!stopToken.stop_requested()) {
const auto iterationTime = endTime - startTime;
startTime = std::chrono::steady_clock::now();
targetFps = ImHexApi::System::getTargetFPS();
// If the target frame rate is below 15, use the current monitor's refresh rate
if (targetFps < 15) {
targetFps = nativeFps;
}
passedTime += iterationTime;
{
std::scoped_lock lock(m_sleepMutex);
if (m_remainingUnlockedTime > std::chrono::nanoseconds(0)) {
m_remainingUnlockedTime -= iterationTime;
} else {
targetFps = 5;
}
requestedFrameTime = (Duration(1.0E9) / targetFps) / 1.3;
if (passedTime >= requestedFrameTime) {
m_sleepFlag = true;
m_sleepCondVar.notify_all();
passedTime = {};
}
}
{
std::unique_lock lock(m_wakeupMutex);
m_wakeupCondVar.wait_for(lock, requestedFrameTime, [&] {
return m_wakeupFlag || stopToken.stop_requested();
});
m_wakeupFlag = false;
}
endTime = std::chrono::steady_clock::now();
}
});
}
void Window::resize(i32 width, i32 height) {
glfwSetWindowSize(m_window, width, height);
}
void Window::initImGui() {
IMGUI_CHECKVERSION();
// Initialize ImGui and all other ImGui extensions
GImGui = ImGui::CreateContext();
GImPlot = ImPlot::CreateContext();
ImPlot3D::GImPlot3D = ImPlot3D::CreateContext();
GImNodes = ImNodes::CreateContext();
ImGuiIO &io = ImGui::GetIO();
ImGuiStyle &style = ImGui::GetStyle();
ImNodes::GetStyle().Flags = ImNodesStyleFlags_NodeOutline | ImNodesStyleFlags_GridLines;
io.ConfigFlags |= ImGuiConfigFlags_DockingEnable | ImGuiConfigFlags_NavEnableKeyboard;
io.ConfigWindowsMoveFromTitleBarOnly = true;
io.FontGlobalScale = 1.0F;
ImGui::GetCurrentContext()->FontAtlasOwnedByContext = false;
if (glfwGetPrimaryMonitor() != nullptr) {
if (ImHexApi::System::isMutliWindowModeEnabled())
io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable;
}
io.ConfigViewportsNoTaskBarIcon = false;
ImNodes::PushAttributeFlag(ImNodesAttributeFlags_EnableLinkDetachWithDragClick);
ImNodes::PushAttributeFlag(ImNodesAttributeFlags_EnableLinkCreationOnSnap);
// Allow ImNodes links to always be detached without holding down any button
{
static bool always = true;
ImNodes::GetIO().LinkDetachWithModifierClick.Modifier = &always;
}
io.UserData = &m_imguiCustomData;
auto scale = ImHexApi::System::getGlobalScale();
style.ScaleAllSizes(scale);
io.DisplayFramebufferScale = ImVec2(scale, scale);
style.WindowMenuButtonPosition = ImGuiDir_None;
style.IndentSpacing = 10.0F;
style.DisplaySafeAreaPadding = ImVec2(0.0F, 0.0F);
style.Colors[ImGuiCol_TabSelectedOverline] = ImVec4(0.0F, 0.0F, 0.0F, 0.0F);
style.Colors[ImGuiCol_TabDimmedSelectedOverline] = ImVec4(0.0F, 0.0F, 0.0F, 0.0F);
// Install custom settings handler
{
ImGuiSettingsHandler handler;
handler.TypeName = "ImHex";
handler.TypeHash = ImHashStr("ImHex");
handler.ReadOpenFn = [](ImGuiContext *ctx, ImGuiSettingsHandler *, const char *) -> void* { return ctx; };
handler.ReadLineFn = [](ImGuiContext *, ImGuiSettingsHandler *, void *, const char *line) {
LayoutManager::onLoad(line);
};
handler.WriteAllFn = [](ImGuiContext *, ImGuiSettingsHandler *handler, ImGuiTextBuffer *buffer) {
buffer->appendf("[%s][General]\n", handler->TypeName);
LayoutManager::onStore(buffer);
buffer->append("\n");
};
handler.UserData = this;
auto context = ImGui::GetCurrentContext();
context->SettingsHandlers.push_back(handler);
context->TestEngineHookItems = true;
io.IniFilename = nullptr;
}
ImGui_ImplGlfw_InitForOpenGL(m_window, true);
#if defined(OS_MACOS)
ImGui_ImplOpenGL3_Init("#version 150");
#elif defined(OS_WEB)
ImGui_ImplOpenGL3_Init();
ImGui_ImplGlfw_InstallEmscriptenCallbacks(m_window, "#canvas");
#else
ImGui_ImplOpenGL3_Init("#version 410");
#endif
ImGui_ImplGlfw_SetCallbacksChainForAllWindows(true);
for (const auto &plugin : PluginManager::getPlugins())
plugin.setImGuiContext(ImGui::GetCurrentContext());
RequestInitThemeHandlers::post();
}
void Window::exitGLFW() {
glfwDestroyWindow(m_window);
m_window = nullptr;
}
void Window::exitImGui() {
ImGui_ImplOpenGL3_Shutdown();
ImGui_ImplGlfw_Shutdown();
ImNodes::DestroyContext();
ImPlot3D::DestroyContext();
ImPlot::DestroyContext();
ImGui::DestroyContext();
}
}