1
0
mirror of synced 2024-12-21 03:55:56 +01:00
ImHex/main/gui/source/window/window.cpp

1033 lines
40 KiB
C++
Raw Normal View History

#include "window.hpp"
#include <hex.hpp>
2021-08-29 22:15:18 +02:00
#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>
2023-12-11 15:54:22 +01:00
#include <hex/api/workspace_manager.hpp>
2023-12-13 11:24:25 +01:00
#include <hex/api/tutorial_manager.hpp>
2021-08-29 22:15:18 +02:00
#include <hex/helpers/utils.hpp>
#include <hex/helpers/fs.hpp>
2021-08-29 22:15:18 +02:00
#include <hex/helpers/logger.hpp>
#include <hex/ui/view.hpp>
#include <hex/ui/popup.hpp>
#include <chrono>
#include <csignal>
#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>
2021-03-02 13:48:23 +01:00
#include <implot.h>
#include <implot_internal.h>
2021-08-17 13:41:19 +02:00
#include <imnodes.h>
#include <imnodes_internal.h>
2023-07-09 12:53:31 +02:00
#include <wolv/utils/string.hpp>
#include <GLFW/glfw3.h>
2023-12-19 23:21:20 +01:00
#include <hex/ui/toast.hpp>
#include <wolv/utils/guards.hpp>
#include <fmt/printf.h>
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();
ContentRegistry::Settings::impl::store();
2024-02-18 11:29:18 +01:00
ContentRegistry::Settings::impl::load();
EventWindowInitialized::post();
EventImHexStartupFinished::post();
}
Window::~Window() {
EventProviderDeleted::unsubscribe(this);
RequestCloseImHex::unsubscribe(this);
RequestUpdateWindowTitle::unsubscribe(this);
EventAbnormalTermination::unsubscribe(this);
RequestOpenPopup::unsubscribe(this);
EventWindowDeinitializing::post(m_window);
2023-12-11 15:54:22 +01:00
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) {
2023-12-19 13:10:25 +01:00
glfwSetWindowShouldClose(m_window, GLFW_TRUE);
if (!noQuestions)
2023-12-19 13:10:25 +01:00
EventWindowClosing::post(m_window);
});
// Handle opening popups
RequestOpenPopup::subscribe(this, [this](auto name) {
2023-12-19 13:10:25 +01:00
std::scoped_lock lock(m_popupMutex);
2023-12-19 13:10:25 +01:00
m_popupsToOpen.push_back(name);
});
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 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() {
static u32 crashWatchdog = 0;
if (auto g = ImGui::GetCurrentContext(); g == nullptr || g->WithinFrameScope) {
return;
}
try {
this->frameBegin();
this->frame();
this->frameEnd();
// 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::ErrorCheckEndFrameRecover(errorRecoverLogCallback, nullptr);
ImGui::EndFrame();
ImGui::UpdatePlatformWindows();
// Handle the exception
handleException();
}
}
void Window::loop() {
2023-12-19 13:10:25 +01:00
while (!glfwWindowShouldClose(m_window)) {
m_lastStartFrameTime = glfwGetTime();
2023-05-11 23:22:06 +02:00
// Determine if the application should be in long sleep mode
bool shouldLongSleep = !m_unlockFrameRate;
static double lockTimeout = 0;
if (!shouldLongSleep) {
lockTimeout = 0.05;
} else if (lockTimeout > 0) {
lockTimeout -= m_lastFrameTime;
}
if (shouldLongSleep && lockTimeout > 0)
shouldLongSleep = false;
m_unlockFrameRate = false;
2023-12-19 13:10:25 +01:00
if (!glfwGetWindowAttrib(m_window, GLFW_VISIBLE) || glfwGetWindowAttrib(m_window, GLFW_ICONIFIED)) {
// If the application is minimized or not visible, don't render anything
glfwWaitEvents();
} else {
// If the application is visible, render a frame
// If the application is in long sleep mode, only render a frame every 200ms
// Long sleep mode is enabled automatically after a few frames if the window content hasn't changed
// and no events have been received
if (shouldLongSleep) {
// Calculate the time until the next frame
constexpr static auto LongSleepFPS = 5.0;
const double timeout = std::max(0.0, (1.0 / LongSleepFPS) - (glfwGetTime() - m_lastStartFrameTime));
glfwPollEvents();
glfwWaitEventsTimeout(timeout);
} else {
glfwPollEvents();
}
}
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);
// Limit frame rate
// If the target FPS are below 15, use the monitor refresh rate, if it's above 200, don't limit the frame rate
auto targetFPS = ImHexApi::System::getTargetFPS();
if (targetFPS >= 200) {
// Let it rip
} else {
// If the target frame rate is below 15, use the current monitor's refresh rate
if (targetFPS < 15) {
// Fall back to 60 FPS if the monitor refresh rate cannot be determined
targetFPS = 60;
if (auto monitor = glfwGetWindowMonitor(m_window); monitor != nullptr) {
if (auto videoMode = glfwGetVideoMode(monitor); videoMode != nullptr) {
targetFPS = videoMode->refreshRate;
}
}
}
// Sleep if we're not in long sleep mode
if (!shouldLongSleep) {
// If anything goes wrong with these checks, make sure that we're sleeping for at least 1ms
std::this_thread::sleep_for(std::chrono::milliseconds(1));
// Sleep for the remaining time if the frame rate is above the target frame rate
const auto frameTime = glfwGetTime() - m_lastStartFrameTime;
const auto targetFrameTime = 1.0 / targetFPS;
if (frameTime < targetFrameTime) {
glfwWaitEventsTimeout(targetFrameTime - frameTime);
// glfwWaitEventsTimeout might return early if there's an event
const auto frameTime = glfwGetTime() - m_lastStartFrameTime;
if (frameTime < targetFrameTime) {
const auto timeToSleepMs = (int)((targetFrameTime - frameTime) * 1000);
std::this_thread::sleep_for(std::chrono::milliseconds(timeToSleepMs));
}
}
}
}
2023-12-11 11:42:33 +01:00
2023-12-19 13:10:25 +01:00
m_lastFrameTime = glfwGetTime() - m_lastStartFrameTime;
}
// 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() {
// Start new ImGui Frame
ImGui_ImplOpenGL3_NewFrame();
ImGui_ImplGlfw_NewFrame();
ImGui::NewFrame();
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);
2023-05-11 23:56:51 +02:00
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0F);
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0F);
2021-08-21 00:52:11 +02:00
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0));
2020-11-30 00:03:12 +01:00
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
2022-02-06 21:39:10 +01:00
if (ImGui::Begin("ImHexDockSpace", nullptr, windowFlags)) {
2021-08-21 00:52:11 +02:00
ImGui::PopStyleVar();
2023-11-08 11:53:26 +01:00
this->beginNativeWindowFrame();
} else {
ImGui::PopStyleVar();
2020-11-11 14:41:44 +01:00
}
ImGui::End();
2021-08-21 00:52:11 +02:00
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
{
2023-11-10 20:47:08 +01:00
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.2);
ImGui::TableSetupColumn("Exists", ImGuiTableColumnFlags_WidthFixed, ImGui::GetTextLineHeight() * 3);
ImGui::TableHeadersRow();
for (const auto &path : fs::getDefaultPaths(fs::ImHexPath::Plugins, true)) {
const auto filePath = path / "builtin.hexplug";
ImGui::TableNextRow();
ImGui::TableNextColumn();
2023-03-12 18:43:05 +01:00
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
{
2023-12-19 13:10:25 +01:00
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("");
2023-04-09 23:24:48 +02:00
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) {
2023-12-11 11:42:33 +01:00
popupDelay = 0.2;
} else {
2023-12-19 13:10:25 +01:00
popupDelay -= m_lastFrameTime;
2023-12-11 11:42:33 +01:00
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;
GImGui->MovingWindow = nullptr;
}
}
ImGui::EndPopup();
}
};
if (currPopup->isModal())
createPopup(ImGui::BeginPopupModal(name, closeButton, flags));
else
createPopup(ImGui::BeginPopup(name, flags));
if (!ImGui::IsPopupOpen(name) && displayFrameCount < 5) {
ImGui::OpenPopup(name);
}
if (currPopup->shouldClose() || !open) {
log::debug("Closing popup '{}'", name);
positionSet = sizeSet = false;
currPopup = nullptr;
2023-04-09 23:24:48 +02:00
}
}
}
2023-12-19 23:21:20 +01:00
// Draw Toasts
{
u32 index = 0;
for (const auto &toast : impl::ToastBase::getQueuedToasts() | std::views::take(4)) {
const auto toastHeight = 60_scaled;
2023-12-19 23:21:20 +01:00
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 5_scaled);
ImGui::SetNextWindowSize(ImVec2(280_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)) {
2023-12-19 23:21:20 +01:00
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);
2023-12-19 23:21:20 +01:00
drawList->PopClipRect();
ImGui::Indent();
toast->draw();
2023-12-19 23:21:20 +01:00
ImGui::Unindent();
if (ImGui::IsWindowHovered() || toast->getAppearTime() <= 0)
toast->setAppearTime(ImGui::GetTime());
2023-12-19 23:21:20 +01:00
}
ImGui::End();
ImGui::PopStyleVar();
index += 1;
2023-12-19 23:21:20 +01:00
}
std::erase_if(impl::ToastBase::getQueuedToasts(), [](const auto &toast){
return toast->getAppearTime() > 0 && (toast->getAppearTime() + impl::ToastBase::VisibilityTime) < ImGui::GetTime();
});
2023-12-19 23:21:20 +01:00
}
// Run all deferred calls
TaskManager::runDeferredCalls();
}
void Window::frame() {
auto &io = ImGui::GetIO();
// Loop through all views and draw them
for (auto &[name, view] : ContentRegistry::Views::impl::getEntries()) {
2021-12-12 21:46:48 +01:00
ImGui::GetCurrentContext()->NextWindowData.ClearFlags();
// Draw always visible views
view->drawAlwaysVisibleContent();
// Skip views that shouldn't be processed currently
if (!view->shouldProcess())
continue;
2023-12-06 13:49:58 +01:00
const auto openViewCount = std::ranges::count_if(ContentRegistry::Views::impl::getEntries(), [](const auto &entry) {
const auto &[unlocalizedName, openView] = entry;
2023-12-06 13:49:58 +01:00
return openView->hasViewMenuItemEntry() && openView->shouldProcess();
2023-12-06 13:49:58 +01:00
});
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();
2023-11-17 14:46:21 +01:00
if (view->getWindowOpenState()) {
2022-01-11 20:29:06 +01:00
bool hasWindow = window != nullptr;
2022-02-01 22:09:44 +01:00
bool focused = false;
// Get the currently focused view
if (hasWindow && (window->Flags & ImGuiWindowFlags_Popup) != ImGuiWindowFlags_Popup) {
auto windowName = View::toWindowName(name);
ImGui::Begin(windowName.c_str());
// Detect if the window is focused
focused = ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows | ImGuiFocusedFlags_NoPopupHierarchy);
// 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());
}
ImGui::End();
}
// Pass on currently pressed keys to the shortcut handler
2023-12-19 13:10:25 +01:00
for (const auto &key : m_pressedKeys) {
ShortcutManager::process(view.get(), io.KeyCtrl, io.KeyAlt, io.KeyShift, io.KeySuper, focused, key);
}
}
}
// Handle global shortcuts
2023-12-19 13:10:25 +01:00
for (const auto &key : m_pressedKeys) {
2023-11-17 14:46:21 +01:00
ShortcutManager::processGlobals(io.KeyCtrl, io.KeyAlt, io.KeyShift, io.KeySuper, key);
}
2023-12-19 13:10:25 +01:00
m_pressedKeys.clear();
}
void Window::frameEnd() {
EventFrameEnd::post();
2023-12-13 11:24:25 +01:00
TutorialManager::drawTutorial();
// Clean up all tasks that are done
TaskManager::collectGarbage();
this->endNativeWindowFrame();
ImGui::ErrorCheckEndFrameRecover(errorRecoverLogCallback, nullptr);
// Finalize ImGui frame
ImGui::Render();
2020-11-17 13:58:50 +01:00
// 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++) {
2023-12-27 16:33:49 +01:00
const ImDrawList *cmdList = drawData->CmdLists[n];
if (vtxDataSize == previousVtxDataSize) {
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;
}
2024-02-28 20:36:22 +01:00
GLFWwindow *backupContext = glfwGetCurrentContext();
ImGui::UpdatePlatformWindows();
2024-02-28 20:36:22 +01:00
ImGui::RenderPlatformWindowsDefault();
glfwMakeContextCurrent(backupContext);
if (shouldRender) {
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());
glfwSwapBuffers(m_window);
}
m_unlockFrameRate = true;
}
// 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();
}
sys/build: Properly support per-system metadata file paths (#181) * sys: Move away from metadata paths next to executable in the application Build system doesn't properly install / pack stuff yet * build: Updated README to contain better install instructions * sys: Search for imhex resource files in ~/Application Support * sys: MAX_PATH -> PATH_MAX * sys: Seach for imhex resource files in Application Support using NSFileManager (#180) * sys: Allow for multiple file search paths Also use install prefix instead of just /usr on Linux * build: Fixed IMHEX_INSTALL_PREFIX macro definition * build: Fix duplicate switch entry on Linux * docs: Updated readme to properly reflect new paths and dependencies * sys: Install files in their proper paths on linux (#183) * Install files in their proper paths on linux * Only create user directories * Follow the XDG specification on linux XDG specification specifies how to find config and data directories on linux systems. Specifically, it says this: - Data should be written to $XDG_DATA_HOME - Config should be written to $XDG_CONFIG_HOME - Data should be read from $XDG_DATA_HOME:$XDG_DATA_DIRS - Config should be read from $XDG_CONFIG_HOME:$XDG_CONFIG_DIRS The default values are this: - XDG_DATA_HOME: $HOME/.local/share - XDG_CONFIG_HOME: $HOME/.config - XDG_DATA_DIRS: /usr/share:/usr/local/share - XDG_CONFIG_DIRS: /etc/xdg Platforms with non-standard filesystems (like NixOS) will correctly set up those environment variables, allowing softwares to work unmodified. In order to make integration as simple as possible, we use a simple header-only dependency called XDGPP which does all the hard work for us to find the default directories. * Look for plugins in all Plugin Paths If the plugin folder was missing from one of the PluginPaths, we would immediately stop loading plugins. We now keep looking even if one of the path is missing. Co-authored-by: Nichole Mattera <me@nicholemattera.com> Co-authored-by: Robin Lambertz <unfiltered@roblab.la>
2021-03-01 08:56:49 +01:00
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
}
2020-11-11 14:41:44 +01:00
});
2022-01-13 14:34:27 +01:00
if (!glfwInit()) {
log::fatal("Failed to initialize GLFW!");
std::abort();
}
configureGLFW();
2021-04-21 20:06:48 +02:00
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE);
glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_API);
2020-11-30 00:03:12 +01:00
if (initialWindowProperties.has_value()) {
glfwWindowHint(GLFW_MAXIMIZED, initialWindowProperties->maximized);
}
// Create window
2023-12-19 13:10:25 +01:00
m_windowTitle = "ImHex";
m_window = glfwCreateWindow(1280_scaled, 720_scaled, m_windowTitle.c_str(), nullptr, nullptr);
2021-08-18 22:36:46 +02:00
ImHexApi::System::impl::setMainWindowHandle(m_window);
2023-12-19 13:10:25 +01:00
glfwSetWindowUserPointer(m_window, this);
2023-12-19 13:10:25 +01:00
if (m_window == nullptr) {
2022-01-13 14:34:27 +01:00
log::fatal("Failed to create window!");
std::abort();
}
// Force window to be fully opaque by default
2023-12-19 13:10:25 +01:00
glfwSetWindowOpacity(m_window, 1.0F);
2023-12-19 13:10:25 +01:00
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;
2023-12-19 13:10:25 +01:00
glfwGetWindowSize(m_window, &windowWidth, &windowHeight);
2023-12-19 13:10:25 +01:00
glfwSetWindowPos(m_window, monitorX + (mode->width - windowWidth) / 2, monitorY + (mode->height - windowHeight) / 2);
}
}
// Set up initial window position
2021-08-18 22:36:46 +02:00
{
int x = 0, y = 0;
2023-12-19 13:10:25 +01:00
glfwGetWindowPos(m_window, &x, &y);
if (initialWindowProperties.has_value()) {
x = initialWindowProperties->x;
y = initialWindowProperties->y;
}
ImHexApi::System::impl::setMainWindowPosition(x, y);
2023-12-19 13:10:25 +01:00
glfwSetWindowPos(m_window, x, y);
2021-08-18 22:36:46 +02:00
}
// Set up initial window size
2021-08-18 22:36:46 +02:00
{
int width = 0, height = 0;
2023-12-19 13:10:25 +01:00
glfwGetWindowSize(m_window, &width, &height);
glfwSetWindowSize(m_window, width, height);
if (initialWindowProperties.has_value()) {
width = initialWindowProperties->width;
height = initialWindowProperties->height;
}
ImHexApi::System::impl::setMainWindowSize(width, height);
2023-12-19 13:10:25 +01:00
glfwSetWindowSize(m_window, width, height);
2021-08-18 22:36:46 +02:00
}
// Register window move callback
2023-12-19 13:10:25 +01:00
glfwSetWindowPosCallback(m_window, [](GLFWwindow *window, int x, int y) {
ImHexApi::System::impl::setMainWindowPosition(x, y);
auto win = static_cast<Window *>(glfwGetWindowUserPointer(window));
2023-12-27 21:23:54 +01:00
win->m_unlockFrameRate = true;
win->fullFrame();
2021-08-18 22:36:46 +02:00
});
// Register window resize callback
glfwSetWindowSizeCallback(m_window, [](GLFWwindow *window, [[maybe_unused]] int width, [[maybe_unused]] int height) {
auto win = static_cast<Window *>(glfwGetWindowUserPointer(window));
2023-12-27 21:23:54 +01:00
win->m_unlockFrameRate = true;
#if !defined(OS_WINDOWS)
if (!glfwGetWindowAttrib(window, GLFW_ICONIFIED))
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();
}
#else
win->fullFrame();
#endif
});
#if defined(OS_MACOS)
glfwSetWindowRefreshCallback(m_window, [](GLFWwindow *window) {
auto win = static_cast<Window *>(glfwGetWindowUserPointer(window));
win->fullFrame();
});
#endif
2023-12-27 21:23:54 +01:00
glfwSetCursorPosCallback(m_window, [](GLFWwindow *window, double, double) {
auto win = static_cast<Window *>(glfwGetWindowUserPointer(window));
win->m_unlockFrameRate = true;
});
glfwSetWindowFocusCallback(m_window, [](GLFWwindow *, int focused) {
EventWindowFocused::post(focused == GLFW_TRUE);
});
#if !defined(OS_WEB)
// Register key press callback
glfwSetInputMode(m_window, GLFW_LOCK_KEY_MODS, GLFW_TRUE);
2023-12-19 13:10:25 +01:00
glfwSetKeyCallback(m_window, [](GLFWwindow *window, int key, int scanCode, int action, int mods) {
hex::unused(mods);
// 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.length() == 1) {
key = std::toupper(name[0]);
}
}
if (key == GLFW_KEY_UNKNOWN) return;
2023-05-23 13:20:18 +02:00
if (action == GLFW_PRESS || action == GLFW_REPEAT) {
2023-11-17 14:46:21 +01:00
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
) {
auto win = static_cast<Window *>(glfwGetWindowUserPointer(window));
2023-12-27 21:23:54 +01:00
win->m_unlockFrameRate = true;
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;
}
2023-11-17 14:46:21 +01:00
win->m_pressedKeys.push_back(key);
}
}
});
#endif
2020-11-11 14:41:44 +01:00
// Register window close callback
2023-12-19 13:10:25 +01:00
glfwSetWindowCloseCallback(m_window, [](GLFWwindow *window) {
EventWindowClosing::post(window);
});
2023-12-19 13:10:25 +01:00
glfwSetWindowSizeLimits(m_window, 480_scaled, 360_scaled, GLFW_DONT_CARE, GLFW_DONT_CARE);
2023-12-19 13:10:25 +01:00
glfwShowWindow(m_window);
}
void Window::resize(i32 width, i32 height) {
2023-12-19 13:10:25 +01:00
glfwSetWindowSize(m_window, width, height);
}
void Window::initImGui() {
IMGUI_CHECKVERSION();
2021-02-03 00:56:33 +01:00
auto fonts = ImHexApi::Fonts::getFontAtlas();
if (fonts == nullptr) {
fonts = IM_NEW(ImFontAtlas)();
fonts->AddFontDefault();
fonts->Build();
}
// Initialize ImGui and all other ImGui extensions
2022-02-01 22:09:44 +01:00
GImGui = ImGui::CreateContext(fonts);
GImPlot = ImPlot::CreateContext();
GImNodes = ImNodes::CreateContext();
2021-02-03 00:56:33 +01:00
2022-02-01 22:09:44 +01:00
ImGuiIO &io = ImGui::GetIO();
ImGuiStyle &style = ImGui::GetStyle();
2020-11-23 15:51:40 +01:00
ImNodes::GetStyle().Flags = ImNodesStyleFlags_NodeOutline | ImNodesStyleFlags_GridLines;
io.ConfigFlags |= ImGuiConfigFlags_DockingEnable | ImGuiConfigFlags_NavEnableKeyboard;
io.ConfigWindowsMoveFromTitleBarOnly = true;
2023-02-16 16:29:41 +01:00
io.FontGlobalScale = 1.0F;
if (glfwGetPrimaryMonitor() != nullptr) {
if (ImHexApi::System::isMutliWindowModeEnabled())
io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable;
}
io.ConfigViewportsNoTaskBarIcon = false;
2020-11-23 15:51:40 +01:00
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;
}
2023-12-19 13:10:25 +01:00
io.UserData = &m_imguiCustomData;
auto scale = ImHexApi::System::getGlobalScale();
style.ScaleAllSizes(scale);
io.DisplayFramebufferScale = ImVec2(scale, scale);
io.Fonts->SetTexID(fonts->TexID);
Proper DPI scaling and basic custom font (#85) * add glm to arch deps After running got `None of the required 'glm' found`. This fixes that * dist/fedora: Include file magic headers Due to differences in package names between Deb based systems, Arch Linux, and RPM based systems the package containing the development headers for file were missing from the Fedora dependencies script. This includes the package `file-devel`, which is the package which resolves the issue. In Fedora, one can identify the package providing a specific file using the verb "whatprovides" with the command dnf, e.g.: [~]$ dnf whatprovides /usr/include/magic.h Last metadata expiration check: 4 days, 0:23:05 ago on Fri 04 Dec 2020 09:06:53 AM PST. file-devel-5.39-3.fc33.i686 : Libraries and header files for file development Repo : fedora Matched from: Filename : /usr/include/magic.h file-devel-5.39-3.fc33.x86_64 : Libraries and header files for file development Repo : @System Matched from: Filename : /usr/include/magic.h file-devel-5.39-3.fc33.x86_64 : Libraries and header files for file development Repo : fedora Matched from: Filename : /usr/include/magic.h If one is unsure of the specific path, globbing may be used (but must be quoted): dnf whatprovides "*/magic.h" Resolves #48 * dist: Prevent already installed packages in ArchLinux and MSYS2. Use --needed option with pacman to prevent it. * Add script to install dependencies on Debian/Ubuntu. Tested with Xubuntu 20.04 and Debian testing (in today's Docker image bitnami/minideb). Update README.md. * ci: rework (#31) * Support non standard LLVM library names (#86) This fix openSUSE and Gentoo issue mentioned in https://github.com/WerWolv/ImHex/issues/37#issuecomment-739503138. (tested on openSUSE tumbleweed via Docker) I also took the liberty of renaming llvm_lib to llvm_demangle_lib to be more specific in the ``CMakeLists.txt``. * Implement proper DPI handling * Implement basic custom font support * Fix building on windows * Hopefully fix fonts on Windows * Fix several scaling issues * Replace font renderer with freetype * Updated CI and dependency scripts * Rebuild default font atlas * Correct platform detection macro for mingw * Fixed PKGBUILD Co-authored-by: brockelmore <31553173+brockelmore@users.noreply.github.com> Co-authored-by: Brian 'Redbeard' Harrington <redbeard@dead-city.org> Co-authored-by: Biswapriyo Nath <nathbappai@gmail.com> Co-authored-by: Stéphane Gourichon <stephane.gourichon@fidergo.fr> Co-authored-by: umarcor <38422348+umarcor@users.noreply.github.com> Co-authored-by: Mary <me@thog.eu> Co-authored-by: WerWolv <werwolv98@gmail.com>
2020-12-11 14:24:42 +01:00
2020-11-23 15:51:40 +01:00
style.WindowMenuButtonPosition = ImGuiDir_None;
2022-02-01 22:09:44 +01:00
style.IndentSpacing = 10.0F;
style.DisplaySafeAreaPadding = ImVec2(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;
2023-12-13 11:24:25 +01:00
auto context = ImGui::GetCurrentContext();
context->SettingsHandlers.push_back(handler);
context->TestEngineHookItems = true;
io.IniFilename = nullptr;
}
sys/build: Properly support per-system metadata file paths (#181) * sys: Move away from metadata paths next to executable in the application Build system doesn't properly install / pack stuff yet * build: Updated README to contain better install instructions * sys: Search for imhex resource files in ~/Application Support * sys: MAX_PATH -> PATH_MAX * sys: Seach for imhex resource files in Application Support using NSFileManager (#180) * sys: Allow for multiple file search paths Also use install prefix instead of just /usr on Linux * build: Fixed IMHEX_INSTALL_PREFIX macro definition * build: Fix duplicate switch entry on Linux * docs: Updated readme to properly reflect new paths and dependencies * sys: Install files in their proper paths on linux (#183) * Install files in their proper paths on linux * Only create user directories * Follow the XDG specification on linux XDG specification specifies how to find config and data directories on linux systems. Specifically, it says this: - Data should be written to $XDG_DATA_HOME - Config should be written to $XDG_CONFIG_HOME - Data should be read from $XDG_DATA_HOME:$XDG_DATA_DIRS - Config should be read from $XDG_CONFIG_HOME:$XDG_CONFIG_DIRS The default values are this: - XDG_DATA_HOME: $HOME/.local/share - XDG_CONFIG_HOME: $HOME/.config - XDG_DATA_DIRS: /usr/share:/usr/local/share - XDG_CONFIG_DIRS: /etc/xdg Platforms with non-standard filesystems (like NixOS) will correctly set up those environment variables, allowing softwares to work unmodified. In order to make integration as simple as possible, we use a simple header-only dependency called XDGPP which does all the hard work for us to find the default directories. * Look for plugins in all Plugin Paths If the plugin folder was missing from one of the PluginPaths, we would immediately stop loading plugins. We now keep looking even if one of the path is missing. Co-authored-by: Nichole Mattera <me@nicholemattera.com> Co-authored-by: Robin Lambertz <unfiltered@roblab.la>
2021-03-01 08:56:49 +01:00
2023-12-19 13:10:25 +01:00
ImGui_ImplGlfw_InitForOpenGL(m_window, true);
2021-04-21 20:06:48 +02:00
#if defined(OS_MACOS)
ImGui_ImplOpenGL3_Init("#version 150");
#elif defined(OS_WEB)
ImGui_ImplOpenGL3_Init();
ImGui_ImplGlfw_InstallEmscriptenCanvasResizeCallback("#canvas");
#else
ImGui_ImplOpenGL3_Init("#version 130");
#endif
2021-08-21 00:52:11 +02:00
for (const auto &plugin : PluginManager::getPlugins())
plugin.setImGuiContext(ImGui::GetCurrentContext());
RequestInitThemeHandlers::post();
}
void Window::exitGLFW() {
2023-12-19 13:10:25 +01:00
glfwDestroyWindow(m_window);
glfwTerminate();
2023-12-19 13:10:25 +01:00
m_window = nullptr;
}
void Window::exitImGui() {
ImGui_ImplOpenGL3_Shutdown();
ImGui_ImplGlfw_Shutdown();
2024-03-21 21:28:06 +01:00
2021-03-02 13:48:23 +01:00
ImPlot::DestroyContext();
ImGui::DestroyContext();
}
}