From e005abeee2b5f518d78f2b852b4a61a670dd42e4 Mon Sep 17 00:00:00 2001 From: Daniil Polyakov Date: Wed, 1 Apr 2026 22:55:34 +0300 Subject: [PATCH] feat: initial project structure --- CMakeLists.txt | 89 +++++++++++++++++++++++++++++++++++++++++ app/CMakeLists.txt | 44 ++++++++++++++++++++ app/main.cpp | 13 ++++++ app/mainwindow.cpp | 60 +++++++++++++++++++++++++++ app/mainwindow.h | 14 +++++++ flake.lock | 27 +++++++++++++ flake.nix | 53 ++++++++++++++++++++++++ interfaces/IComponent.h | 17 ++++++++ metadata.json | 10 +++++ nix/app.nix | 64 +++++++++++++++++++++++++++++ nix/default.nix | 27 +++++++++++++ nix/lib.nix | 45 +++++++++++++++++++++ src/ExplorerPlugin.cpp | 17 ++++++++ src/ExplorerPlugin.h | 18 +++++++++ src/ExplorerWidget.cpp | 38 ++++++++++++++++++ src/ExplorerWidget.h | 13 ++++++ 16 files changed, 549 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 app/CMakeLists.txt create mode 100644 app/main.cpp create mode 100644 app/mainwindow.cpp create mode 100644 app/mainwindow.h create mode 100644 flake.lock create mode 100644 flake.nix create mode 100644 interfaces/IComponent.h create mode 100644 metadata.json create mode 100644 nix/app.nix create mode 100644 nix/default.nix create mode 100644 nix/lib.nix create mode 100644 src/ExplorerPlugin.cpp create mode 100644 src/ExplorerPlugin.h create mode 100644 src/ExplorerWidget.cpp create mode 100644 src/ExplorerWidget.h diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..1cb8436 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,89 @@ +cmake_minimum_required(VERSION 3.16) +project(LEZExplorerUIPlugin VERSION 1.0.0 LANGUAGES CXX) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTORCC ON) +set(CMAKE_AUTOUIC ON) + +# Find Qt packages +find_package(Qt6 REQUIRED COMPONENTS Core Widgets) + +# Try to find the component-interfaces package first +find_package(component-interfaces QUIET) + +# If not found, use the local interfaces folder +if(NOT component-interfaces_FOUND) + include_directories(${CMAKE_CURRENT_SOURCE_DIR}/interfaces) + add_library(component-interfaces INTERFACE) + target_include_directories(component-interfaces INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/interfaces) +endif() + +# Source files +set(SOURCES + src/ExplorerPlugin.cpp + src/ExplorerPlugin.h + src/ExplorerWidget.cpp + src/ExplorerWidget.h +) + +# Create the plugin library +add_library(lez_explorer_ui SHARED ${SOURCES}) + +# Set output name without lib prefix +set_target_properties(lez_explorer_ui PROPERTIES + PREFIX "" + OUTPUT_NAME "lez_explorer_ui" +) + +# Include directories +target_include_directories(lez_explorer_ui PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/src + ${CMAKE_CURRENT_BINARY_DIR} +) + +# Link against libraries +target_link_libraries(lez_explorer_ui PRIVATE + Qt6::Core + Qt6::Widgets + component-interfaces +) + +# Set common properties for both platforms +set_target_properties(lez_explorer_ui PROPERTIES + LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/modules" + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/modules" + BUILD_WITH_INSTALL_RPATH TRUE + SKIP_BUILD_RPATH FALSE +) + +if(APPLE) + set_target_properties(lez_explorer_ui PROPERTIES + INSTALL_RPATH "@loader_path" + INSTALL_NAME_DIR "@rpath" + BUILD_WITH_INSTALL_NAME_DIR TRUE + ) + add_custom_command(TARGET lez_explorer_ui POST_BUILD + COMMAND install_name_tool -id "@rpath/lez_explorer_ui.dylib" $ + COMMENT "Updating library paths for macOS" + ) +else() + set_target_properties(lez_explorer_ui PROPERTIES + INSTALL_RPATH "$ORIGIN" + INSTALL_RPATH_USE_LINK_PATH FALSE + ) +endif() + +install(TARGETS lez_explorer_ui + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}/logos/modules + RUNTIME DESTINATION ${CMAKE_INSTALL_LIBDIR}/logos/modules + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}/logos/modules +) + +install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/metadata.json + DESTINATION ${CMAKE_INSTALL_DATADIR}/lez-explorer-ui +) + +message(STATUS "LEZ Explorer UI Plugin configured successfully") diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt new file mode 100644 index 0000000..88bb6fc --- /dev/null +++ b/app/CMakeLists.txt @@ -0,0 +1,44 @@ +cmake_minimum_required(VERSION 3.16) +project(LEZExplorerUIApp LANGUAGES CXX) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_AUTOMOC ON) +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +# Find Qt packages +find_package(Qt6 REQUIRED COMPONENTS Core Widgets) + +# Set output directories +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) + +# Create the executable +add_executable(lez-explorer-ui-app + main.cpp + mainwindow.cpp + mainwindow.h +) + +# Link libraries +target_link_libraries(lez-explorer-ui-app PRIVATE + Qt6::Core + Qt6::Widgets +) + +# Set RPATH settings for the executable +if(APPLE) + set_target_properties(lez-explorer-ui-app PROPERTIES + INSTALL_RPATH "@executable_path/../lib" + BUILD_WITH_INSTALL_RPATH TRUE + ) +elseif(UNIX) + set_target_properties(lez-explorer-ui-app PROPERTIES + INSTALL_RPATH "$ORIGIN/../lib" + BUILD_WITH_INSTALL_RPATH TRUE + ) +endif() + +# Install rules +install(TARGETS lez-explorer-ui-app + RUNTIME DESTINATION bin +) diff --git a/app/main.cpp b/app/main.cpp new file mode 100644 index 0000000..8d904da --- /dev/null +++ b/app/main.cpp @@ -0,0 +1,13 @@ +#include "mainwindow.h" + +#include + +int main(int argc, char* argv[]) +{ + QApplication app(argc, argv); + + MainWindow window; + window.show(); + + return app.exec(); +} diff --git a/app/mainwindow.cpp b/app/mainwindow.cpp new file mode 100644 index 0000000..67ea477 --- /dev/null +++ b/app/mainwindow.cpp @@ -0,0 +1,60 @@ +#include "mainwindow.h" + +#include + +MainWindow::MainWindow(QWidget* parent) + : QMainWindow(parent) +{ + setupUi(); +} + +MainWindow::~MainWindow() = default; + +void MainWindow::setupUi() +{ + // Determine the appropriate plugin extension based on the platform + QString pluginExtension; +#if defined(Q_OS_WIN) + pluginExtension = ".dll"; +#elif defined(Q_OS_MAC) + pluginExtension = ".dylib"; +#else + pluginExtension = ".so"; +#endif + + QString pluginPath = QCoreApplication::applicationDirPath() + "/../lez_explorer_ui" + pluginExtension; + QPluginLoader loader(pluginPath); + + QWidget* explorerWidget = nullptr; + + if (loader.load()) { + QObject* plugin = loader.instance(); + if (plugin) { + QMetaObject::invokeMethod(plugin, "createWidget", + Qt::DirectConnection, + Q_RETURN_ARG(QWidget*, explorerWidget)); + } + } + + if (explorerWidget) { + setCentralWidget(explorerWidget); + } else { + qWarning() << "Failed to load LEZ Explorer UI plugin from:" << pluginPath; + qWarning() << "Error:" << loader.errorString(); + + auto* fallbackWidget = new QWidget(this); + auto* layout = new QVBoxLayout(fallbackWidget); + + auto* messageLabel = new QLabel("LEZ Explorer UI module not loaded", fallbackWidget); + QFont font = messageLabel->font(); + font.setPointSize(14); + messageLabel->setFont(font); + messageLabel->setAlignment(Qt::AlignCenter); + + layout->addWidget(messageLabel); + setCentralWidget(fallbackWidget); + } + + setWindowTitle("LEZ Explorer"); + resize(1024, 768); +} diff --git a/app/mainwindow.h b/app/mainwindow.h new file mode 100644 index 0000000..90a9e50 --- /dev/null +++ b/app/mainwindow.h @@ -0,0 +1,14 @@ +#pragma once + +#include + +class MainWindow : public QMainWindow { + Q_OBJECT + +public: + explicit MainWindow(QWidget* parent = nullptr); + ~MainWindow() override; + +private: + void setupUi(); +}; diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..fe08f56 --- /dev/null +++ b/flake.lock @@ -0,0 +1,27 @@ +{ + "nodes": { + "nixpkgs": { + "locked": { + "lastModified": 1751274312, + "narHash": "sha256-/bVBlRpECLVzjV19t5KMdMFWSwKLtb5RyXdjz3LJT+g=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "50ab793786d9de88ee30ec4e4c24fb4236fc2674", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-24.11", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "nixpkgs": "nixpkgs" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..30b99af --- /dev/null +++ b/flake.nix @@ -0,0 +1,53 @@ +{ + description = "LEZ Explorer UI - A Qt block explorer for the Logos Execution Zone"; + + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.11"; + }; + + outputs = { self, nixpkgs }: + let + systems = [ "aarch64-darwin" "x86_64-darwin" "aarch64-linux" "x86_64-linux" ]; + forAllSystems = f: nixpkgs.lib.genAttrs systems (system: f { + pkgs = import nixpkgs { inherit system; }; + }); + in + { + packages = forAllSystems ({ pkgs }: + let + common = import ./nix/default.nix { inherit pkgs; }; + src = ./.; + + lib = import ./nix/lib.nix { inherit pkgs common src; }; + + app = import ./nix/app.nix { + inherit pkgs common src; + lezExplorerUI = lib; + }; + in + { + lez-explorer-ui-lib = lib; + app = app; + lib = lib; + default = lib; + } + ); + + devShells = forAllSystems ({ pkgs }: { + default = pkgs.mkShell { + nativeBuildInputs = [ + pkgs.cmake + pkgs.ninja + pkgs.pkg-config + ]; + buildInputs = [ + pkgs.qt6.qtbase + ]; + + shellHook = '' + echo "LEZ Explorer UI development environment" + ''; + }; + }); + }; +} diff --git a/interfaces/IComponent.h b/interfaces/IComponent.h new file mode 100644 index 0000000..06ad57a --- /dev/null +++ b/interfaces/IComponent.h @@ -0,0 +1,17 @@ +#pragma once + +#include +#include +#include + +class LogosAPI; + +class IComponent { +public: + virtual ~IComponent() = default; + virtual QWidget* createWidget(LogosAPI* logosAPI = nullptr) = 0; + virtual void destroyWidget(QWidget* widget) = 0; +}; + +#define IComponent_iid "com.logos.component.IComponent" +Q_DECLARE_INTERFACE(IComponent, IComponent_iid) diff --git a/metadata.json b/metadata.json new file mode 100644 index 0000000..0907f1f --- /dev/null +++ b/metadata.json @@ -0,0 +1,10 @@ +{ + "name": "lez_explorer_ui", + "version": "1.0.0", + "description": "LEZ Explorer UI - A block explorer for the Logos Execution Zone", + "type": "ui", + "main": "lez_explorer_ui", + "dependencies": [], + "category": "explorer", + "capabilities": ["ui_components"] +} diff --git a/nix/app.nix b/nix/app.nix new file mode 100644 index 0000000..6f96a0d --- /dev/null +++ b/nix/app.nix @@ -0,0 +1,64 @@ +# Builds the lez-explorer-ui-app standalone application +{ pkgs, common, src, lezExplorerUI }: + +pkgs.stdenv.mkDerivation rec { + pname = "lez-explorer-ui-app"; + version = common.version; + + inherit src; + inherit (common) buildInputs meta; + + nativeBuildInputs = common.nativeBuildInputs ++ pkgs.lib.optionals pkgs.stdenv.isLinux [ pkgs.patchelf ]; + + # This is a GUI application, enable Qt wrapping + dontWrapQtApps = false; + + qtLibPath = pkgs.lib.makeLibraryPath ([ + pkgs.qt6.qtbase + ] ++ pkgs.lib.optionals pkgs.stdenv.isLinux [ + pkgs.libglvnd + ]); + + qtPluginPath = "${pkgs.qt6.qtbase}/lib/qt-6/plugins"; + + qtWrapperArgs = [ + "--prefix" "LD_LIBRARY_PATH" ":" qtLibPath + "--prefix" "QT_PLUGIN_PATH" ":" qtPluginPath + ]; + + configurePhase = '' + runHook preConfigure + cmake -S app -B build \ + -GNinja \ + -DCMAKE_BUILD_TYPE=Release + runHook postConfigure + ''; + + buildPhase = '' + runHook preBuild + cmake --build build + runHook postBuild + ''; + + installPhase = '' + runHook preInstall + + mkdir -p $out/bin $out/lib + + # Install the app binary + cp build/bin/lez-explorer-ui-app $out/bin/ + + # Determine platform-specific plugin extension + OS_EXT="so" + case "$(uname -s)" in + Darwin) OS_EXT="dylib";; + esac + + # Copy the explorer UI plugin to root directory (loaded relative to binary) + if [ -f "${lezExplorerUI}/lib/lez_explorer_ui.$OS_EXT" ]; then + cp -L "${lezExplorerUI}/lib/lez_explorer_ui.$OS_EXT" "$out/" + fi + + runHook postInstall + ''; +} diff --git a/nix/default.nix b/nix/default.nix new file mode 100644 index 0000000..96cc00b --- /dev/null +++ b/nix/default.nix @@ -0,0 +1,27 @@ +# Common build configuration shared across all packages +{ pkgs }: + +{ + pname = "lez-explorer-ui"; + version = "1.0.0"; + + nativeBuildInputs = [ + pkgs.cmake + pkgs.ninja + pkgs.pkg-config + pkgs.qt6.wrapQtAppsHook + ]; + + buildInputs = [ + pkgs.qt6.qtbase + ]; + + cmakeFlags = [ + "-GNinja" + ]; + + meta = with pkgs.lib; { + description = "LEZ Explorer UI - A Qt block explorer for the Logos Execution Zone"; + platforms = platforms.unix; + }; +} diff --git a/nix/lib.nix b/nix/lib.nix new file mode 100644 index 0000000..1eb260e --- /dev/null +++ b/nix/lib.nix @@ -0,0 +1,45 @@ +# Builds the lez-explorer-ui library (Qt plugin) +{ pkgs, common, src }: + +pkgs.stdenv.mkDerivation { + pname = "${common.pname}-lib"; + version = common.version; + + inherit src; + inherit (common) buildInputs cmakeFlags meta; + nativeBuildInputs = common.nativeBuildInputs; + + # Library (Qt plugin), not an app — no Qt wrapper + dontWrapQtApps = true; + + configurePhase = '' + runHook preConfigure + cmake -S . -B build \ + -GNinja \ + -DCMAKE_BUILD_TYPE=Release + runHook postConfigure + ''; + + buildPhase = '' + runHook preBuild + cmake --build build + runHook postBuild + ''; + + installPhase = '' + runHook preInstall + + mkdir -p $out/lib + if [ -f build/modules/lez_explorer_ui.dylib ]; then + cp build/modules/lez_explorer_ui.dylib $out/lib/ + elif [ -f build/modules/lez_explorer_ui.so ]; then + cp build/modules/lez_explorer_ui.so $out/lib/ + else + echo "Error: No library file found in build/modules/" + ls -la build/modules/ 2>/dev/null || true + exit 1 + fi + + runHook postInstall + ''; +} diff --git a/src/ExplorerPlugin.cpp b/src/ExplorerPlugin.cpp new file mode 100644 index 0000000..58aaeb5 --- /dev/null +++ b/src/ExplorerPlugin.cpp @@ -0,0 +1,17 @@ +#include "ExplorerPlugin.h" +#include "ExplorerWidget.h" + +ExplorerPlugin::ExplorerPlugin(QObject* parent) + : QObject(parent) +{ +} + +QWidget* ExplorerPlugin::createWidget(LogosAPI* /*logosAPI*/) +{ + return new ExplorerWidget(); +} + +void ExplorerPlugin::destroyWidget(QWidget* widget) +{ + delete widget; +} diff --git a/src/ExplorerPlugin.h b/src/ExplorerPlugin.h new file mode 100644 index 0000000..458b9ec --- /dev/null +++ b/src/ExplorerPlugin.h @@ -0,0 +1,18 @@ +#pragma once + +#include "IComponent.h" + +#include +#include + +class ExplorerPlugin : public QObject, public IComponent { + Q_OBJECT + Q_INTERFACES(IComponent) + Q_PLUGIN_METADATA(IID IComponent_iid FILE "metadata.json") + +public: + explicit ExplorerPlugin(QObject* parent = nullptr); + + Q_INVOKABLE QWidget* createWidget(LogosAPI* logosAPI = nullptr) override; + void destroyWidget(QWidget* widget) override; +}; diff --git a/src/ExplorerWidget.cpp b/src/ExplorerWidget.cpp new file mode 100644 index 0000000..76b72bb --- /dev/null +++ b/src/ExplorerWidget.cpp @@ -0,0 +1,38 @@ +#include "ExplorerWidget.h" + +#include +#include +#include + +ExplorerWidget::ExplorerWidget(QWidget* parent) + : QWidget(parent) +{ + auto* layout = new QVBoxLayout(this); + + auto* label = new QLabel("LEZ Explorer", this); + label->setAlignment(Qt::AlignCenter); + QFont font = label->font(); + font.setPointSize(24); + font.setBold(true); + label->setFont(font); + label->setStyleSheet("color: white;"); + + layout->addWidget(label); +} + +void ExplorerWidget::paintEvent(QPaintEvent* /*event*/) +{ + QPainter painter(this); + painter.setRenderHint(QPainter::Antialiasing); + + // Draw a blue rectangle filling the widget + painter.setBrush(QColor(30, 60, 120)); + painter.setPen(Qt::NoPen); + painter.drawRect(rect()); + + // Draw a lighter inner rectangle + int margin = 40; + QRect inner = rect().adjusted(margin, margin, -margin, -margin); + painter.setBrush(QColor(40, 80, 160)); + painter.drawRoundedRect(inner, 16, 16); +} diff --git a/src/ExplorerWidget.h b/src/ExplorerWidget.h new file mode 100644 index 0000000..341247f --- /dev/null +++ b/src/ExplorerWidget.h @@ -0,0 +1,13 @@ +#pragma once + +#include + +class ExplorerWidget : public QWidget { + Q_OBJECT + +public: + explicit ExplorerWidget(QWidget* parent = nullptr); + +protected: + void paintEvent(QPaintEvent* event) override; +};