diff --git a/lib/libimhex/CMakeLists.txt b/lib/libimhex/CMakeLists.txt index 9932a189c..731555855 100644 --- a/lib/libimhex/CMakeLists.txt +++ b/lib/libimhex/CMakeLists.txt @@ -14,6 +14,7 @@ set(LIBIMHEX_SOURCES source/api/localization.cpp source/api/project_file_manager.cpp source/api/theme_manager.cpp + source/api/layout_manager.cpp source/data_processor/attribute.cpp source/data_processor/link.cpp diff --git a/lib/libimhex/include/hex/api/layout_manager.hpp b/lib/libimhex/include/hex/api/layout_manager.hpp new file mode 100644 index 000000000..20fbae363 --- /dev/null +++ b/lib/libimhex/include/hex/api/layout_manager.hpp @@ -0,0 +1,38 @@ +#pragma once + +#include +#include + +#include +#include + +#include +#include + +namespace hex { + + class LayoutManager { + public: + struct Layout { + std::string name; + std::fs::path path; + }; + + static void save(const std::string &name); + static void load(const std::fs::path &path); + static void loadString(const std::string &content); + + static std::vector getLayouts(); + + static void process(); + static void reload(); + + private: + LayoutManager() = default; + + static std::optional s_layoutPathToLoad; + static std::optional s_layoutStringToLoad; + static std::vector s_layouts; + }; + +} \ No newline at end of file diff --git a/lib/libimhex/include/hex/helpers/fs.hpp b/lib/libimhex/include/hex/helpers/fs.hpp index 98522f291..939d57290 100644 --- a/lib/libimhex/include/hex/helpers/fs.hpp +++ b/lib/libimhex/include/hex/helpers/fs.hpp @@ -40,6 +40,7 @@ namespace hex::fs { Themes, Libraries, Nodes, + Layouts, END }; diff --git a/lib/libimhex/source/api/layout_manager.cpp b/lib/libimhex/source/api/layout_manager.cpp new file mode 100644 index 000000000..f203d1521 --- /dev/null +++ b/lib/libimhex/source/api/layout_manager.cpp @@ -0,0 +1,75 @@ +#include + +#include +#include + +#include + +namespace hex { + + std::optional LayoutManager::s_layoutPathToLoad; + std::optional LayoutManager::s_layoutStringToLoad; + std::vector LayoutManager::s_layouts; + + void LayoutManager::load(const std::fs::path &path) { + s_layoutPathToLoad = path; + } + + void LayoutManager::loadString(const std::string &content) { + s_layoutStringToLoad = content; + } + + void LayoutManager::save(const std::string &name) { + auto fileName = name; + fileName = wolv::util::replaceStrings(fileName, " ", "_"); + std::transform(fileName.begin(), fileName.end(), fileName.begin(), tolower); + fileName += ".hexlyt"; + + const auto path = hex::fs::getDefaultPaths(fs::ImHexPath::Layouts).front() / fileName; + ImGui::SaveIniSettingsToDisk(wolv::util::toUTF8String(path).c_str()); + + LayoutManager::reload(); + } + + std::vector LayoutManager::getLayouts() { + return s_layouts; + } + + void LayoutManager::process() { + if (s_layoutPathToLoad.has_value()) { + ImGui::LoadIniSettingsFromDisk(wolv::util::toUTF8String(*s_layoutPathToLoad).c_str()); + s_layoutPathToLoad = std::nullopt; + } + + if (s_layoutStringToLoad.has_value()) { + ImGui::LoadIniSettingsFromMemory(s_layoutStringToLoad->c_str()); + s_layoutStringToLoad = std::nullopt; + } + } + + void LayoutManager::reload() { + s_layouts.clear(); + + for (const auto &directory : hex::fs::getDefaultPaths(fs::ImHexPath::Layouts)) { + for (const auto &entry : std::fs::directory_iterator(directory)) { + const auto &path = entry.path(); + + if (path.extension() != ".hexlyt") + continue; + + auto name = path.stem().string(); + name = wolv::util::replaceStrings(name, "_", " "); + for (size_t i = 0; i < name.size(); i++) { + if (i == 0 || name[i - 1] == '_') + name[i] = char(std::toupper(name[i])); + } + + s_layouts.push_back({ + name, + path + }); + } + } + } + +} \ No newline at end of file diff --git a/lib/libimhex/source/helpers/fs.cpp b/lib/libimhex/source/helpers/fs.cpp index ba446495a..a92bd024e 100644 --- a/lib/libimhex/source/helpers/fs.cpp +++ b/lib/libimhex/source/helpers/fs.cpp @@ -214,6 +214,9 @@ namespace hex::fs { case ImHexPath::Themes: result = appendPath(getDataPaths(), "themes"); break; + case ImHexPath::Layouts: + result = appendPath(getDataPaths(), "layouts"); + break; } if (!listNonExisting) { diff --git a/main/source/window/window.cpp b/main/source/window/window.cpp index cced947e5..657ce93f8 100644 --- a/main/source/window/window.cpp +++ b/main/source/window/window.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include @@ -690,6 +691,10 @@ namespace hex { 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(); } void Window::initGLFW() { diff --git a/plugins/builtin/include/content/popups/popup_text_input.hpp b/plugins/builtin/include/content/popups/popup_text_input.hpp new file mode 100644 index 000000000..f90bb601e --- /dev/null +++ b/plugins/builtin/include/content/popups/popup_text_input.hpp @@ -0,0 +1,62 @@ +#include + +#include + +#include +#include + +namespace hex::plugin::builtin { + + class PopupTextInput : public Popup { + public: + PopupTextInput(std::string unlocalizedName, std::string message, std::function function) + : hex::Popup(std::move(unlocalizedName), false), + m_message(std::move(message)), m_function(std::move(function)) { } + + void drawContent() override { + ImGui::TextFormattedWrapped("{}", this->m_message.c_str()); + ImGui::NewLine(); + + ImGui::PushItemWidth(-1); + ImGui::SetKeyboardFocusHere(); + ImGui::InputTextIcon("##input", ICON_VS_SYMBOL_KEY, this->m_input); + ImGui::PopItemWidth(); + + ImGui::NewLine(); + ImGui::Separator(); + + auto width = ImGui::GetWindowWidth(); + ImGui::SetCursorPosX(width / 9); + if (ImGui::Button("hex.builtin.common.okay"_lang, ImVec2(width / 3, 0)) || ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Enter))) { + this->m_function(this->m_input); + this->close(); + } + ImGui::SameLine(); + ImGui::SetCursorPosX(width / 9 * 5); + if (ImGui::Button("hex.builtin.common.cancel"_lang, ImVec2(width / 3, 0)) || ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Escape))) { + this->close(); + } + + ImGui::SetWindowPos((ImHexApi::System::getMainWindowSize() - ImGui::GetWindowSize()) / 2, ImGuiCond_Appearing); + } + + [[nodiscard]] ImGuiWindowFlags getFlags() const override { + return ImGuiWindowFlags_AlwaysAutoResize; + } + + [[nodiscard]] ImVec2 getMinSize() const override { + return scaled({ 400, 100 }); + } + + [[nodiscard]] ImVec2 getMaxSize() const override { + return scaled({ 600, 300 }); + } + + private: + std::string m_input; + + std::string m_message; + std::function m_function; + }; + +} \ No newline at end of file diff --git a/plugins/builtin/romfs/layouts/default.hexlyt b/plugins/builtin/romfs/layouts/default.hexlyt new file mode 100644 index 000000000..48dd82b1d --- /dev/null +++ b/plugins/builtin/romfs/layouts/default.hexlyt @@ -0,0 +1,389 @@ +[Window][ImHexDockSpace] +Pos=0,38 +Size=2560,1513 +Collapsed=0 + +[Window][Debug##Default] +Pos=162,46 +Size=400,400 +Collapsed=0 + +[Window][Question] +Pos=729,657 +Size=400,105 +Collapsed=0 + +[Window][###hex.builtin.view.settings.name] +Pos=259,200 +Size=700,400 +Collapsed=0 + +[Window][###hex.builtin.view.hex_editor.name] +Pos=0,76 +Size=1140,980 +Collapsed=0 +DockId=0x00000007,0 + +[Window][###hex.builtin.view.data_inspector.name] +Pos=1142,76 +Size=553,980 +Collapsed=0 +DockId=0x00000008,0 + +[Window][###hex.builtin.view.pattern_data.name] +Pos=0,1058 +Size=1695,445 +Collapsed=0 +DockId=0x00000006,0 + +[Window][###hex.builtin.view.pattern_editor.name] +Pos=1697,76 +Size=863,1427 +Collapsed=0 +DockId=0x00000004,0 + +[Window][###hex.builtin.view.hashes.name] +Pos=1697,76 +Size=863,1427 +Collapsed=0 +DockId=0x00000004,4 + +[Window][###hex.builtin.view.find.name] +Pos=1697,76 +Size=863,1427 +Collapsed=0 +DockId=0x00000004,3 + +[Window][###hex.builtin.view.bookmarks.name] +Pos=1697,76 +Size=863,1427 +Collapsed=0 +DockId=0x00000004,2 + +[Window][Restore lost data] +Pos=498,309 +Size=283,101 +Collapsed=0 + +[Window][###hex.builtin.view.information.name] +Pos=-1889,-120 +Size=510,420 +Collapsed=0 + +[Window][Accept pattern] +Pos=598,267 +Size=311,251 +Collapsed=0 + +[Window][Error] +Pos=413,296 +Size=400,100 +Collapsed=0 + +[Window][Exit Application?] +Pos=470,301 +Size=339,118 +Collapsed=0 + +[Window][Close Provider?] +ViewportPos=-327,0 +ViewportId=0x47432683 +Size=346,144 +Collapsed=0 + +[Window][Choose file] +Pos=-1500,152 +Size=516,458 +Collapsed=0 + +[Window][###hex.builtin.view.yara.name] +Pos=720,38 +Size=560,660 +Collapsed=0 + +[Window][hex.builtin.popup.waiting_for_tasks.title] +Pos=481,308 +Size=318,103 +Collapsed=0 + +[Window][Waiting for Tasks] +Pos=316,308 +Size=353,122 +Collapsed=0 + +[Window][###hex.builtin.view.store.name] +Pos=562,201 +Size=900,700 +Collapsed=0 + +[Window][###hex.builtin.view.diff.name] +Pos=1233,38 +Size=687,1021 +Collapsed=0 + +[Window][###hex.builtin.view.provider_settings.name] +Pos=822,38 +Size=637,720 +Collapsed=0 + +[Window][###hex.builtin.view.provider_settings.load_popup] +Pos=585,322 +Size=314,208 +Collapsed=0 + +[Window][###hex.builtin.view.patches.name] +Pos=-1889,-120 +Size=510,420 +Collapsed=0 + +[Window][###hex.builtin.view.data_processor.name] +Pos=1697,76 +Size=863,1427 +Collapsed=0 +DockId=0x00000004,1 + +[Window][###hex.builtin.view.tools.name] +ViewportPos=952,218 +ViewportId=0x9B385D8A +Size=1306,1600 +Collapsed=0 + +[Window][###hex.builtin.view.theme_manager.name] +Pos=391,302 +Size=469,400 +Collapsed=0 + +[Window][Section] +ViewportPos=380,240 +ViewportId=0xE2CE4373 +Size=600,700 +Collapsed=0 + +[Window][###hex.builtin.tools.invariant_multiplication] +Pos=-1826,300 +Size=600,232 +Collapsed=0 + +[Window][###hex.builtin.tools.regex_replacer] +Pos=-2091,104 +Size=600,305 +Collapsed=0 + +[Window][###hex.builtin.tools.calc] +Pos=-1811,118 +Size=600,402 +Collapsed=0 + +[Window][###hex.builtin.tools.permissions] +Pos=423,296 +Size=600,220 +Collapsed=0 + +[Window][ImPlot Demo] +Pos=1647,388 +Size=600,741 +Collapsed=0 + +[Window][Dear ImGui Debug Log] +Pos=60,152 +Size=1166,312 +Collapsed=0 + +[Window][Dear ImGui Stack Tool] +Pos=60,152 +Size=707,208 +Collapsed=0 + +[Window][Dear ImGui Metrics/Debugger] +Pos=74,297 +Size=677,614 +Collapsed=0 + +[Window][Dear ImGui Style Editor] +ViewportPos=280,340 +ViewportId=0x6D551092 +Size=678,1804 +Collapsed=0 + +[Window][Dear ImGui Demo] +Pos=650,158 +Size=550,680 +Collapsed=0 + +[Window][###hex.builtin.view.help.about.name] +Pos=214,201 +Size=600,350 +Collapsed=0 + +[Window][mal.malcore.popup.api_key] +Pos=465,304 +Size=350,111 +Collapsed=0 + +[Window][Malcore Analysis] +Pos=592,38 +Size=688,660 +Collapsed=0 + +[Window][Enter your Malcore API Key] +Pos=2065,148 +Size=350,124 +Collapsed=0 + +[Window][Malcore Upload] +Pos=276,261 +Size=350,101 +Collapsed=0 + +[Window][###hex.builtin.view.constants.name] +Pos=419,-124 +Size=652,660 +Collapsed=0 + +[Window][###hex.builtin.view.disassembler.name] +Pos=1004,-204 +Size=1336,1320 +Collapsed=0 + +[Window][Test] +ViewportPos=-888,391 +ViewportId=0x784DD132 +Size=731,257 +Collapsed=0 + +[Window][ABCD] +Pos=60,60 +Size=52,54 +Collapsed=0 + +[Window][Information] +Pos=440,310 +Size=400,100 +Collapsed=0 + +[Window][Fatal Error] +Pos=440,310 +Size=400,100 +Collapsed=0 + +[Window][hex.builtin.popup.update_store.name] +Pos=440,285 +Size=400,150 +Collapsed=0 + +[Window][hex.builtin.popup.docs_question.title] +Pos=864,462 +Size=832,516 +Collapsed=0 + +[Window][Documentation query] +Pos=664,312 +Size=1232,816 +Collapsed=0 + +[Window][hex.builtin.popup.save_layout.title] +Pos=880,641 +Size=800,264 +Collapsed=0 + +[Table][0x7EE28D79,6] +Column 0 Weight=1.2067 +Column 1 Weight=0.7016 +Column 2 Weight=1.2516 Sort=0v +Column 3 Weight=0.4715 +Column 4 Weight=0.8026 +Column 5 Weight=1.5659 + +[Table][0x6A4694E4,3] +RefScale=26 +Column 0 Sort=0v + +[Table][0x8A6C34FA,4] +Column 0 Weight=1.0000 +Column 1 Weight=1.0000 +Column 2 Weight=1.0000 +Column 3 Weight=1.0000 Sort=0^ + +[Table][0x86997068,3] +Column 0 Weight=1.0000 Sort=0v +Column 1 Weight=1.0000 +Column 2 Weight=1.0000 + +[Table][0x6190819E,3] +Column 0 Weight=0.7508 +Column 1 Weight=1.2447 +Column 2 Weight=1.0044 + +[Table][0x204AA40F,6] +Column 0 Weight=0.9981 +Column 1 Weight=0.5784 +Column 2 Weight=0.8393 Sort=0v +Column 3 Weight=0.7259 +Column 4 Weight=0.7486 +Column 5 Weight=2.1096 + +[Table][0x92C7E695,3] +Column 0 Weight=1.0000 +Column 1 Weight=1.0000 +Column 2 Weight=1.0000 + +[Table][0xDB02BA59,4] +Column 0 Weight=1.0000 Sort=0v +Column 1 Weight=1.0000 +Column 2 Weight=1.0000 +Column 3 Weight=1.0000 + +[Table][0x22CDC436,4] +Column 0 Weight=1.0000 +Column 1 Weight=1.0000 +Column 2 Weight=1.0000 +Column 3 Weight=1.0000 + +[Docking][Data] +DockSpace ID=0x81A8BB71 Window=0xF9B0A590 Pos=161,350 Size=2560,1427 Split=X + DockNode ID=0x00000001 Parent=0x81A8BB71 SizeRef=847,0 Split=Y + DockNode ID=0x00000005 Parent=0x00000001 SizeRef=0,44 Split=X Selected=0x5708C63F + DockNode ID=0x00000007 Parent=0x00000005 SizeRef=1140,701 Selected=0x5708C63F + DockNode ID=0x00000008 Parent=0x00000005 SizeRef=553,701 Selected=0xFBA7A393 + DockNode ID=0x00000006 Parent=0x00000001 SizeRef=0,20 Selected=0x7AD1CDDD + DockNode ID=0x00000002 Parent=0x81A8BB71 SizeRef=431,0 Split=X + DockNode ID=0x00000003 Parent=0x00000002 SizeRef=12,0 + DockNode ID=0x00000004 Parent=0x00000002 SizeRef=52,0 Selected=0xCACA884B + +[ImHex][General] +hex.builtin.view.bookmarks.name=1 +hex.builtin.view.command_palette.name=0 +hex.builtin.view.constants.name=0 +hex.builtin.view.data_inspector.name=1 +hex.builtin.view.data_processor.name=1 +hex.builtin.view.diff.name=0 +hex.builtin.view.disassembler.name=0 +hex.builtin.view.find.name=1 +hex.builtin.view.hashes.name=1 +hex.builtin.view.help.about.name=0 +hex.builtin.view.hex_editor.name=1 +hex.builtin.view.information.name=0 +hex.builtin.view.patches.name=0 +hex.builtin.view.pattern_data.name=1 +hex.builtin.view.pattern_editor.name=1 +hex.builtin.view.provider_settings.name=0 +hex.builtin.view.settings.name=0 +hex.builtin.view.store.name=0 +hex.builtin.view.theme_manager.name=0 +hex.builtin.view.tools.name=0 +hex.builtin.view.yara.name=0 +hex.windows.view.tty_console.name=0 +hex.builtin.tools.demangler=0 +hex.builtin.tools.ascii_table=0 +hex.builtin.tools.regex_replacer=0 +hex.builtin.tools.color=0 +hex.builtin.tools.calc=0 +hex.builtin.tools.base_converter=0 +hex.builtin.tools.byte_swapper=0 +hex.builtin.tools.permissions=0 +hex.builtin.tools.file_uploader=0 +hex.builtin.tools.wiki_explain=0 +hex.builtin.tools.file_tools=0 +hex.builtin.tools.ieee754=0 +hex.builtin.tools.invariant_multiplication=0 + diff --git a/plugins/builtin/source/content/main_menu_items.cpp b/plugins/builtin/source/content/main_menu_items.cpp index e7c086bcd..2828ba230 100644 --- a/plugins/builtin/source/content/main_menu_items.cpp +++ b/plugins/builtin/source/content/main_menu_items.cpp @@ -6,16 +6,20 @@ #include #include #include +#include #include #include -#include "content/global_actions.hpp" +#include #include #include +#include #include +#include + using namespace std::literals::string_literals; namespace hex::plugin::builtin { @@ -432,21 +436,26 @@ namespace hex::plugin::builtin { } static void createLayoutMenu() { + LayoutManager::reload(); + ContentRegistry::Interface::registerMainMenuItem("hex.builtin.menu.layout", 4000); - ContentRegistry::Interface::addMenuItemSubMenu({ "hex.builtin.menu.layout" }, 1000, [] { - for (auto &[layoutName, func] : ContentRegistry::Interface::impl::getLayouts()) { - if (ImGui::MenuItem(LangEntry(layoutName), "", false, ImHexApi::Provider::isValid())) { - auto dock = ImHexApi::System::getMainDockSpaceId(); + ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.layout", "hex.builtin.menu.layout.save" }, 1100, Shortcut::None, [] { + PopupTextInput::open("hex.builtin.popup.save_layout.title"_lang, "hex.builtin.popup.save_layout.desc"_lang, [](const std::string &name) { + LayoutManager::save(name); + }); + }, ImHexApi::Provider::isValid); - for (auto &[viewName, view] : ContentRegistry::Views::impl::getEntries()) { - view->getWindowOpenState() = false; - } + ContentRegistry::Interface::addMenuItemSeparator({ "hex.builtin.menu.layout" }, 1200); - ImGui::DockBuilderRemoveNode(dock); - ImGui::DockBuilderAddNode(dock); - func(dock); - ImGui::DockBuilderFinish(dock); + ContentRegistry::Interface::addMenuItemSubMenu({ "hex.builtin.menu.layout" }, 2000, [] { + if (ImGui::MenuItem("hex.builtin.layouts.default"_lang, "", false, ImHexApi::Provider::isValid())) { + LayoutManager::loadString(std::string(romfs::get("layouts/default.hexlyt").string())); + } + + for (auto &[name, path] : LayoutManager::getLayouts()) { + if (ImGui::MenuItem(name.c_str(), "", false, ImHexApi::Provider::isValid())) { + LayoutManager::load(path); } } }); diff --git a/plugins/builtin/source/content/welcome_screen.cpp b/plugins/builtin/source/content/welcome_screen.cpp index 691b4c75a..727cdf8d7 100644 --- a/plugins/builtin/source/content/welcome_screen.cpp +++ b/plugins/builtin/source/content/welcome_screen.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -192,20 +193,7 @@ namespace hex::plugin::builtin { } static void loadDefaultLayout() { - auto layouts = ContentRegistry::Interface::impl::getLayouts(); - if (!layouts.empty()) { - - for (auto &[viewName, view] : ContentRegistry::Views::impl::getEntries()) { - view->getWindowOpenState() = false; - } - - auto dockId = ImHexApi::System::getMainDockSpaceId(); - - ImGui::DockBuilderRemoveNode(dockId); - ImGui::DockBuilderAddNode(dockId); - layouts.front().callback(dockId); - ImGui::DockBuilderFinish(dockId); - } + LayoutManager::loadString(std::string(romfs::get("layouts/default.hexlyt").string())); } static bool isAnyViewOpen() {