diff --git a/lib/libimhex/CMakeLists.txt b/lib/libimhex/CMakeLists.txt index 5389198ac..f419e6c1b 100644 --- a/lib/libimhex/CMakeLists.txt +++ b/lib/libimhex/CMakeLists.txt @@ -50,6 +50,7 @@ set(LIBIMHEX_SOURCES source/ui/view.cpp source/ui/popup.cpp source/ui/toast.cpp + source/ui/banner.cpp source/subcommands/subcommands.cpp ) diff --git a/lib/libimhex/include/hex/ui/banner.hpp b/lib/libimhex/include/hex/ui/banner.hpp new file mode 100644 index 000000000..f6af6fa90 --- /dev/null +++ b/lib/libimhex/include/hex/ui/banner.hpp @@ -0,0 +1,57 @@ +#pragma once + +#include +#include +#include +#include + +#include +#include +#include +#include "hex/api/localization_manager.hpp" + +namespace hex { + + namespace impl { + + class BannerBase { + public: + BannerBase(ImColor color) : m_color(color) {} + virtual ~BannerBase() = default; + + virtual void draw() { drawContent(); } + virtual void drawContent() = 0; + + [[nodiscard]] static std::list> &getOpenBanners(); + + [[nodiscard]] const ImColor& getColor() const { + return m_color; + } + + void close() { m_shouldClose = true; } + [[nodiscard]] bool shouldClose() const { return m_shouldClose; } + + protected: + static std::mutex& getMutex(); + + bool m_shouldClose = false; + ImColor m_color; + }; + + } + + template + class Banner : public impl::BannerBase { + public: + using impl::BannerBase::BannerBase; + + template + static void open(Args && ... args) { + std::lock_guard lock(getMutex()); + + auto toast = std::make_unique(std::forward(args)...); + getOpenBanners().emplace_back(std::move(toast)); + } + }; + +} \ No newline at end of file diff --git a/lib/libimhex/source/helpers/utils.cpp b/lib/libimhex/source/helpers/utils.cpp index 0bc7798fb..ea939da31 100644 --- a/lib/libimhex/source/helpers/utils.cpp +++ b/lib/libimhex/source/helpers/utils.cpp @@ -686,7 +686,7 @@ namespace hex { return string; // If the string is longer than the max length, find the last space before the max length - auto it = string.begin() + maxLength; + auto it = string.begin() + maxLength / 2; while (it != string.begin() && !std::isspace(*it)) --it; // If there's no space before the max length, just cut the string diff --git a/lib/libimhex/source/ui/banner.cpp b/lib/libimhex/source/ui/banner.cpp new file mode 100644 index 000000000..e7ea37482 --- /dev/null +++ b/lib/libimhex/source/ui/banner.cpp @@ -0,0 +1,18 @@ +#include +#include + +namespace hex::impl { + + [[nodiscard]] std::list> &BannerBase::getOpenBanners() { + static AutoReset>> openBanners; + + return openBanners; + } + + std::mutex& BannerBase::getMutex() { + static std::mutex mutex; + + return mutex; + } + +} \ No newline at end of file diff --git a/main/gui/source/main.cpp b/main/gui/source/main.cpp index 2d981d771..b3b2c6cfe 100644 --- a/main/gui/source/main.cpp +++ b/main/gui/source/main.cpp @@ -25,6 +25,8 @@ namespace hex::init { int main(int argc, char **argv) { using namespace hex; + std::setlocale(LC_ALL, "en_US.utf8"); + // Set the main thread's name to "Main" TaskManager::setCurrentThreadName("Main"); @@ -43,9 +45,10 @@ int main(int argc, char **argv) { log::info("Welcome to ImHex {}!", ImHexApi::System::getImHexVersion().get()); log::info("Compiled using commit {}@{}", ImHexApi::System::getCommitBranch(), ImHexApi::System::getCommitHash()); log::info("Running on {} {} ({})", ImHexApi::System::getOSName(), ImHexApi::System::getOSVersion(), ImHexApi::System::getArchitecture()); + #if defined(OS_LINUX) - auto distro = ImHexApi::System::getLinuxDistro().value(); - log::info("Linux distribution: {}. Version: {}", distro.name, distro.version == "" ? "None" : distro.version); + auto distro = ImHexApi::System::getLinuxDistro().value(); + log::info("Linux distribution: {}. Version: {}", distro.name, distro.version == "" ? "None" : distro.version); #endif diff --git a/main/gui/source/window/window.cpp b/main/gui/source/window/window.cpp index 4f244006e..8bcbbb41d 100644 --- a/main/gui/source/window/window.cpp +++ b/main/gui/source/window/window.cpp @@ -1,4 +1,5 @@ #include "window.hpp" +#include "hex/ui/banner.hpp" #include @@ -553,6 +554,44 @@ namespace hex { }); } + // Draw Banners + { + const bool onWelcomeScreen = !ImHexApi::Provider::isValid(); + + float startY = (ImGui::GetTextLineHeight() + ImGui::GetStyle().FramePadding.y * 2.0F) * (onWelcomeScreen ? 2 : 3); + const auto height = 30_scaled; + + for (const auto &banner : impl::BannerBase::getOpenBanners() | std::views::take(5)) { + ImGui::PushID(banner.get()); + { + ImGui::SetNextWindowPos(ImVec2(1_scaled, startY)); + ImGui::SetNextWindowSize(ImVec2(ImHexApi::System::getMainWindowSize().x - 2_scaled, height)); + ImGui::PushStyleColor(ImGuiCol_WindowBg, banner->getColor().Value); + if (ImGui::Begin("##Banner", nullptr, ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse | ImGuiWindowFlags_NoDocking | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoFocusOnAppearing)) { + if (ImGui::BeginChild("##Content", ImGui::GetContentRegionAvail() - ImVec2(20_scaled, 0))) { + banner->draw(); + } + ImGui::EndChild(); + + ImGui::SameLine(); + + if (ImGui::CloseButton(ImGui::GetID("BannerCloseButton"), ImGui::GetCursorScreenPos())) { + banner->close(); + } + } + ImGui::End(); + ImGui::PopStyleColor(); + } + ImGui::PopID(); + + startY += height; + } + + std::erase_if(impl::BannerBase::getOpenBanners(), [](const auto &banner) { + return banner->shouldClose(); + }); + } + // Run all deferred calls TaskManager::runDeferredCalls(); } diff --git a/plugins/builtin/romfs/lang/en_US.json b/plugins/builtin/romfs/lang/en_US.json index 25727efbb..ed1778c02 100644 --- a/plugins/builtin/romfs/lang/en_US.json +++ b/plugins/builtin/romfs/lang/en_US.json @@ -422,8 +422,10 @@ "hex.builtin.provider.file.size": "Size", "hex.builtin.provider.file.menu.open_file": "Open file externally", "hex.builtin.provider.file.menu.open_folder": "Open containing folder", - "hex.builtin.provider.file.too_large": "This file is too large to be loaded into memory. Opening it anyways will result in modifications to be written directly to the file. Would you like to open it as Read-Only instead?", - "hex.builtin.provider.file.reload_changes": "The file has been modified by an external source. Do you want to reload it?", + "hex.builtin.provider.file.too_large": "File is larger than limit set limit, changes will be written directly to the file. Allow write access anyways?", + "hex.builtin.provider.file.too_large.allow_write": "Allow write access", + "hex.builtin.provider.file.reload_changes": "File has been modified by an external source. Do you want to reload it?", + "hex.builtin.provider.file.reload_changes.reload": "Reload", "hex.builtin.provider.gdb": "GDB Server Data", "hex.builtin.provider.gdb.ip": "IP Address", "hex.builtin.provider.gdb.name": "GDB Server <{0}:{1}>", diff --git a/plugins/builtin/source/content/providers/file_provider.cpp b/plugins/builtin/source/content/providers/file_provider.cpp index 03d30ab9c..9de0f2967 100644 --- a/plugins/builtin/source/content/providers/file_provider.cpp +++ b/plugins/builtin/source/content/providers/file_provider.cpp @@ -7,7 +7,7 @@ #include #include -#include +#include #include #include @@ -216,11 +216,7 @@ namespace hex::plugin::builtin { if (result && directAccess) { m_writable = false; - ui::PopupQuestion::open("hex.builtin.provider.file.too_large"_lang, - [this] { - m_writable = false; - }, - [this] { + ui::BannerButton::open(ICON_VS_WARNING, "hex.builtin.provider.file.too_large", ImColor(135, 116, 66), "hex.builtin.provider.file.too_large.allow_write", [this]{ m_writable = true; RequestUpdateWindowTitle::post(); }); @@ -281,6 +277,8 @@ namespace hex::plugin::builtin { } } + m_changeEventAcknowledgementPending = false; + return true; } @@ -368,15 +366,12 @@ namespace hex::plugin::builtin { } m_changeEventAcknowledgementPending = true; - - ui::PopupQuestion::open("hex.builtin.provider.file.reload_changes"_lang, [this] { + ui::BannerButton::open(ICON_VS_INFO, "hex.builtin.provider.file.reload_changes"_lang, ImColor(66, 104, 135), "hex.builtin.provider.file.reload_changes.reload", [this] { this->close(); (void)this->open(!m_loadedIntoMemory); + getUndoStack().reapply(); m_changeEventAcknowledgementPending = false; - }, - [this]{ - m_changeEventAcknowledgementPending = false; }); } diff --git a/plugins/ui/include/banners/banner_button.hpp b/plugins/ui/include/banners/banner_button.hpp new file mode 100644 index 000000000..d3b7d598f --- /dev/null +++ b/plugins/ui/include/banners/banner_button.hpp @@ -0,0 +1,51 @@ +#pragma once + +#include + +#include +#include + +namespace hex::ui { + + class BannerButton : public Banner { + public: + BannerButton(const char *icon, UnlocalizedString message, ImColor color, UnlocalizedString buttonText, std::function buttonCallback) + : Banner(color), m_icon(icon), m_message(std::move(message)), m_buttonText(std::move(buttonText)), m_buttonCallback(std::move(buttonCallback)) { } + + void drawContent() override { + const std::string buttonText = Lang(m_buttonText); + const auto buttonSize = ImGui::CalcTextSize(buttonText.c_str()); + + ImGui::TextUnformatted(m_icon); + ImGui::SameLine(0, 10_scaled); + + const std::string message = Lang(m_message); + const auto messageSize = ImGui::CalcTextSize(message.c_str()); + ImGuiExt::TextFormatted("{}", limitStringLength(message, message.size() * ((ImGui::GetContentRegionAvail().x - buttonSize.x - 40_scaled) / messageSize.x))); + if (ImGui::IsItemHovered()) { + ImGui::SetNextWindowSize(ImVec2(400_scaled, 0)); + if (ImGui::BeginTooltip()) { + ImGuiExt::TextFormattedWrapped("{}", message); + ImGui::EndTooltip(); + } + } + + ImGui::SameLine(); + + ImGui::SetCursorPosX(ImGui::GetCursorPosX() + ImGui::GetContentRegionAvail().x - buttonSize.x - 20_scaled); + ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 2_scaled); + if (ImGui::SmallButton(buttonText.c_str())) { + m_buttonCallback(); + this->close(); + } + ImGui::PopStyleVar(1); + } + + private: + const char *m_icon; + UnlocalizedString m_message; + UnlocalizedString m_buttonText; + std::function m_buttonCallback; + }; + +} diff --git a/plugins/ui/include/banners/banner_icon.hpp b/plugins/ui/include/banners/banner_icon.hpp new file mode 100644 index 000000000..f96f4c50e --- /dev/null +++ b/plugins/ui/include/banners/banner_icon.hpp @@ -0,0 +1,24 @@ +#pragma once + +#include +#include + +namespace hex::ui { + + class BannerIcon : public Banner { + public: + BannerIcon(const char *icon, UnlocalizedString message, ImColor color) + : Banner(color), m_icon(icon), m_message(std::move(message)) { } + + void drawContent() override { + ImGui::TextUnformatted(m_icon); + ImGui::SameLine(0, 10_scaled); + ImGui::TextUnformatted(Lang(m_message)); + } + + private: + const char *m_icon; + UnlocalizedString m_message; + }; + +} \ No newline at end of file