From d71f4168fb7d460340610fa7b29b14f06c0dab94 Mon Sep 17 00:00:00 2001 From: m-aXimilian <56168660+m-aXimilian@users.noreply.github.com> Date: Mon, 18 Aug 2025 22:39:43 +0000 Subject: [PATCH] some decouplings (#2) * logger business * code review * rm c-style array * extract image rendering to view * missing view files * init view abstractions * leverage concepts and provide a templateized Enumerate function * RVO * get rid of some warnings * logger business * code review * rm c-style array * extract image rendering to view * missing view files * init view abstractions * leverage concepts and provide a templateized Enumerate function * RVO * get rid of some warnings * logger business * code review * rm c-style array * init view abstractions * leverage concepts and provide a templateized Enumerate function * RVO * get rid of some warnings * logger business * code review * rm c-style array * init view abstractions * leverage concepts and provide a templateized Enumerate function * RVO * get rid of some warnings * Factor out AppGLFW base class The intention here is to get rid of scaffolding in the consumer application class and allow to focus on the "important bits". * Some cleanup * dump unnecessary dependency * link stuff where needed & use only what's needed * remove deprecation warnings * add gallery toggle * make some includes private * add presets * remove dir locals, use presets instead `projectile-configure-project' in conjunction with CMakePresets.json will allow to configure the project. `projectile-compile-project' does sth similar for the compile command. This is equivalent to something like ~cmake --preset clang-debug && cmake --build build --preset clang-debug~ * use presets in pipeline --------- Co-authored-by: m-aXimilian --- .dir-locals.el | 8 -- .github/workflows/cmake.yml | 4 +- CMakeLists.txt | 49 +++-------- CMakePresets.json | 70 +++++++++++++++ cmake/libCZI.cmake | 6 +- lib/CMakeLists.txt | 1 + {src => lib/app}/AppGLFW.cpp | 117 +++++++++----------------- lib/app/AppGLFW.hpp | 37 ++++++++ lib/app/CMakeLists.txt | 44 ++++++++++ lib/app/app_resources_default.h.in | 13 +++ lib/imaging/PixelariumImage.hpp | 8 +- lib/rendering/CvMatRender.cpp | 4 +- lib/rendering/CvMatRender.hpp | 2 +- lib/resources/resource.cpp | 1 + lib/resources/resource.hpp | 25 ++++-- lib/utilities/ILog.hpp | 10 +-- lib/utilities/SpdLogger.cpp | 47 +++++++---- lib/utilities/SpdLogger.hpp | 10 +-- src/AppGLFW.hpp | 74 ---------------- src/CMakeLists.txt | 6 +- src/MyApp.cpp | 61 ++++++++++++++ src/MyApp.hpp | 38 +++++++++ src/app_resources_local.h.in | 8 ++ src/main.cpp | 9 +- src/uiresources.h.in | 8 -- src/viewmodels/ImageViewFactory.cpp | 15 ++++ src/viewmodels/ImageViewFactory.hpp | 27 ++++++ src/views/PixelariumImageView.cpp | 53 ++++++++++++ src/views/PixelariumImageView.hpp | 34 ++++++++ tests/lib/resources/test_resource.cpp | 18 +++- 30 files changed, 548 insertions(+), 259 deletions(-) delete mode 100644 .dir-locals.el create mode 100644 CMakePresets.json rename {src => lib/app}/AppGLFW.cpp (57%) create mode 100644 lib/app/AppGLFW.hpp create mode 100644 lib/app/CMakeLists.txt create mode 100644 lib/app/app_resources_default.h.in delete mode 100644 src/AppGLFW.hpp create mode 100644 src/MyApp.cpp create mode 100644 src/MyApp.hpp create mode 100644 src/app_resources_local.h.in delete mode 100644 src/uiresources.h.in create mode 100644 src/viewmodels/ImageViewFactory.cpp create mode 100644 src/viewmodels/ImageViewFactory.hpp create mode 100644 src/views/PixelariumImageView.cpp create mode 100644 src/views/PixelariumImageView.hpp diff --git a/.dir-locals.el b/.dir-locals.el deleted file mode 100644 index a75c213..0000000 --- a/.dir-locals.el +++ /dev/null @@ -1,8 +0,0 @@ -;;; Directory Local Variables -*- no-byte-compile: t -*- -;;; For more information see (info "(emacs) Directory Variables") - -((nil . ((compile-command . - (eval - (format "cmake -B build -S . -DCMAKE_C_COMIPLER=%S -DCMAKE_CXX_COMIPLER=%S -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -DFETCHCONTENT_FULLY_DISCONNECTED=OFF -G Ninja && cmake --build build" - (executable-find "clang") - (executable-find "clang++"))))))) diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index 969a540..63eaedf 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -21,9 +21,9 @@ jobs: sudo apt update sudo apt -y install libopencv-dev libglfw3 libglfw3-dev libxkbcommon-dev libxinerama-dev libxcursor-dev libxi-dev - name: configure - run: cmake -B ${{github.workspace}}/build -S . + run: cmake --preset gcc-debug - name: build - run: cmake --build ${{github.workspace}}/build + run: cmake --build build --preset debug - name: test run: | cd ${{github.workspace}}/build diff --git a/CMakeLists.txt b/CMakeLists.txt index e5747a0..aa5f367 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,15 +6,16 @@ set(CMAKE_VERBOSE_MAKEFILE ON) set(CXX_STANDARD 20) set(CXX_STANDARD_REQUIRED true) +# setting global module directories +set(glfw3_module_DIR ${PROJECT_SOURCE_DIR}/modules/glfw) +set(glfw3_DIR "${glfw3_module_DIR}/CMake") set(imgui_DIR ${PROJECT_SOURCE_DIR}/modules/imgui) -set(glfw_DIR ${PROJECT_SOURCE_DIR}/modules/glfw) set(pfd_DIR ${PROJECT_SOURCE_DIR}/modules/portable-file-dialogs) set(spdlog_DIR ${PROJECT_SOURCE_DIR}/modules/spdlog) find_package(OpenGL REQUIRED) -message(STATUS "IMGUI:\t" ${imgui_DIR}) -message(STATUS "GLFW:\t" ${glfw_DIR}) +message(STATUS "GLFW:\t" ${glfw3_module_DIR}) message(STATUS "PFD:\t\t" ${pfd_DIR}) message(STATUS "SPDLOG:\t" ${spdlog_DIR}) @@ -33,23 +34,18 @@ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE ${CMAKE_BINARY_DIR}/bin/Release) add_subdirectory(${pfd_DIR}) add_subdirectory(${spdlog_DIR}) -add_subdirectory(${glfw_DIR}) +add_subdirectory(${glfw3_module_DIR}) add_subdirectory(src) add_subdirectory(lib) set(SRC - src/AppGLFW.cpp - src/main.cpp - ${imgui_DIR}/imgui.cpp - ${imgui_DIR}/imgui_demo.cpp - ${imgui_DIR}/imgui_draw.cpp - ${imgui_DIR}/imgui_tables.cpp - ${imgui_DIR}/imgui_widgets.cpp - ${imgui_DIR}/backends/imgui_impl_opengl3.cpp - ${imgui_DIR}/backends/imgui_impl_glfw.cpp) + src/MyApp.cpp + src/views/PixelariumImageView.cpp + src/viewmodels/ImageViewFactory.cpp + src/main.cpp) #==================== -# needed for the spdlogger implemntation +# needed for the spdlogger implementation # this is not nice, but it won't work when, e.g. doing it from lower level cmake files add_compile_options("$<$:/utf-8>") add_compile_options("$<$:/utf-8>") @@ -61,38 +57,19 @@ target_link_libraries(${PROJECT_NAME} PRIVATE pixelariumimagelib PRIVATE pixelariumrenderlib PRIVATE pixelariumutilslib - PRIVATE pixelariumresourcelib) + PRIVATE pixelariumresourcelib + PUBLIC pixelariumapplicationlib) target_include_directories(${PROJECT_NAME} PUBLIC ${PROJECT_SOURCE_DIR}/src PUBLIC ${PROJECT_SOURCE_DIR}/lib PUBLIC ${PROJECT_SOURCE_DIR}/lib/imaging + PUBLIC ${PROJECT_SOURCE_DIR}/lib/app PUBLIC ${spdlog_DIR}/include - PUBLIC ${imgui_DIR} - PUBLIC ${imgui_DIR}/backends - PUBLIC ${glfw_INCLUDE_DIR} PUBLIC ${pfd_DIR} PUBLIC ${LIBCZI_INCLUDE_DIR} PUBLIC ${CMAKE_CURRENT_BINARY_DIR}) - -if(WIN32) - target_link_libraries(${PROJECT_NAME} - PRIVATE opengl32.lib - PRIVATE glfw - PRIVATE OpenGL::GL) -endif() -if(LINUX) - target_link_libraries(${PROJECT_NAME} - PRIVATE glfw GL) -endif() -if(APPLE) - target_link_libraries(${PROJECT_NAME} - PUBLIC glfw - PUBLIC "-framework OpenGL") -endif() - - option(PIXELARIUM_BUILD_UNITTESTS "Generate Unittests" ON) diff --git a/CMakePresets.json b/CMakePresets.json new file mode 100644 index 0000000..6fc526f --- /dev/null +++ b/CMakePresets.json @@ -0,0 +1,70 @@ +{ + "version": 3, + "cmakeMinimumRequired": { + "major": 3, + "minor": 21, + "patch": 0 + }, + "configurePresets": [ + { + "name": "default", + "generator": "Ninja", + "binaryDir": "build", + "cacheVariables": { + "CMAKE_C_COMPILER": "clang", + "CMAKE_CXX_COMPILER": "clang++", + "CMAKE_EXPORT_COMPILE_COMMANDS": "ON", + "FETCHCONTENT_FULLY_DISCONNECTED": "OFF" + } + }, + { + "name": "clang-release", + "inherits": "default", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Release" + } + }, + { + "name": "clang-debug", + "inherits": "default", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug" + } + }, + { + "name": "gcc-release", + "inherits": "default", + "cacheVariables": { + "CMAKE_C_COMPILER": "gcc", + "CMAKE_CXX_COMPILER": "g++", + "CMAKE_BUILD_TYPE": "Release" + } + }, + { + "name": "gcc-debug", + "inherits": "default", + "cacheVariables": { + "CMAKE_C_COMPILER": "gcc", + "CMAKE_CXX_COMPILER": "g++", + "CMAKE_BUILD_TYPE": "Debug" + } + } + ], + "buildPresets": [ + { + "name": "default", + "configurePreset": "default", + "jobs": 10 + }, + { + "name": "release", + "inherits": "default", + "configuration": "Release" + }, + { + "name": "debug", + "inherits": "default", + "configuration": "Debug" + } + ] +} diff --git a/cmake/libCZI.cmake b/cmake/libCZI.cmake index b21a4d4..01ef7aa 100644 --- a/cmake/libCZI.cmake +++ b/cmake/libCZI.cmake @@ -8,14 +8,12 @@ FetchContent_Declare( if(NOT libCZI_POPULATED) message(STATUS "Fetching libCZI") - FetchContent_Populate(libCZI) + FetchContent_MakeAvailable(libCZI) set(LIBCZI_BUILD_CZICMD OFF CACHE BOOL "" FORCE) set(LIBCZI_BUILD_DYNLIB OFF CACHE BOOL "" FORCE) set(LIBCZI_BUILD_UNITTESTS OFF CACHE BOOL "" FORCE) - set(LIBCZI_DO_NOT_SET_MSVC_RUNTIME_LIBRARY ON CACHE BOOL "" FORCE) - - add_subdirectory(${libczi_SOURCE_DIR} ${libczi_BINARY_DIR}) + set(LIBCZI_DO_NOT_SET_MSVC_RUNTIME_LIBRARY ON CACHE BOOL "" FORCE) endif() FetchContent_GetProperties(libCZI) diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index e131d02..071482d 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -2,3 +2,4 @@ add_subdirectory(utilities) add_subdirectory(imaging) add_subdirectory(rendering) add_subdirectory(resources) +add_subdirectory(app) diff --git a/src/AppGLFW.cpp b/lib/app/AppGLFW.cpp similarity index 57% rename from src/AppGLFW.cpp rename to lib/app/AppGLFW.cpp index 49f0aa5..67b7ca6 100644 --- a/src/AppGLFW.cpp +++ b/lib/app/AppGLFW.cpp @@ -1,40 +1,16 @@ #include "AppGLFW.hpp" -#include -#include - -#include "imaging/PixelariumImage.hpp" #include "imgui.h" #include "imgui_impl_glfw.h" #include "imgui_impl_opengl3.h" -#include "portable-file-dialogs.h" -#include "rendering/CvMatRender.hpp" -#include "uiresources.h" +#include "app_resources_default.h" -using namespace pixelarium::imaging; - -/*static*/ bool pixelarium::ui::dim_changed_p(const ImVec2& ref_rect, const ImVec2& new_rect) +static void glfw_error_callback(int error, const char* description) { - if (std::abs(ref_rect.y - new_rect.y) > 5 || std::abs(ref_rect.x - new_rect.x)) - { - return true; - } - - return false; + fprintf(stderr, "GLFW Error %d: %s\n", error, description); } -/*static*/ ImVec2 pixelarium::ui::aspect_const_dimensions(const pixelarium::imaging::PixelariumImage& img, - const ImVec2& curr_dim) -{ - const auto w_fact = (static_cast(curr_dim.x) / img.GetImage().cols); - const auto h_fact = (static_cast(curr_dim.y) / img.GetImage().rows); - - const auto fact = w_fact < h_fact ? w_fact : h_fact; - - return ImVec2(img.GetImage().cols * fact, img.GetImage().rows * fact); -} - -void pixelarium::ui::AppGLFW::InitMainWindow() +void pixelarium::application::AppGLFW::InitMainWindow() { glfwSetErrorCallback(glfw_error_callback); if (!glfwInit()) @@ -119,7 +95,7 @@ void pixelarium::ui::AppGLFW::InitMainWindow() ImGui_ImplOpenGL3_Init(glsl_version); } -int pixelarium::ui::AppGLFW::Run() +int pixelarium::application::AppGLFW::RunLoop() { ImGuiIO& io = ImGui::GetIO(); (void)io; @@ -135,36 +111,7 @@ int pixelarium::ui::AppGLFW::Run() this->MenuBar(); - if (this->imagep_) - { - // auto render = render::CvMatRender(this->_img); - ImGui::Begin("An image", &this->imagep_, ImGuiWindowFlags_HorizontalScrollbar | ImGuiWindowFlags_MenuBar); - this->curr_dim_ = ImGui::GetContentRegionAvail(); - auto new_dim = ImGui::GetContentRegionAvail(); - auto texture = dim_changed_p(this->curr_dim_, new_dim) - ? this->render_.Render(static_cast(this->curr_dim_.x), - static_cast(this->curr_dim_.y)) - : this->render_.Render(); - - this->curr_dim_ = new_dim; - - // random aspect ratio - // ImGui::Image(reinterpret_cast( - // reinterpret_cast(*texture)), - // this->_curr_dim); - ImVec2 dim(this->img_->GetImage().cols, this->img_->GetImage().rows); - // aspect ratio constant render - ImGui::Image(reinterpret_cast(reinterpret_cast(texture)), - aspect_const_dimensions(*this->img_, new_dim)); - // ImGui::Image(reinterpret_cast(reinterpret_cast(texture)), - // ImVec2(img_->GetImage().cols, img_->GetImage().rows)); - - // We can do everything else from within the image buffer that ImGui offers - // ImGui::Separator(); - // ImGui::Text("This is a text within the image frame"); - - ImGui::End(); - } + this->Run(); // Rendering ImGui::Render(); @@ -182,6 +129,7 @@ int pixelarium::ui::AppGLFW::Run() glfwSwapBuffers(this->window); } + // Cleanup ImGui_ImplOpenGL3_Shutdown(); ImGui_ImplGlfw_Shutdown(); @@ -193,43 +141,58 @@ int pixelarium::ui::AppGLFW::Run() return 0; } -void pixelarium::ui::AppGLFW::MenuBar() +void pixelarium::application::AppGLFW::MenuBar() { if (ImGui::BeginMainMenuBar()) { // main menu if (ImGui::BeginMenu(MAINMENUNAME)) { - ImGui::EndMenu(); - } - - // file menu - if (ImGui::BeginMenu(FILEMENUNAME)) - { - if (ImGui::MenuItem("Load File")) + if (ImGui::BeginCombo(LOGLEVELSELECT, LOGLEVELS[log_level_].data())) { - this->LoadImageProt(); + for (int n = 0; n < static_cast(LOGLEVELS.size()); n++) + { + bool is_selected = (LOGLEVELS[log_level_] == LOGLEVELS[n]); + if (ImGui::Selectable(LOGLEVELS[n].data(), is_selected)) + { + log_level_ = n; + this->logger_.ChangeLevel(static_cast(1 << log_level_)); + } + if (is_selected) ImGui::SetItemDefaultFocus(); + } + ImGui::EndCombo(); } + // consumer main menu bar entries + this->MenuBarOptionsColumn1(); + ImGui::EndMenu(); } + // consumer menu bar columns + this->MenuBarOptionsColumn2(); + this->MenuBarOptionsColumn3(); + this->MenuBarOptionsColumn4(); + this->MenuBarOptionsColumn5(); + ImGui::EndMainMenuBar(); } } -void pixelarium::ui::AppGLFW::LoadImageProt() +void pixelarium::application::AppGLFW::LogLevelSelect() { - auto res{pfd::open_file("Load Inputs", pfd::path::home(), {"All Files", "*"}, pfd::opt::multiselect).result()}; - for (auto& p : res) + if (ImGui::BeginCombo(LOGLEVELSELECT, LOGLEVELS[log_level_].data())) { - if (this->logger_) + for (int n = 0; n < static_cast(LOGLEVELS.size()); n++) { - this->logger_->Debug(std::format("{}: Creating image {}", __FUNCTION__, p)); + bool is_selected = (LOGLEVELS[log_level_] == LOGLEVELS[n]); + if (ImGui::Selectable(LOGLEVELS[n].data(), is_selected)) + { + log_level_ = n; + this->logger_.ChangeLevel(static_cast(1 << log_level_)); + } + if (is_selected) ImGui::SetItemDefaultFocus(); } - - this->img_ = std::make_shared(p); - this->render_ = pixelarium::render::CvMatRender(this->img_); - this->imagep_ = true; + ImGui::EndCombo(); } } diff --git a/lib/app/AppGLFW.hpp b/lib/app/AppGLFW.hpp new file mode 100644 index 0000000..698c4fe --- /dev/null +++ b/lib/app/AppGLFW.hpp @@ -0,0 +1,37 @@ +#pragma once + +#include + +#include + +#include "imgui.h" +#include "utilities/ILog.hpp" + +namespace pixelarium::application +{ +class AppGLFW +{ + public: + explicit AppGLFW(const utils::log::ILog& log) : logger_(log) { this->InitMainWindow(); } + + void Start() { this->RunLoop(); } + + protected: + virtual void MenuBarOptionsColumn1() {} + virtual void MenuBarOptionsColumn2() {} + virtual void MenuBarOptionsColumn3() {} + virtual void MenuBarOptionsColumn4() {} + virtual void MenuBarOptionsColumn5() {} + virtual void Run() {} + + const utils::log::ILog& logger_; + + private: + int RunLoop(); + void InitMainWindow(); + void MenuBar(); + void LogLevelSelect(); + int log_level_{0}; + GLFWwindow* window = nullptr; +}; +} // namespace pixelarium::application diff --git a/lib/app/CMakeLists.txt b/lib/app/CMakeLists.txt new file mode 100644 index 0000000..f2f9964 --- /dev/null +++ b/lib/app/CMakeLists.txt @@ -0,0 +1,44 @@ +set(PIXELARIUM_TITLE ${CMAKE_PROJECT_NAME}) +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/app_resources_default.h.in + ${CMAKE_BINARY_DIR}/app_resources_default.h @ONLY) + +set(APPLIBSRC + AppGLFW.cpp + ${imgui_DIR}/imgui.cpp + ${imgui_DIR}/imgui_demo.cpp + ${imgui_DIR}/imgui_draw.cpp + ${imgui_DIR}/imgui_tables.cpp + ${imgui_DIR}/imgui_widgets.cpp + ${imgui_DIR}/backends/imgui_impl_opengl3.cpp + ${imgui_DIR}/backends/imgui_impl_glfw.cpp) + +set(APPLIBNAME pixelariumapplicationlib) + +add_library(${APPLIBNAME} + STATIC ${APPLIBSRC}) + +target_link_libraries(${APPLIBNAME} + PRIVATE pixelariumutilslib) + +# This needs to be public to let the consumer know about it. +if(WIN32) + target_link_libraries(${APPLIBNAME} + PUBLIC opengl32.lib + PUBLIC glfw + PUBLIC OpenGL::GL) +endif() +if(LINUX) + target_link_libraries(${APPLIBNAME} + PUBLIC glfw GL) +endif() +if(APPLE) + target_link_libraries(${APPLIBNAME} + PUBLIC glfw + PUBLIC "-framework OpenGL") +endif() + +target_include_directories(${APPLIBNAME} + PRIVATE ${CMAKE_BINARY_DIR} + PRIVATE ${PROJECT_SOURCE_DIR}/lib + PUBLIC ${imgui_DIR} + PUBLIC ${imgui_DIR}/backends) diff --git a/lib/app/app_resources_default.h.in b/lib/app/app_resources_default.h.in new file mode 100644 index 0000000..e8b7159 --- /dev/null +++ b/lib/app/app_resources_default.h.in @@ -0,0 +1,13 @@ +#pragma once + +#include +#include + +#cmakedefine PIXELARIUM_TITLE "@PIXELARIUM_TITLE@" + +#define MAINMENUNAME "Menu" +#define FILEMENUNAME "File" +#define LOGLEVELSELECT "Log Level" + + +inline constexpr std::array LOGLEVELS = {"Trace", "Debug", "Info", "Warning", "Error"}; diff --git a/lib/imaging/PixelariumImage.hpp b/lib/imaging/PixelariumImage.hpp index c5b98ce..2abad19 100644 --- a/lib/imaging/PixelariumImage.hpp +++ b/lib/imaging/PixelariumImage.hpp @@ -14,8 +14,12 @@ class PixelariumImage // get back the defaults PixelariumImage() = default; - // we cannot copy an Image since this conflicts with the _img field - PixelariumImage(const PixelariumImage& other) = delete; + PixelariumImage(const PixelariumImage& other) + { + // be ware!! + // we make a copy of the image data here! + img_ = std::make_unique(*other.img_); + }; PixelariumImage(PixelariumImage&& other) noexcept : img_(std::move(other.img_)) {} // requires a copy ctor which we don't have diff --git a/lib/rendering/CvMatRender.cpp b/lib/rendering/CvMatRender.cpp index be191dd..fbbc200 100644 --- a/lib/rendering/CvMatRender.cpp +++ b/lib/rendering/CvMatRender.cpp @@ -61,8 +61,8 @@ GLuint pixelarium::render::CvMatRender::uploadTexture() glBindTexture(GL_TEXTURE_2D, this->texture_); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + // glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + // glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); diff --git a/lib/rendering/CvMatRender.hpp b/lib/rendering/CvMatRender.hpp index 0f485da..2aea15d 100644 --- a/lib/rendering/CvMatRender.hpp +++ b/lib/rendering/CvMatRender.hpp @@ -31,7 +31,7 @@ class CvMatRender CvMatRender(const CvMatRender&) = delete; CvMatRender(CvMatRender&&) = delete; CvMatRender& operator=(CvMatRender&) = default; - CvMatRender& operator=(CvMatRender&&) = default; + CvMatRender& operator=(CvMatRender&& other) = default; ~CvMatRender(); explicit CvMatRender(const std::shared_ptr& img); diff --git a/lib/resources/resource.cpp b/lib/resources/resource.cpp index 3c6e81e..d98e10b 100644 --- a/lib/resources/resource.cpp +++ b/lib/resources/resource.cpp @@ -1,6 +1,7 @@ #include "resource.hpp" #include +#include #include using pixelarium::imaging::PixelariumImage; diff --git a/lib/resources/resource.hpp b/lib/resources/resource.hpp index 61e17a8..5d3a4a8 100644 --- a/lib/resources/resource.hpp +++ b/lib/resources/resource.hpp @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include @@ -17,17 +18,17 @@ struct IResource template concept ResT = requires(R& r) { static_cast(r); }; -// template -template +template class IResourcePool { public: virtual ~IResourcePool() = default; - virtual std::optional GetResource(size_t id) const = 0; - virtual size_t SetResource(std::unique_ptr res) = 0; - virtual bool ModifyResource(size_t id, std::unique_ptr res) = 0; + virtual std::optional GetResource(size_t id) const = 0; + virtual size_t SetResource(std::unique_ptr res) = 0; + virtual bool ModifyResource(size_t id, std::unique_ptr res) = 0; virtual bool DeleteResource(size_t id) = 0; - virtual void EnumerateResources(const std::function& func) = 0; + virtual void EnumerateResources(const std::function& func) = 0; + virtual size_t GetTotalSize() const = 0; }; // Now with the =GetResource= method, I do not want to transfer ownership to the caller of that method. The ownership @@ -52,6 +53,18 @@ class ImageResourcePool : public IResourcePool void EnumerateResources(const std::function& func) override; + template + requires std::invocable + void Enumerate(Callable&& func) const + { + for (const auto& e : this->resources_) + { + func(e.first, *e.second); + } + } + + size_t GetTotalSize() const override { return resources_.size(); } + private: std::unordered_map> resources_; }; diff --git a/lib/utilities/ILog.hpp b/lib/utilities/ILog.hpp index be551d9..4f2c1ea 100644 --- a/lib/utilities/ILog.hpp +++ b/lib/utilities/ILog.hpp @@ -14,11 +14,11 @@ enum class LogLevel class ILog { public: - virtual void Info(const std::string& msg) = 0; - virtual void Debug(const std::string& msg) = 0; - virtual void Warn(const std::string& msg) = 0; - virtual void Error(const std::string& msg) = 0; - virtual void ChangeLevel(LogLevel lvl) = 0; + virtual void Info(const std::string& msg) const = 0; + virtual void Debug(const std::string& msg) const = 0; + virtual void Warn(const std::string& msg) const = 0; + virtual void Error(const std::string& msg) const = 0; + virtual void ChangeLevel(LogLevel lvl) const = 0; virtual ~ILog() {} }; diff --git a/lib/utilities/SpdLogger.cpp b/lib/utilities/SpdLogger.cpp index b60b606..112c526 100644 --- a/lib/utilities/SpdLogger.cpp +++ b/lib/utilities/SpdLogger.cpp @@ -1,12 +1,13 @@ #include "SpdLogger.hpp" #include -#include #include +#include + #include #include -#include "ILog.hpp" +#include "ILog.hpp" using namespace pixelarium::utils::log; @@ -18,22 +19,32 @@ SpdLogger::SpdLogger(const std::string& file_sink, const std::string& name) logger_->info("Logger initiated"); } -void SpdLogger::Info(const std::string& msg) -{ - this->logger_->info(msg); -} -void SpdLogger::Debug(const std::string& msg) -{ - this->logger_->debug(msg); -} -void SpdLogger::Warn(const std::string& msg) -{ - this->logger_->warn(msg); -} -void SpdLogger::Error(const std::string& msg) { this->logger_->error(msg); } +void SpdLogger::Info(const std::string& msg) const { this->logger_->info(msg); } +void SpdLogger::Debug(const std::string& msg) const { this->logger_->debug(msg); } +void SpdLogger::Warn(const std::string& msg) const { this->logger_->warn(msg); } +void SpdLogger::Error(const std::string& msg) const { this->logger_->error(msg); } -void SpdLogger::ChangeLevel(LogLevel lvl) +void SpdLogger::ChangeLevel(LogLevel lvl) const { + constexpr auto LogLevelToString = [](LogLevel l) -> const char* + { + switch (l) + { + case LogLevel::Trace: + return "Trace"; + case LogLevel::Debug: + return "Debug"; + case LogLevel::Info: + return "Info"; + case LogLevel::Warn: + return "Warn"; + case LogLevel::Error: + return "Error"; + default: + return "Not Found"; + } + }; + switch (lvl) { case LogLevel::Trace: @@ -58,5 +69,7 @@ void SpdLogger::ChangeLevel(LogLevel lvl) spdlog::flush_on(spdlog::level::debug); } - this->logger_->debug("Changed log level;"); + // you will only get this message for log levels <= info! I.e., not for error or warning. + this->logger_->info( + std::format("{}: Changed log level to {}({})", __FUNCTION__, LogLevelToString(lvl), static_cast(lvl))); } diff --git a/lib/utilities/SpdLogger.hpp b/lib/utilities/SpdLogger.hpp index 3a8907d..c844ffc 100644 --- a/lib/utilities/SpdLogger.hpp +++ b/lib/utilities/SpdLogger.hpp @@ -13,11 +13,11 @@ class SpdLogger : public ILog public: explicit SpdLogger(const std::string& file_sink, const std::string& name); - void Info(const std::string& msg) override; - void Debug(const std::string& msg) override; - void Warn(const std::string& msg) override; - void Error(const std::string& msg) override; - void ChangeLevel(LogLevel lvl) override; + void Info(const std::string& msg) const override; + void Debug(const std::string& msg) const override; + void Warn(const std::string& msg) const override; + void Error(const std::string& msg) const override; + void ChangeLevel(LogLevel lvl) const override; private: std::shared_ptr logger_; diff --git a/src/AppGLFW.hpp b/src/AppGLFW.hpp deleted file mode 100644 index 7deca27..0000000 --- a/src/AppGLFW.hpp +++ /dev/null @@ -1,74 +0,0 @@ -#pragma once - -#include - -#include -#include -#include - -#include "PixelariumImage.hpp" -#include "imgui.h" -#include "rendering/CvMatRender.hpp" -#include "resources/resource.hpp" -#include "utilities/ILog.hpp" - -namespace pixelarium::ui -{ -static bool dim_changed_p(const ImVec2& ref_rect, const ImVec2& new_rect); - -static ImVec2 aspect_const_dimensions(const pixelarium::imaging::PixelariumImage& img, const ImVec2& curr_dim); - -enum LogLevelSelection -{ - Debug = 0, - Info = 1, - Warning = 2, - Error = 3 -}; - -class AppGLFW -{ - public: - AppGLFW() { this->InitMainWindow(); } - AppGLFW(std::unique_ptr& log) : AppGLFW() - { - logger_ = log.get(); - if (logger_) - { - logger_->Debug(std::format("{}: Initiating a new window", __FUNCTION__).c_str()); - - if (pool_) - { - logger_->Debug(std::format("{}: We have an image resource pool!", __FUNCTION__).c_str()); - } - } - } - AppGLFW(std::unique_ptr& log, std::unique_ptr& pool) - : AppGLFW(log) - { - pool_ = pool.get(); - }; - int Run(); - - private: - void InitMainWindow(); - void MenuBar(); - void LoadImageProt(); - - private: - // LogLevelSelection log_level_ = static_cast(0); - utils::log::ILog* logger_; - resources::ImageResourcePool* pool_; - GLFWwindow* window = nullptr; - ImGuiWindowFlags window_flags_ = 0; - std::shared_ptr img_; - pixelarium::render::CvMatRender render_; - bool imagep_{false}; - ImVec2 curr_dim_; -}; - -static void glfw_error_callback(int error, const char* description) -{ - fprintf(stderr, "GLFW Error %d: %s\n", error, description); -} -} // namespace pixelarium::ui diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 76feb0c..d8cfb57 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,6 +1,4 @@ -set(PIXELARIUM_TITLE ${CMAKE_PROJECT_NAME}) - message(STATUS "Configuring Resources") -configure_file(${CMAKE_CURRENT_SOURCE_DIR}/uiresources.h.in - ${CMAKE_BINARY_DIR}/uiresources.h @ONLY) +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/app_resources_local.h.in + ${CMAKE_BINARY_DIR}/app_resources_local.h @ONLY) diff --git a/src/MyApp.cpp b/src/MyApp.cpp new file mode 100644 index 0000000..8d1c1d2 --- /dev/null +++ b/src/MyApp.cpp @@ -0,0 +1,61 @@ +#include "MyApp.hpp" + +#include +#include + +#include "imaging/PixelariumImage.hpp" +#include "imgui.h" +#include "portable-file-dialogs.h" +#include "app_resources_default.h" +#include "app_resources_local.h" +#include "utilities/ILog.hpp" + +using namespace pixelarium::imaging; + +void pixelarium::ui::MyApp::MenuBarOptionsColumn1() +{ + ImGui::MenuItem(SHOWIMGUIDEMOS, NULL, &this->demop_); + ImGui::MenuItem(SHOWIMAGEGALLERY, NULL, &this->image_listp_); +} + +void pixelarium::ui::MyApp::MenuBarOptionsColumn2() +{ + if (ImGui::BeginMenu(FILEMENUNAME)) + { + if (ImGui::MenuItem("Load File")) + { + this->LoadImageProt(); + } + + ImGui::EndMenu(); + } +} + +void pixelarium::ui::MyApp::Run() +{ + if (demop_) ImGui::ShowDemoWindow(&this->demop_); + if (image_listp_) this->ImageGalleryRender(); +} + +void pixelarium::ui::MyApp::ImageGalleryRender() +{ + if (ImGui::BeginListBox("ListBox")) + { + pool_.EnumerateResources([](size_t id, const imaging::PixelariumImage&) -> void + { ImGui::Selectable(std::format("Image {}", id).c_str()); }); + + ImGui::EndListBox(); + } +} + +void pixelarium::ui::MyApp::LoadImageProt() +{ + size_t last_id{}; + auto res{pfd::open_file("Load Inputs", pfd::path::home(), {"All Files", "*"}, pfd::opt::multiselect).result()}; + for (auto& p : res) + { + this->logger_.Debug(std::format("{}: Creating image {}", __FUNCTION__, p)); + + last_id = image_view_model_->AddImage(std::make_unique(p)); + } +} diff --git a/src/MyApp.hpp b/src/MyApp.hpp new file mode 100644 index 0000000..60fc038 --- /dev/null +++ b/src/MyApp.hpp @@ -0,0 +1,38 @@ +#pragma once + +#include + +#include "AppGLFW.hpp" +#include "imgui.h" +#include "resources/resource.hpp" +#include "utilities/ILog.hpp" +#include "viewmodels/ImageViewFactory.hpp" + +namespace pixelarium::ui +{ +class MyApp : public application::AppGLFW +{ + public: + MyApp(const utils::log::ILog& log, pixelarium::resources::ImageResourcePool& pool) + : application::AppGLFW(log), pool_(pool) + { + image_view_model_ = std::make_unique(pool_); + } + + protected: + void MenuBarOptionsColumn1() override; + void MenuBarOptionsColumn2() override; + void Run() override; + + private: + void LoadImageProt(); + void ImageGalleryRender(); + + private: + resources::ImageResourcePool& pool_; + std::unique_ptr image_view_model_; + bool image_listp_{false}; + bool demop_{false}; + ImVec2 curr_dim_; +}; +} // namespace pixelarium::ui diff --git a/src/app_resources_local.h.in b/src/app_resources_local.h.in new file mode 100644 index 0000000..1fb160c --- /dev/null +++ b/src/app_resources_local.h.in @@ -0,0 +1,8 @@ +#pragma once + +// Currently, there is no need for this to be a configurable header. +// It is left as such to keep the API open for possible build-system +// injections in future. + +#define SHOWIMGUIDEMOS "ImGui Demos" +#define SHOWIMAGEGALLERY "Image Gallery" diff --git a/src/main.cpp b/src/main.cpp index 7ec5dd0..cd1d862 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,9 +1,8 @@ #include #include -#include "AppGLFW.hpp" +#include "MyApp.hpp" #include "resources/resource.hpp" -#include "uiresources.h" #include "utilities/ILog.hpp" #include "utilities/SpdLogger.hpp" @@ -18,12 +17,12 @@ int main(int argc, char** argv) #else logger = make_unique(std::string(getenv("HOME")) + "/.cache/pixelarium/log.log", "default"); #endif - logger->Info(std::format("{}: Starting Application {}", __FUNCTION__, PIXELARIUM_TITLE)); + logger->Info(std::format("{}: Starting Application {}", __FUNCTION__, "Pixelarium")); logger->ChangeLevel(utils::log::LogLevel::Debug); auto image_pool{std::make_unique()}; - auto app = pixelarium::ui::AppGLFW(logger, image_pool); + pixelarium::ui::MyApp app = pixelarium::ui::MyApp(*logger, *image_pool); - return app.Run(); + app.Start(); } diff --git a/src/uiresources.h.in b/src/uiresources.h.in deleted file mode 100644 index 706bc9c..0000000 --- a/src/uiresources.h.in +++ /dev/null @@ -1,8 +0,0 @@ -#pragma once - - -/*-- Gets filled in during the cmake configuration step --*/ -#cmakedefine PIXELARIUM_TITLE "@PIXELARIUM_TITLE@" - -#define MAINMENUNAME "Menu" -#define FILEMENUNAME "File" \ No newline at end of file diff --git a/src/viewmodels/ImageViewFactory.cpp b/src/viewmodels/ImageViewFactory.cpp new file mode 100644 index 0000000..633565b --- /dev/null +++ b/src/viewmodels/ImageViewFactory.cpp @@ -0,0 +1,15 @@ +#include "ImageViewFactory.hpp" +#include +#include + +using namespace pixelarium::ui; + +std::unique_ptr ImageViewFactory::RenderImage(size_t image_id) +{ + auto img{this->image_pool_.GetResource(image_id)}; + + if (!img.has_value()) return nullptr; + + // beware: here we copy the actual image resource over to the new image + return std::make_unique(std::make_shared(*img.value())); +} diff --git a/src/viewmodels/ImageViewFactory.hpp b/src/viewmodels/ImageViewFactory.hpp new file mode 100644 index 0000000..bfd6018 --- /dev/null +++ b/src/viewmodels/ImageViewFactory.hpp @@ -0,0 +1,27 @@ +#pragma once + +#include "PixelariumImage.hpp" +#include "resources/resource.hpp" +#include "views/PixelariumImageView.hpp" +namespace pixelarium::ui +{ +class ImageViewFactory +{ + using Image = imaging::PixelariumImage; + using Pool = resources::ImageResourcePool; + + public: + explicit ImageViewFactory(Pool& pool) : image_pool_(pool) {} + + [[nodiscard("Image Id is ignored")]] + size_t AddImage(std::unique_ptr res) const noexcept + { + return image_pool_.SetResource(std::move(res)); + } + + std::unique_ptr RenderImage(size_t id); + + private: + Pool& image_pool_; +}; +} // namespace pixelarium::ui diff --git a/src/views/PixelariumImageView.cpp b/src/views/PixelariumImageView.cpp new file mode 100644 index 0000000..069846f --- /dev/null +++ b/src/views/PixelariumImageView.cpp @@ -0,0 +1,53 @@ +#include "PixelariumImageView.hpp" + +#include + +#include "imgui.h" + +using namespace pixelarium::ui; + +static bool dim_changed_p(const ImVec2& ref_rect, const ImVec2& new_rect) +{ + if (std::abs(ref_rect.y - new_rect.y) > 5 || std::abs(ref_rect.x - new_rect.x)) + { + return true; + } + + return false; +} + +ImVec2 aspect_const_dimensions(const pixelarium::imaging::PixelariumImage& img, const ImVec2& curr_dim) +{ + const auto w_fact = (static_cast(curr_dim.x) / img.GetImage().cols); + const auto h_fact = (static_cast(curr_dim.y) / img.GetImage().rows); + + const auto fact = w_fact < h_fact ? w_fact : h_fact; + + return ImVec2(img.GetImage().cols * fact, img.GetImage().rows * fact); +} + +void PixelariumImageView::ShowImage() +{ + if (this->open_p) + { + ImGui::Begin("An image", &this->open_p, ImGuiWindowFlags_HorizontalScrollbar | ImGuiWindowFlags_MenuBar); + this->curr_dim_ = ImGui::GetContentRegionAvail(); + auto new_dim = ImGui::GetContentRegionAvail(); + auto texture = + dim_changed_p(this->curr_dim_, new_dim) + ? this->render_.Render(static_cast(this->curr_dim_.x), static_cast(this->curr_dim_.y)) + : this->render_.Render(); + + this->curr_dim_ = new_dim; + + ImVec2 dim(this->img_->GetImage().cols, this->img_->GetImage().rows); + + ImGui::Image(reinterpret_cast(reinterpret_cast(texture)), + aspect_const_dimensions(*this->img_, new_dim)); + + ImGui::Separator(); + ImGui::Text("%s", std::format("Dimensions W: {}, H: {}", curr_dim_.x, curr_dim_.y).c_str()); + + ImGui::End(); + } +} diff --git a/src/views/PixelariumImageView.hpp b/src/views/PixelariumImageView.hpp new file mode 100644 index 0000000..0f6a149 --- /dev/null +++ b/src/views/PixelariumImageView.hpp @@ -0,0 +1,34 @@ +#pragma once + +#include + +#include "PixelariumImage.hpp" +#include "imgui.h" +#include "rendering/CvMatRender.hpp" + +namespace pixelarium::ui +{ +class PixelariumImageView +{ + using Image = imaging::PixelariumImage; + using Render = render::CvMatRender; + + public: + explicit PixelariumImageView(const std::shared_ptr& img) : img_(img) { render_ = Render(img_); } + PixelariumImageView() = delete; + PixelariumImageView(PixelariumImageView&) = delete; + PixelariumImageView(const PixelariumImageView&) = delete; + PixelariumImageView(PixelariumImageView&&) = delete; + PixelariumImageView& operator=(PixelariumImageView&) = delete; + PixelariumImageView& operator=(PixelariumImageView&&) = delete; + + void ToggleView(bool target) { open_p = target; } + void ShowImage(); + + private: + const std::shared_ptr img_; + Render render_; + bool open_p{false}; + ImVec2 curr_dim_{}; +}; +} // namespace pixelarium::ui diff --git a/tests/lib/resources/test_resource.cpp b/tests/lib/resources/test_resource.cpp index 69fcb97..4cbf38d 100644 --- a/tests/lib/resources/test_resource.cpp +++ b/tests/lib/resources/test_resource.cpp @@ -82,9 +82,21 @@ TEST(ImageResourcePoolTest, EnumerateResources) auto id2 = pool.SetResource(std::make_unique()); std::vector found_ids{}; - std::function func = - [&found_ids](size_t id, const pixelarium::imaging::PixelariumImage&) { found_ids.push_back(id); }; - pool.EnumerateResources(func); + pool.EnumerateResources([&found_ids](size_t id, const pixelarium::imaging::PixelariumImage&) { found_ids.push_back(id); }); + + EXPECT_EQ(found_ids.size(), 2); + EXPECT_NE(std::find(found_ids.begin(), found_ids.end(), id1), found_ids.end()); + EXPECT_NE(std::find(found_ids.begin(), found_ids.end(), id2), found_ids.end()); +} + +TEST(ImageResourcePoolTest, TemplatedEnumerate) +{ + ImageResourcePool pool; + auto id1 = pool.SetResource(std::make_unique()); + auto id2 = pool.SetResource(std::make_unique()); + std::vector found_ids{}; + + pool.Enumerate([&found_ids](size_t id, const pixelarium::imaging::PixelariumImage&) { found_ids.push_back(id); }); EXPECT_EQ(found_ids.size(), 2); EXPECT_NE(std::find(found_ids.begin(), found_ids.end(), id1), found_ids.end());