1
0
mirror of synced 2024-11-25 08:10:24 +01:00
ImHex/main/gui/source/window/window.cpp

1265 lines
52 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>
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/helpers/stacktrace.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 <fonts/codicons_font.h>
2021-02-24 22:42:26 +01:00
#include <hex/api/project_file_manager.hpp>
#include <GLFW/glfw3.h>
namespace hex {
using namespace std::literals::chrono_literals;
static std::fs::path s_imguiSettingsPath;
/**
* @brief returns the path to load/save imgui settings to, or an empty path if no location was found
*/
std::fs::path getImGuiSettingsPath() {
return s_imguiSettingsPath;
}
Window::Window() {
stacktrace::initialize();
constexpr static auto openEmergencyPopup = [](const std::string &title){
TaskManager::doLater([title] {
for (const auto &provider : ImHexApi::Provider::getProviders())
ImHexApi::Provider::remove(provider, false);
ImGui::OpenPopup(title.c_str());
});
};
// 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 == "no-builtin-plugin") {
openEmergencyPopup("No Builtin Plugin");
} else if (argument == "multiple-builtin-plugins") {
openEmergencyPopup("Multiple Builtin Plugins");
}
}
}
// Initialize the window
this->initGLFW();
this->initImGui();
this->setupNativeWindow();
this->registerEventHandlers();
this->m_logoTexture = ImGuiExt::Texture(romfs::get("logo.png").span());
ContentRegistry::Settings::impl::store();
EventManager::post<EventSettingsChanged>();
EventManager::post<EventWindowInitialized>();
2023-07-22 21:30:22 +02:00
EventManager::post<EventImHexStartupFinished>();
}
Window::~Window() {
EventManager::unsubscribe<EventProviderDeleted>(this);
EventManager::unsubscribe<RequestCloseImHex>(this);
EventManager::unsubscribe<RequestUpdateWindowTitle>(this);
EventManager::unsubscribe<EventAbnormalTermination>(this);
EventManager::unsubscribe<RequestOpenPopup>(this);
this->exitImGui();
this->exitGLFW();
}
void Window::registerEventHandlers() {
// Initialize default theme
EventManager::post<RequestChangeTheme>("Dark");
// Handle the close window request by telling GLFW to shut down
EventManager::subscribe<RequestCloseImHex>(this, [this](bool noQuestions) {
glfwSetWindowShouldClose(this->m_window, GLFW_TRUE);
if (!noQuestions)
EventManager::post<EventWindowClosing>(this->m_window);
});
// Handle updating the window title
2023-11-10 20:47:08 +01:00
EventManager::subscribe<RequestUpdateWindowTitle>(this, [this] {
std::string title = "ImHex";
if (ProjectFile::hasPath()) {
// If a project is open, show the project name instead of the file name
title += " - Project " + hex::limitStringLength(ProjectFile::getPath().stem().string(), 32);
if (ImHexApi::Provider::isDirty())
title += " (*)";
} else if (ImHexApi::Provider::isValid()) {
auto provider = ImHexApi::Provider::get();
if (provider != nullptr) {
title += " - " + hex::limitStringLength(provider->getName(), 32);
if (provider->isDirty())
title += " (*)";
if (!provider->isWritable())
title += " (Read Only)";
}
}
2021-08-18 22:36:46 +02:00
this->m_windowTitle = title;
if (this->m_window != nullptr)
glfwSetWindowTitle(this->m_window, title.c_str());
});
// Handle opening popups
EventManager::subscribe<RequestOpenPopup>(this, [this](auto name) {
std::scoped_lock lock(this->m_popupMutex);
this->m_popupsToOpen.push_back(name);
});
}
void Window::fullFrame() {
this->m_lastFrameTime = glfwGetTime();
glfwPollEvents();
// Render frame
this->frameBegin();
this->frame();
this->frameEnd();
}
void Window::loop() {
u64 frameCount = 0;
while (!glfwWindowShouldClose(this->m_window)) {
2023-05-11 23:22:06 +02:00
this->m_lastFrameTime = glfwGetTime();
if (!glfwGetWindowAttrib(this->m_window, GLFW_VISIBLE) || glfwGetWindowAttrib(this->m_window, GLFW_ICONIFIED)) {
// If the application is minimized or not visible, don't render anything
glfwWaitEvents();
} else {
// If no events have been received in a while, lower the frame rate
{
// If the mouse is down, the mouse is moving or a popup is open, we don't want to lower the frame rate
bool frameRateUnlocked =
ImGui::IsPopupOpen(ImGuiID(0), ImGuiPopupFlags_AnyPopupId) ||
TaskManager::getRunningTaskCount() > 0 ||
this->m_buttonDown ||
this->m_hadEvent ||
!this->m_pressedKeys.empty() ||
frameCount < 100;
// Calculate the time until the next frame
const double timeout = std::max(0.0, (1.0 / 5.0) - (glfwGetTime() - this->m_lastFrameTime));
// If the frame rate has been unlocked for 5 seconds, lock it again
if ((this->m_lastFrameTime - this->m_frameRateUnlockTime) > 5 && this->m_frameRateTemporarilyUnlocked && !frameRateUnlocked) {
this->m_frameRateTemporarilyUnlocked = false;
}
// If the frame rate is locked, wait for events with a timeout
if (frameRateUnlocked || this->m_frameRateTemporarilyUnlocked) {
if (!this->m_frameRateTemporarilyUnlocked) {
this->m_frameRateTemporarilyUnlocked = true;
this->m_frameRateUnlockTime = this->m_lastFrameTime;
}
} else {
glfwWaitEventsTimeout(timeout);
}
this->m_hadEvent = false;
}
}
this->fullFrame();
frameCount += 1;
// 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
const auto targetFPS = ImHexApi::System::getTargetFPS();
if (targetFPS < 15) {
glfwSwapInterval(1);
} else if (targetFPS > 200) {
glfwSwapInterval(0);
} else {
glfwSwapInterval(0);
const auto frameTime = glfwGetTime() - this->m_lastFrameTime;
const auto targetFrameTime = 1.0 / targetFPS;
if (frameTime < targetFrameTime) {
glfwWaitEventsTimeout(targetFrameTime - frameTime);
}
}
}
}
static void createNestedMenu(std::span<const std::string> menuItems, const Shortcut &shortcut, const std::function<void()> &callback, const std::function<bool()> &enabledCallback) {
const auto &name = menuItems.front();
if (name == ContentRegistry::Interface::impl::SeparatorValue) {
ImGui::Separator();
return;
}
if (name == ContentRegistry::Interface::impl::SubMenuValue) {
callback();
} else if (menuItems.size() == 1) {
if (ImGui::MenuItem(Lang(name), shortcut.toString().c_str(), false, enabledCallback()))
callback();
} else {
bool isSubmenu = *(menuItems.begin() + 1) == ContentRegistry::Interface::impl::SubMenuValue;
if (ImGui::BeginMenu(Lang(name), isSubmenu ? enabledCallback() : true)) {
createNestedMenu({ menuItems.begin() + 1, menuItems.end() }, shortcut, callback, enabledCallback);
ImGui::EndMenu();
}
}
}
void Window::drawTitleBar() const {
auto titleBarHeight = ImGui::GetCurrentWindow()->MenuBarHeight();
auto buttonSize = ImVec2(titleBarHeight * 1.5F, titleBarHeight - 1);
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0));
ImGui::PushStyleColor(ImGuiCol_Button, ImGui::GetColorU32(ImGuiCol_MenuBarBg));
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImGui::GetColorU32(ImGuiCol_ScrollbarGrabActive));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImGui::GetColorU32(ImGuiCol_ScrollbarGrabHovered));
const auto windowSize = ImHexApi::System::getMainWindowSize();
const auto searchBoxSize = ImVec2(std::sqrt(windowSize.x) * 14_scaled, titleBarHeight - 3_scaled);
const auto searchBoxPos = ImVec2((windowSize / 2 - searchBoxSize / 2).x, 3_scaled);
// Custom titlebar buttons implementation for borderless window mode
auto &titleBarButtons = ContentRegistry::Interface::impl::getTitleBarButtons();
// Draw custom title bar buttons
2023-11-10 20:47:08 +01:00
if (!titleBarButtons.empty()) {
ImGui::SetCursorPosX(ImGui::GetWindowWidth() - buttonSize.x * float(4 + titleBarButtons.size()));
if (ImGui::GetCursorPosX() > (searchBoxPos.x + searchBoxSize.x)) {
for (const auto &[icon, tooltip, callback]: titleBarButtons) {
if (ImGuiExt::TitleBarButton(icon.c_str(), buttonSize)) {
callback();
}
ImGuiExt::InfoTooltip(Lang(tooltip));
}
}
}
if (ImHexApi::System::isBorderlessWindowModeEnabled()) {
// Draw minimize, restore and maximize buttons
ImGui::SetCursorPosX(ImGui::GetWindowWidth() - buttonSize.x * 3);
if (ImGuiExt::TitleBarButton(ICON_VS_CHROME_MINIMIZE, buttonSize))
glfwIconifyWindow(this->m_window);
if (glfwGetWindowAttrib(this->m_window, GLFW_MAXIMIZED)) {
if (ImGuiExt::TitleBarButton(ICON_VS_CHROME_RESTORE, buttonSize))
glfwRestoreWindow(this->m_window);
} else {
if (ImGuiExt::TitleBarButton(ICON_VS_CHROME_MAXIMIZE, buttonSize))
glfwMaximizeWindow(this->m_window);
}
impr: Don't force using discrete graphics card on macOS (#1341) <!-- Please provide as much information as possible about what your PR aims to do. PRs with no description will most likely be closed until more information is provided. If you're planing on changing fundamental behaviour or add big new features, please open a GitHub Issue first before starting to work on it. If it's not something big and you still want to contact us about it, feel free to do so ! --> ### Problem description <!-- Describe the bug that you fixed/feature request that you implemented, or link to an existing issue describing it --> When starting ImHex on a MacBook model with both integrated and discrete graphics, it will force the computer to use the discrete graphics card. This causes increased power usage, meaning the fans will spin up, the battery will drain faster, etc. This program is not very graphics intensive, so using the discrete graphics card shouldn't be needed. ### Implementation description <!-- Explain what you did to correct the problem --> I changed the [`GLFW_COCOA_GRAPHICS_SWITCHING`](https://www.glfw.org/docs/latest/window_guide.html#window_hints_osx) setting in GLFW to not enforce using the discrete graphics. ### Screenshots <!-- If your change is visual, take a screenshot showing it. Ideally, make before/after sceenshots --> ### Additional things <!-- Anything else you would like to say --> My editor is configured to automatically remove trailing whitespace, so I hope that those whitespace changes are ok
2023-10-05 08:39:53 +02:00
ImGui::PushStyleColor(ImGuiCol_ButtonActive, 0xFF7A70F1);
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, 0xFF2311E8);
// Draw close button
if (ImGuiExt::TitleBarButton(ICON_VS_CHROME_CLOSE, buttonSize)) {
ImHexApi::System::closeImHex();
}
ImGui::PopStyleColor(2);
}
ImGui::PopStyleColor(3);
ImGui::PopStyleVar();
{
const auto buttonColor = [](float alpha) {
return ImU32(ImColor(ImGui::GetStyleColorVec4(ImGuiCol_DockingEmptyBg) * ImVec4(1, 1, 1, alpha)));
};
ImGui::PushStyleColor(ImGuiCol_Button, buttonColor(0.5F));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, buttonColor(0.7F));
ImGui::PushStyleColor(ImGuiCol_ButtonActive, buttonColor(0.9F));
ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.0_scaled);
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 4_scaled);
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, scaled({ 1, 1 }));
ImGui::SetCursorPos(searchBoxPos);
if (ImGui::Button(this->m_windowTitle.c_str(), searchBoxSize)) {
EventManager::post<EventSearchBoxClicked>();
}
ImGui::PopStyleVar(3);
ImGui::PopStyleColor(3);
}
}
impr: Don't force using discrete graphics card on macOS (#1341) <!-- Please provide as much information as possible about what your PR aims to do. PRs with no description will most likely be closed until more information is provided. If you're planing on changing fundamental behaviour or add big new features, please open a GitHub Issue first before starting to work on it. If it's not something big and you still want to contact us about it, feel free to do so ! --> ### Problem description <!-- Describe the bug that you fixed/feature request that you implemented, or link to an existing issue describing it --> When starting ImHex on a MacBook model with both integrated and discrete graphics, it will force the computer to use the discrete graphics card. This causes increased power usage, meaning the fans will spin up, the battery will drain faster, etc. This program is not very graphics intensive, so using the discrete graphics card shouldn't be needed. ### Implementation description <!-- Explain what you did to correct the problem --> I changed the [`GLFW_COCOA_GRAPHICS_SWITCHING`](https://www.glfw.org/docs/latest/window_guide.html#window_hints_osx) setting in GLFW to not enforce using the discrete graphics. ### Screenshots <!-- If your change is visual, take a screenshot showing it. Ideally, make before/after sceenshots --> ### Additional things <!-- Anything else you would like to say --> My editor is configured to automatically remove trailing whitespace, so I hope that those whitespace changes are ok
2023-10-05 08:39:53 +02:00
2023-11-07 16:40:37 +01:00
static bool isAnyViewOpen() {
const auto &views = ContentRegistry::Views::impl::getEntries();
return std::any_of(views.begin(), views.end(),
[](const auto &entry) {
return entry.second->getWindowOpenState();
});
}
void Window::frameBegin() {
// Start new ImGui Frame
ImGui_ImplOpenGL3_NewFrame();
ImGui_ImplGlfw_NewFrame();
ImGui::NewFrame();
// 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_MenuBar | ImGuiWindowFlags_NoDocking | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoNavFocus | ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse;
ImGui::GetIO().ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
// Render main dock space
2022-02-06 21:39:10 +01:00
if (ImGui::Begin("ImHexDockSpace", nullptr, windowFlags)) {
2022-01-22 22:03:19 +01:00
auto drawList = ImGui::GetWindowDrawList();
2021-08-21 00:52:11 +02:00
ImGui::PopStyleVar();
2023-11-08 11:53:26 +01:00
bool shouldDrawSidebar = [] {
if (const auto &items = ContentRegistry::Interface::impl::getSidebarItems(); items.empty())
return false;
else {
return std::any_of(items.begin(), items.end(), [](const auto &item) {
return item.enabledCallback();
});
}
}();
2023-11-08 11:53:26 +01:00
const auto menuBarHeight = ImGui::GetCurrentWindow()->MenuBarHeight();
auto sidebarPos = ImGui::GetCursorPos() + ImVec2(0, menuBarHeight);
auto sidebarWidth = shouldDrawSidebar ? 20_scaled : 0;
2022-01-22 22:03:19 +01:00
ImGui::SetCursorPosX(sidebarWidth);
2022-02-01 22:09:44 +01:00
auto footerHeight = ImGui::GetTextLineHeightWithSpacing() + ImGui::GetStyle().FramePadding.y * 2 + 1_scaled;
auto dockSpaceSize = ImVec2(ImHexApi::System::getMainWindowSize().x - sidebarWidth, ImGui::GetContentRegionAvail().y - footerHeight);
2022-01-22 22:03:19 +01:00
// Render footer
{
2022-01-22 22:03:19 +01:00
auto dockId = ImGui::DockSpace(ImGui::GetID("ImHexMainDock"), dockSpaceSize);
ImHexApi::System::impl::setMainDockSpaceId(dockId);
drawList->AddRectFilled(ImGui::GetWindowPos(), ImGui::GetWindowPos() + ImGui::GetWindowSize() - ImVec2(dockSpaceSize.x, footerHeight - ImGui::GetStyle().FramePadding.y - 1_scaled), ImGui::GetColorU32(ImGuiCol_MenuBarBg));
ImGui::Separator();
ImGui::SetCursorPosX(8);
for (const auto &callback : ContentRegistry::Interface::impl::getFooterItems()) {
auto prevIdx = drawList->_VtxCurrentIdx;
callback();
auto currIdx = drawList->_VtxCurrentIdx;
// Only draw separator if something was actually drawn
if (prevIdx != currIdx) {
ImGui::SameLine();
ImGui::SeparatorEx(ImGuiSeparatorFlags_Vertical);
ImGui::SameLine();
}
2022-08-04 11:00:49 +02:00
}
}
// Render sidebar
if (shouldDrawSidebar) {
2022-01-22 22:03:19 +01:00
ImGui::SetCursorPos(sidebarPos);
static i32 openWindow = -1;
2023-11-07 16:40:37 +01:00
u32 index = 0;
2022-01-22 22:03:19 +01:00
ImGui::PushID("SideBarWindows");
for (const auto &[icon, callback, enabledCallback] : ContentRegistry::Interface::impl::getSidebarItems()) {
2022-01-22 22:03:19 +01:00
ImGui::SetCursorPosY(sidebarPos.y + sidebarWidth * index);
ImGui::PushStyleColor(ImGuiCol_Button, ImGui::GetColorU32(ImGuiCol_MenuBarBg));
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImGui::GetColorU32(ImGuiCol_ScrollbarGrabActive));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImGui::GetColorU32(ImGuiCol_ScrollbarGrabHovered));
ImGui::BeginDisabled(!(ImHexApi::Provider::isValid() && enabledCallback()));
2022-01-22 22:03:19 +01:00
{
if (ImGui::Button(icon.c_str(), ImVec2(sidebarWidth, sidebarWidth))) {
if (static_cast<u32>(openWindow) == index)
2022-01-22 22:03:19 +01:00
openWindow = -1;
else
openWindow = index;
}
}
ImGui::EndDisabled();
ImGui::PopStyleColor(3);
2023-11-08 11:53:26 +01:00
auto sideBarFocused = ImGui::IsWindowFocused();
bool open = static_cast<u32>(openWindow) == index;
2022-01-22 22:03:19 +01:00
if (open) {
static float width = 200_scaled;
2023-11-08 11:53:26 +01:00
ImGui::SetNextWindowPos(ImGui::GetWindowPos() + sidebarPos + ImVec2(sidebarWidth - 1_scaled, -1_scaled));
ImGui::SetNextWindowSize(ImVec2(width, dockSpaceSize.y + 11_scaled - footerHeight));
2022-01-22 22:03:19 +01:00
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 1);
ImGui::PushStyleColor(ImGuiCol_WindowShadow, 0x00000000);
if (ImGui::Begin("SideBarWindow", &open, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse)) {
if (ImGui::BeginTable("##Table", 2)) {
ImGui::TableSetupColumn("Main", ImGuiTableColumnFlags_WidthStretch, 1.0F);
ImGui::TableSetupColumn("Slider", ImGuiTableColumnFlags_WidthFixed, 1);
ImGui::TableNextRow();
ImGui::TableNextColumn();
callback();
ImGui::TableNextColumn();
static bool dragging = false;
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0));
ImGui::Button("##Resize", ImGui::GetContentRegionAvail());
ImGui::PopStyleVar();
if (ImGui::IsMouseDragging(ImGuiMouseButton_Left, 0)) {
if (ImGui::IsItemHovered())
dragging = true;
} else {
dragging = false;
}
if (ImGui::IsItemHovered()) {
ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeEW);
}
if (dragging) {
width += ImGui::GetMouseDragDelta(ImGuiMouseButton_Left, 0).x;
ImGui::ResetMouseDragDelta(ImGuiMouseButton_Left);
}
ImGui::EndTable();
}
2023-11-08 11:53:26 +01:00
if (!ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows) && !sideBarFocused) {
2023-11-08 11:53:26 +01:00
openWindow = -1;
}
2022-01-22 22:03:19 +01:00
}
ImGui::End();
ImGui::PopStyleVar();
ImGui::PopStyleColor();
2022-01-22 22:03:19 +01:00
}
ImGui::NewLine();
index++;
}
ImGui::PopID();
}
// Render main menu
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0F);
ImGui::SetNextWindowScroll(ImVec2(0, 0));
2021-08-21 00:52:11 +02:00
if (ImGui::BeginMainMenuBar()) {
if (ImHexApi::System::isBorderlessWindowModeEnabled()) {
ImGui::SetCursorPosX(5);
ImGui::Image(this->m_logoTexture, ImVec2(menuBarHeight, menuBarHeight));
ImGui::SetCursorPosX(5);
ImGui::InvisibleButton("##logo", ImVec2(menuBarHeight, menuBarHeight));
ImGui::OpenPopupOnItemClick("WindowingMenu", ImGuiPopupFlags_MouseButtonLeft);
}
if (ImGui::BeginPopup("WindowingMenu")) {
bool maximized = glfwGetWindowAttrib(this->m_window, GLFW_MAXIMIZED);
ImGui::BeginDisabled(!maximized);
if (ImGui::MenuItem(ICON_VS_CHROME_RESTORE " Restore")) glfwRestoreWindow(this->m_window);
ImGui::EndDisabled();
if (ImGui::MenuItem(ICON_VS_CHROME_MINIMIZE " Minimize")) glfwIconifyWindow(this->m_window);
ImGui::BeginDisabled(maximized);
if (ImGui::MenuItem(ICON_VS_CHROME_MAXIMIZE " Maximize")) glfwMaximizeWindow(this->m_window);
ImGui::EndDisabled();
ImGui::Separator();
if (ImGui::MenuItem(ICON_VS_CHROME_CLOSE " Close")) ImHexApi::System::closeImHex();
ImGui::EndPopup();
}
2021-08-18 22:36:46 +02:00
const static auto drawMenu = [] {
for (const auto &[priority, menuItem] : ContentRegistry::Interface::impl::getMainMenuItems()) {
ImGui::GetStyle().TouchExtraPadding = scaled(ImVec2(0, 2));
if (ImGui::BeginMenu(Lang(menuItem.unlocalizedName))) {
ImGui::EndMenu();
}
ImGui::GetStyle().TouchExtraPadding = ImVec2(0, 0);
}
2020-11-11 09:22:55 +01:00
for (auto &[priority, menuItem] : ContentRegistry::Interface::impl::getMenuItems()) {
const auto &[unlocalizedNames, shortcut, view, callback, enabledCallback] = menuItem;
createNestedMenu(unlocalizedNames, *shortcut, callback, enabledCallback);
}
};
const auto windowWidth = ImHexApi::System::getMainWindowSize().x;
if (windowWidth > 1200_scaled) {
drawMenu();
} else {
if (ImGui::BeginMenu(ICON_VS_MENU)) {
drawMenu();
ImGui::EndMenu();
}
}
2020-11-11 09:22:55 +01:00
this->drawTitleBar();
ImGui::EndMainMenuBar();
2021-08-21 00:52:11 +02:00
}
ImGui::PopStyleVar();
// Render toolbar
2021-08-21 00:52:11 +02:00
if (ImGui::BeginMenuBar()) {
for (const auto &callback : ContentRegistry::Interface::impl::getToolbarItems()) {
2021-08-21 00:52:11 +02:00
callback();
ImGui::SameLine();
}
ImGui::EndMenuBar();
}
2020-11-11 14:41:44 +01:00
this->beginNativeWindowFrame();
2022-01-22 22:03:19 +01:00
2023-11-07 16:40:37 +01:00
if (ImHexApi::Provider::isValid() && isAnyViewOpen()) {
2023-11-08 11:53:26 +01:00
drawList->AddLine(
ImGui::GetWindowPos() + sidebarPos + ImVec2(sidebarWidth - 1_scaled, -2_scaled),
ImGui::GetWindowPos() + sidebarPos + ImGui::GetWindowSize() - ImVec2(dockSpaceSize.x + 1_scaled, footerHeight - ImGui::GetStyle().FramePadding.y - 1_scaled + menuBarHeight * 2),
ImGui::GetColorU32(ImGuiCol_Separator));
2023-11-07 16:40:37 +01:00
}
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) ? ICON_VS_CHECK : ICON_VS_CLOSE);
}
ImGui::EndTable();
}
};
// No plugins error popup
ImGui::SetNextWindowPos(ImGui::GetMainViewport()->GetCenter(), ImGuiCond_Appearing, ImVec2(0.5F, 0.5F));
if (ImGui::BeginPopupModal("No Plugins", nullptr, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove)) {
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 (ImGui::Button("Close ImHex", ImVec2(ImGui::GetContentRegionAvail().x, 0)))
ImHexApi::System::closeImHex(true);
ImGui::EndPopup();
}
// No built-in plugin error popup
ImGui::SetNextWindowPos(ImGui::GetMainViewport()->GetCenter(), ImGuiCond_Appearing, ImVec2(0.5F, 0.5F));
if (ImGui::BeginPopupModal("No Builtin Plugin", nullptr, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove)) {
ImGui::TextUnformatted("The ImHex built-in plugins could not be loaded!");
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 (ImGui::Button("Close ImHex", ImVec2(ImGui::GetContentRegionAvail().x, 0)))
ImHexApi::System::closeImHex(true);
ImGui::EndPopup();
}
// Multiple built-in plugins error popup
ImGui::SetNextWindowPos(ImGui::GetMainViewport()->GetCenter(), ImGuiCond_Appearing, ImVec2(0.5F, 0.5F));
if (ImGui::BeginPopupModal("Multiple Builtin Plugins", nullptr, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove)) {
ImGui::TextUnformatted("ImHex found and attempted to load multiple built-in plugins!");
ImGui::TextUnformatted("Make sure you installed ImHex correctly and, if needed,");
ImGui::TextUnformatted("cleaned up older installations correctly,");
ImGui::TextUnformatted("There should be exactly one 'builtin.hexplug' file in any one your plugin folders.");
ImGui::NewLine();
drawPluginFolderTable();
ImGui::NewLine();
if (ImGui::Button("Close ImHex", ImVec2(ImGui::GetContentRegionAvail().x, 0)))
ImHexApi::System::closeImHex(true);
ImGui::EndPopup();
}
}
// Open popups when plugins requested it
{
std::scoped_lock lock(this->m_popupMutex);
this->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 popupDisplaying = false;
static bool positionSet = false;
static bool sizeSet = false;
static double popupDelay = -2.0;
static std::unique_ptr<impl::PopupBase> currPopup;
static Lang name("");
2023-04-09 23:24:48 +02:00
if (auto &popups = impl::PopupBase::getOpenPopups(); !popups.empty()) {
if (!ImGui::IsPopupOpen(ImGuiID(0), ImGuiPopupFlags_AnyPopupId)) {
if (popupDelay <= -1.0) {
popupDelay = 200;
} else {
popupDelay -= this->m_lastFrameTime;
if (popupDelay < 0) {
popupDelay = -2.0;
currPopup = std::move(popups.back());
name = Lang(currPopup->getUnlocalizedName());
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) {
currPopup->drawContent();
2023-04-09 23:24:48 +02:00
popupDisplaying = true;
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();
2023-04-09 23:24:48 +02:00
} else {
popupDisplaying = false;
}
};
if (currPopup->isModal())
createPopup(ImGui::BeginPopupModal(name, closeButton, flags));
else
createPopup(ImGui::BeginPopup(name, flags));
if (!popupDisplaying || currPopup->shouldClose()) {
log::debug("Closing popup '{}'", name);
positionSet = sizeSet = false;
currPopup = nullptr;
2023-04-09 23:24:48 +02:00
}
}
}
// Run all deferred calls
TaskManager::runDeferredCalls();
// Draw main menu popups
for (auto &[priority, menuItem] : ContentRegistry::Interface::impl::getMenuItems()) {
const auto &[unlocalizedNames, shortcut, view, callback, enabledCallback] = menuItem;
if (ImGui::BeginPopup(unlocalizedNames.front().c_str())) {
createNestedMenu({ unlocalizedNames.begin() + 1, unlocalizedNames.end() }, *shortcut, callback, enabledCallback);
ImGui::EndPopup();
}
}
EventManager::post<EventFrameBegin>();
}
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;
// Draw view
view->draw();
2023-11-17 14:46:21 +01:00
if (view->getWindowOpenState()) {
2022-02-01 22:09:44 +01:00
auto window = ImGui::FindWindowByName(view->getName().c_str());
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());
}
ImGui::End();
}
// Pass on currently pressed keys to the shortcut handler
for (const auto &key : this->m_pressedKeys) {
ShortcutManager::process(view, io.KeyCtrl, io.KeyAlt, io.KeyShift, io.KeySuper, focused, key);
}
}
}
// Handle global shortcuts
2023-11-17 14:46:21 +01:00
for (const auto &key : this->m_pressedKeys) {
ShortcutManager::processGlobals(io.KeyCtrl, io.KeyAlt, io.KeyShift, io.KeySuper, key);
}
this->m_pressedKeys.clear();
}
void Window::frameEnd() {
EventManager::post<EventFrameEnd>();
// Clean up all tasks that are done
TaskManager::collectGarbage();
this->endNativeWindowFrame();
// Render UI
ImGui::Render();
2020-11-17 13:58:50 +01:00
int displayWidth, displayHeight;
glfwGetFramebufferSize(this->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());
GLFWwindow *backup_current_context = glfwGetCurrentContext();
2020-11-23 15:51:40 +01:00
ImGui::UpdatePlatformWindows();
ImGui::RenderPlatformWindowsDefault();
glfwMakeContextCurrent(backup_current_context);
glfwSwapBuffers(this->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();
}
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() {
bool restoreWindowPos = ContentRegistry::Settings::read("hex.builtin.setting.interface", "hex.builtin.setting.interface.restore_window_pos", false);
glfwSetErrorCallback([](int error, const char *desc) {
if (error == GLFW_PLATFORM_ERROR) {
// Ignore error spam caused by Wayland not supporting moving or resizing
// windows or querying their position and size.
if (std::string_view(desc).contains("Wayland"))
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();
}
// Set up used OpenGL version
#if defined(OS_MACOS)
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
glfwWindowHint(GLFW_COCOA_RETINA_FRAMEBUFFER, GLFW_FALSE);
impr: Don't force using discrete graphics card on macOS (#1341) <!-- Please provide as much information as possible about what your PR aims to do. PRs with no description will most likely be closed until more information is provided. If you're planing on changing fundamental behaviour or add big new features, please open a GitHub Issue first before starting to work on it. If it's not something big and you still want to contact us about it, feel free to do so ! --> ### Problem description <!-- Describe the bug that you fixed/feature request that you implemented, or link to an existing issue describing it --> When starting ImHex on a MacBook model with both integrated and discrete graphics, it will force the computer to use the discrete graphics card. This causes increased power usage, meaning the fans will spin up, the battery will drain faster, etc. This program is not very graphics intensive, so using the discrete graphics card shouldn't be needed. ### Implementation description <!-- Explain what you did to correct the problem --> I changed the [`GLFW_COCOA_GRAPHICS_SWITCHING`](https://www.glfw.org/docs/latest/window_guide.html#window_hints_osx) setting in GLFW to not enforce using the discrete graphics. ### Screenshots <!-- If your change is visual, take a screenshot showing it. Ideally, make before/after sceenshots --> ### Additional things <!-- Anything else you would like to say --> My editor is configured to automatically remove trailing whitespace, so I hope that those whitespace changes are ok
2023-10-05 08:39:53 +02:00
glfwWindowHint(GLFW_COCOA_GRAPHICS_SWITCHING, GLFW_TRUE);
#else
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);
#endif
glfwWindowHint(GLFW_DECORATED, ImHexApi::System::isBorderlessWindowModeEnabled() ? GL_FALSE : GL_TRUE);
2021-04-21 20:06:48 +02:00
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
glfwWindowHint(GLFW_TRANSPARENT_FRAMEBUFFER, GLFW_TRUE);
glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE);
glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_API);
2020-11-30 00:03:12 +01:00
if (restoreWindowPos) {
int maximized = ContentRegistry::Settings::read("hex.builtin.setting.interface", "hex.builtin.setting.interface.window.maximized", GLFW_FALSE);
glfwWindowHint(GLFW_MAXIMIZED, maximized);
}
// Create window
2021-08-18 22:36:46 +02:00
this->m_windowTitle = "ImHex";
2022-02-01 22:09:44 +01:00
this->m_window = glfwCreateWindow(1280_scaled, 720_scaled, this->m_windowTitle.c_str(), nullptr, nullptr);
2021-08-18 22:36:46 +02:00
glfwSetWindowUserPointer(this->m_window, this);
2022-01-13 14:34:27 +01:00
if (this->m_window == nullptr) {
log::fatal("Failed to create window!");
std::abort();
}
// Force window to be fully opaque by default
glfwSetWindowOpacity(this->m_window, 1.0F);
glfwMakeContextCurrent(this->m_window);
glfwSwapInterval(1);
// 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(this->m_window, &windowWidth, &windowHeight);
glfwSetWindowPos(this->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;
glfwGetWindowPos(this->m_window, &x, &y);
if (restoreWindowPos) {
x = ContentRegistry::Settings::read("hex.builtin.setting.interface", "hex.builtin.setting.interface.window.x", x);
y = ContentRegistry::Settings::read("hex.builtin.setting.interface", "hex.builtin.setting.interface.window.y", y);
}
ImHexApi::System::impl::setMainWindowPosition(x, y);
glfwSetWindowPos(this->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;
glfwGetWindowSize(this->m_window, &width, &height);
glfwSetWindowSize(this->m_window, width, height);
if (restoreWindowPos) {
width = ContentRegistry::Settings::read("hex.builtin.setting.interface", "hex.builtin.setting.interface.window.width", width);
height = ContentRegistry::Settings::read("hex.builtin.setting.interface", "hex.builtin.setting.interface.window.height", height);
}
ImHexApi::System::impl::setMainWindowSize(width, height);
glfwSetWindowSize(this->m_window, width, height);
2021-08-18 22:36:46 +02:00
}
// Register window move callback
2021-08-18 22:36:46 +02:00
glfwSetWindowPosCallback(this->m_window, [](GLFWwindow *window, int x, int y) {
ImHexApi::System::impl::setMainWindowPosition(x, y);
if (auto g = ImGui::GetCurrentContext(); g == nullptr || g->WithinFrameScope) return;
auto win = static_cast<Window *>(glfwGetWindowUserPointer(window));
2021-08-18 22:36:46 +02:00
win->frameBegin();
win->frame();
win->frameEnd();
win->processEvent();
2021-08-18 22:36:46 +02:00
});
// Register window resize callback
glfwSetWindowSizeCallback(this->m_window, [](GLFWwindow *window, int width, int height) {
if (!glfwGetWindowAttrib(window, GLFW_ICONIFIED))
ImHexApi::System::impl::setMainWindowSize(width, height);
if (auto g = ImGui::GetCurrentContext(); g == nullptr || g->WithinFrameScope) return;
2021-08-18 22:36:46 +02:00
auto win = static_cast<Window *>(glfwGetWindowUserPointer(window));
win->frameBegin();
win->frame();
win->frameEnd();
win->processEvent();
});
// Register mouse handling callback
ui/ux: Rewrite of the entire hex editor view to make it more flexible (#512) * ui/ux: Initial recreation of the hex editor view * ui/ux: Added back support for editing cells * ux: Make scrolling and selecting bytes feel nice again * ui/ux: Improved byte selecting, added footer * sys: Make math evaluator more generic to support integer only calculations * patterns: Moved value formatting into pattern language * ui/ux: Added Goto and Search popups, improved selection * ui: Added better tooltips for bookmarks and patterns * sys: Use worse hex search algorithm on macOS Sadly it still doesn't support `std::boyer_moore_horsepool_searcher` * ui: Added back missing events, menu items and shortcuts * fix: Bookmark highlighting being rendered off by one * fix: Various macOS build errors * fix: size_t is not u64 on macos * fix: std::fmod and std::pow not working with integer types on macos * fix: Missing semicolons * sys: Added proper integer pow function * ui: Added back support for custom encodings * fix: Editor not jumping to selection when selection gets changed * ui: Turn Hexii setting into a data visualizer * sys: Added back remaining shortcuts * sys: Remove old hex editor files * sys: Moved more legacy things away from the hex editor view, updated localization * fix: Hex editor scrolling behaving weirdly and inconsistently * sys: Cleaned up Hex editor code * sys: Added selection color setting, localized all new settings * fix: Search feature not working correctly * ui: Replace custom ImGui::Disabled function with native ImGui ones * ui: Fix bookmark tooltip rendering issues * fix: Another size_t not being 64 bit issue on MacOS
2022-05-27 20:42:07 +02:00
glfwSetMouseButtonCallback(this->m_window, [](GLFWwindow *window, int button, int action, int mods) {
hex::unused(button, mods);
auto win = static_cast<Window *>(glfwGetWindowUserPointer(window));
if (action == GLFW_PRESS)
win->m_buttonDown = true;
ui/ux: Rewrite of the entire hex editor view to make it more flexible (#512) * ui/ux: Initial recreation of the hex editor view * ui/ux: Added back support for editing cells * ux: Make scrolling and selecting bytes feel nice again * ui/ux: Improved byte selecting, added footer * sys: Make math evaluator more generic to support integer only calculations * patterns: Moved value formatting into pattern language * ui/ux: Added Goto and Search popups, improved selection * ui: Added better tooltips for bookmarks and patterns * sys: Use worse hex search algorithm on macOS Sadly it still doesn't support `std::boyer_moore_horsepool_searcher` * ui: Added back missing events, menu items and shortcuts * fix: Bookmark highlighting being rendered off by one * fix: Various macOS build errors * fix: size_t is not u64 on macos * fix: std::fmod and std::pow not working with integer types on macos * fix: Missing semicolons * sys: Added proper integer pow function * ui: Added back support for custom encodings * fix: Editor not jumping to selection when selection gets changed * ui: Turn Hexii setting into a data visualizer * sys: Added back remaining shortcuts * sys: Remove old hex editor files * sys: Moved more legacy things away from the hex editor view, updated localization * fix: Hex editor scrolling behaving weirdly and inconsistently * sys: Cleaned up Hex editor code * sys: Added selection color setting, localized all new settings * fix: Search feature not working correctly * ui: Replace custom ImGui::Disabled function with native ImGui ones * ui: Fix bookmark tooltip rendering issues * fix: Another size_t not being 64 bit issue on MacOS
2022-05-27 20:42:07 +02:00
else if (action == GLFW_RELEASE)
win->m_buttonDown = false;
win->processEvent();
ui/ux: Rewrite of the entire hex editor view to make it more flexible (#512) * ui/ux: Initial recreation of the hex editor view * ui/ux: Added back support for editing cells * ux: Make scrolling and selecting bytes feel nice again * ui/ux: Improved byte selecting, added footer * sys: Make math evaluator more generic to support integer only calculations * patterns: Moved value formatting into pattern language * ui/ux: Added Goto and Search popups, improved selection * ui: Added better tooltips for bookmarks and patterns * sys: Use worse hex search algorithm on macOS Sadly it still doesn't support `std::boyer_moore_horsepool_searcher` * ui: Added back missing events, menu items and shortcuts * fix: Bookmark highlighting being rendered off by one * fix: Various macOS build errors * fix: size_t is not u64 on macos * fix: std::fmod and std::pow not working with integer types on macos * fix: Missing semicolons * sys: Added proper integer pow function * ui: Added back support for custom encodings * fix: Editor not jumping to selection when selection gets changed * ui: Turn Hexii setting into a data visualizer * sys: Added back remaining shortcuts * sys: Remove old hex editor files * sys: Moved more legacy things away from the hex editor view, updated localization * fix: Hex editor scrolling behaving weirdly and inconsistently * sys: Cleaned up Hex editor code * sys: Added selection color setting, localized all new settings * fix: Search feature not working correctly * ui: Replace custom ImGui::Disabled function with native ImGui ones * ui: Fix bookmark tooltip rendering issues * fix: Another size_t not being 64 bit issue on MacOS
2022-05-27 20:42:07 +02:00
});
// Register scrolling callback
glfwSetScrollCallback(this->m_window, [](GLFWwindow *window, double xOffset, double yOffset) {
hex::unused(xOffset, yOffset);
auto win = static_cast<Window *>(glfwGetWindowUserPointer(window));
win->processEvent();
});
#if !defined(OS_WEB)
// Register key press callback
glfwSetKeyCallback(this->m_window, [](GLFWwindow *window, int key, int scanCode, int action, int mods) {
hex::unused(mods);
auto win = static_cast<Window *>(glfwGetWindowUserPointer(window));
if (action == GLFW_RELEASE) {
win->m_buttonDown = false;
} else {
win->m_buttonDown = true;
}
// 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
) {
win->m_pressedKeys.push_back(key);
}
}
win->processEvent();
});
#endif
2020-11-11 14:41:44 +01:00
// Register cursor position callback
glfwSetCursorPosCallback(this->m_window, [](GLFWwindow *window, double x, double y) {
hex::unused(x, y);
auto win = static_cast<Window *>(glfwGetWindowUserPointer(window));
win->processEvent();
});
// Register window close callback
glfwSetWindowCloseCallback(this->m_window, [](GLFWwindow *window) {
EventManager::post<EventWindowClosing>(window);
});
// Register file drop callback
glfwSetDropCallback(this->m_window, [](GLFWwindow *, int count, const char **paths) {
// Loop over all dropped files
for (int i = 0; i < count; i++) {
auto path = std::fs::path(reinterpret_cast<const char8_t *>(paths[i]));
// Check if a custom file handler can handle the file
2022-01-13 14:34:27 +01:00
bool handled = false;
for (const auto &[extensions, handler] : ContentRegistry::FileHandler::impl::getEntries()) {
2022-01-13 14:34:27 +01:00
for (const auto &extension : extensions) {
if (path.extension() == extension) {
// Pass the file to the handler and check if it was successful
if (!handler(path)) {
log::error("Handler for extensions '{}' failed to process file!", extension);
break;
}
2022-01-13 14:34:27 +01:00
handled = true;
}
}
}
2022-01-13 14:34:27 +01:00
// If no custom handler was found, just open the file regularly
2022-01-13 14:34:27 +01:00
if (!handled)
EventManager::post<RequestOpenFile>(path);
}
2020-11-17 13:58:50 +01:00
});
glfwSetWindowSizeLimits(this->m_window, 480_scaled, 360_scaled, GLFW_DONT_CARE, GLFW_DONT_CARE);
glfwShowWindow(this->m_window);
}
void Window::resize(i32 width, i32 height) {
glfwSetWindowSize(this->m_window, width, height);
}
void Window::initImGui() {
IMGUI_CHECKVERSION();
2021-02-03 00:56:33 +01:00
auto fonts = ImHexApi::Fonts::getFontAtlas();
// 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) {
bool multiWindowEnabled = ContentRegistry::Settings::read("hex.builtin.setting.interface", "hex.builtin.setting.interface.multi_windows", false);
if (multiWindowEnabled)
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;
}
io.UserData = &this->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; };
2023-12-02 14:35:44 +01:00
handler.ReadLineFn = [](ImGuiContext *, ImGuiSettingsHandler *handler, void *, const char *line) {
Window* window = static_cast<Window*>(handler->UserData);
for (auto &[name, view] : ContentRegistry::Views::impl::getEntries()) {
std::string format = view->getUnlocalizedName() + "=%d";
sscanf(line, format.c_str(), &view->getWindowOpenState());
}
for (auto &[name, function, detached] : ContentRegistry::Tools::impl::getEntries()) {
std::string format = name + "=%d";
sscanf(line, format.c_str(), &detached);
}
2023-12-02 14:35:44 +01:00
int width = 0, height = 0;
sscanf(line, "MainWindowSize=%d,%d", &width, &height);
if (width > 0 && height > 0) {
TaskManager::doLater([width, height, window]{
glfwSetWindowSize(window->m_window, width, height);
});
}
};
handler.WriteAllFn = [](ImGuiContext *, ImGuiSettingsHandler *handler, ImGuiTextBuffer *buf) {
buf->appendf("[%s][General]\n", handler->TypeName);
for (auto &[name, view] : ContentRegistry::Views::impl::getEntries()) {
buf->appendf("%s=%d\n", name.c_str(), view->getWindowOpenState());
}
for (auto &[name, function, detached] : ContentRegistry::Tools::impl::getEntries()) {
buf->appendf("%s=%d\n", name.c_str(), detached);
}
buf->append("\n");
};
handler.UserData = this;
ImGui::GetCurrentContext()->SettingsHandlers.push_back(handler);
io.IniFilename = nullptr;
for (const auto &dir : fs::getDefaultPaths(fs::ImHexPath::Config)) {
if (std::fs::exists(dir) && (fs::isPathWritable(dir))) {
s_imguiSettingsPath = dir / "interface.ini";
break;
}
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
}
if (!s_imguiSettingsPath.empty() && wolv::io::fs::exists(s_imguiSettingsPath)) {
ImGui::LoadIniSettingsFromDisk(wolv::util::toUTF8String(s_imguiSettingsPath).c_str());
}
}
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
ImGui_ImplGlfw_InitForOpenGL(this->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();
#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());
EventManager::post<RequestInitThemeHandlers>();
}
void Window::exitGLFW() {
{
int x = 0, y = 0, width = 0, height = 0, maximized = 0;
glfwGetWindowPos(this->m_window, &x, &y);
glfwGetWindowSize(this->m_window, &width, &height);
maximized = glfwGetWindowAttrib(this->m_window, GLFW_MAXIMIZED);
ContentRegistry::Settings::write("hex.builtin.setting.interface", "hex.builtin.setting.interface.window.x", x);
ContentRegistry::Settings::write("hex.builtin.setting.interface", "hex.builtin.setting.interface.window.y", y);
ContentRegistry::Settings::write("hex.builtin.setting.interface", "hex.builtin.setting.interface.window.width", width);
ContentRegistry::Settings::write("hex.builtin.setting.interface", "hex.builtin.setting.interface.window.height", height);
ContentRegistry::Settings::write("hex.builtin.setting.interface", "hex.builtin.setting.interface.window.maximized", maximized);
}
glfwDestroyWindow(this->m_window);
glfwTerminate();
this->m_window = nullptr;
}
void Window::exitImGui() {
ImGui::SaveIniSettingsToDisk(wolv::util::toUTF8String(s_imguiSettingsPath).c_str());
ImGui_ImplOpenGL3_Shutdown();
ImGui_ImplGlfw_Shutdown();
2021-03-02 13:48:23 +01:00
ImPlot::DestroyContext();
ImGui::DestroyContext();
}
}