From 0a569ba430716865ffdf886b5c8d7af56c9a81ed Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Mon, 19 Feb 2024 09:32:16 +0100 Subject: [PATCH 01/20] Add googletest submodule --- .gitmodules | 3 +++ thirdparty/googletest | 1 + 2 files changed, 4 insertions(+) create mode 160000 thirdparty/googletest diff --git a/.gitmodules b/.gitmodules index 089a429..f6af83a 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "scratchcpp-render"] path = scratchcpp-render url = https://github.com/scratchcpp/scratchcpp-render +[submodule "thirdparty/googletest"] + path = thirdparty/googletest + url = https://github.com/google/googletest/ diff --git a/thirdparty/googletest b/thirdparty/googletest new file mode 160000 index 0000000..5df0241 --- /dev/null +++ b/thirdparty/googletest @@ -0,0 +1 @@ +Subproject commit 5df0241ea4880e5a846775d3efc8b873f7b36c31 From c614f646dee1480eb20451c277fadb3ca2a53b2b Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Mon, 19 Feb 2024 09:56:48 +0100 Subject: [PATCH 02/20] Add module.cmake --- build/module.cmake | 27 +++++++++++++++++++++++++++ src/CMakeLists.txt | 2 -- src/uicomponents/CMakeLists.txt | 28 +++++++--------------------- 3 files changed, 34 insertions(+), 23 deletions(-) create mode 100644 build/module.cmake diff --git a/build/module.cmake b/build/module.cmake new file mode 100644 index 0000000..cefc9a4 --- /dev/null +++ b/build/module.cmake @@ -0,0 +1,27 @@ +qt_add_library(${MODULE} STATIC) + +set_target_properties(${MODULE} PROPERTIES AUTOMOC ON) + +qt_add_qml_module(${MODULE} + URI ScratchCPP.${MODULE_URI} + VERSION 1.0 + NO_PLUGIN + OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/ScratchCPP/${MODULE_URI} + QML_FILES + ${MODULE_QML_FILES} + RESOURCES + ${MODULE_RESOURCES} + SOURCES + ${MODULE_SRC} +) + +set(QML_IMPORT_PATH "${QML_IMPORT_PATH};${CMAKE_CURRENT_LIST_DIR}" + CACHE STRING "Qt Creator extra QML import paths" + FORCE +) + +list(APPEND QML_IMPORT_PATH ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR}) +list(REMOVE_DUPLICATES QML_IMPORT_PATH) +set(QML_IMPORT_PATH ${QML_IMPORT_PATH} CACHE STRING "" FORCE) + +target_link_libraries(appscratchcpp-player PRIVATE ${MODULE}) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 92a8408..ca1f093 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -9,5 +9,3 @@ qt_add_qml_module(appscratchcpp-player ) add_subdirectory(uicomponents) - -target_link_libraries(appscratchcpp-player PRIVATE scratchcpp-uicomponents) diff --git a/src/uicomponents/CMakeLists.txt b/src/uicomponents/CMakeLists.txt index 7e54304..3f2ab62 100644 --- a/src/uicomponents/CMakeLists.txt +++ b/src/uicomponents/CMakeLists.txt @@ -1,23 +1,9 @@ -qt_add_library(scratchcpp-uicomponents STATIC) - -set_target_properties(scratchcpp-uicomponents PROPERTIES AUTOMOC ON) - -qt_add_qml_module(scratchcpp-uicomponents - URI ScratchCPP.UiComponents - VERSION 1.0 - NO_PLUGIN - OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/ScratchCPP/UiComponents - QML_FILES - CustomButton.qml - CustomToolButton.qml - HoverToolTip.qml -) - -set(QML_IMPORT_PATH "${QML_IMPORT_PATH};${CMAKE_CURRENT_LIST_DIR}" - CACHE STRING "Qt Creator extra QML import paths" - FORCE +set(MODULE uicomponents) +set(MODULE_URI UiComponents) +set(MODULE_QML_FILES + CustomButton.qml + CustomToolButton.qml + HoverToolTip.qml ) -list(APPEND QML_IMPORT_PATH ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR}) -list(REMOVE_DUPLICATES QML_IMPORT_PATH) -set(QML_IMPORT_PATH ${QML_IMPORT_PATH} CACHE STRING "" FORCE) +include(${PROJECT_SOURCE_DIR}/build/module.cmake) From 430ceb862d73727993acf3e2adcd24cbe962a8f8 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Mon, 19 Feb 2024 10:04:40 +0100 Subject: [PATCH 03/20] Add global module --- src/CMakeLists.txt | 1 + src/global/CMakeLists.txt | 6 ++++++ 2 files changed, 7 insertions(+) create mode 100644 src/global/CMakeLists.txt diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index ca1f093..d202aa3 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -8,4 +8,5 @@ qt_add_qml_module(appscratchcpp-player QML_FILES main.qml ) +add_subdirectory(global) add_subdirectory(uicomponents) diff --git a/src/global/CMakeLists.txt b/src/global/CMakeLists.txt new file mode 100644 index 0000000..cd9da32 --- /dev/null +++ b/src/global/CMakeLists.txt @@ -0,0 +1,6 @@ +set(MODULE global) +set(MODULE_URI Global) +set(MODULE_SRC +) + +include(${PROJECT_SOURCE_DIR}/build/module.cmake) From 11ba95583b72d0752f1c79f1901218eec8201b58 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Wed, 21 Feb 2024 17:39:19 +0100 Subject: [PATCH 04/20] Add CMake API for tests --- CMakeLists.txt | 6 ++++++ build/module.cmake | 1 + build/module_test.cmake | 24 ++++++++++++++++++++++++ test/main.cpp | 9 +++++++++ 4 files changed, 40 insertions(+) create mode 100644 build/module_test.cmake create mode 100644 test/main.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 572ff8b..6b000d1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,8 +5,14 @@ project(scratchcpp-player VERSION 0.1 LANGUAGES CXX) set(CMAKE_AUTOMOC ON) set(CMAKE_CXX_STANDARD_REQUIRED ON) +option(SCRATCHCPP_PLAYER_BUILD_UNIT_TESTS "Build unit tests" ON) + find_package(Qt6 6.6 COMPONENTS Quick QuickControls2 REQUIRED) +if (SCRATCHCPP_PLAYER_BUILD_UNIT_TESTS) + find_package(Qt6 6.6 COMPONENTS Test REQUIRED) +endif() + add_subdirectory(src) add_subdirectory(res) diff --git a/build/module.cmake b/build/module.cmake index cefc9a4..843b2e7 100644 --- a/build/module.cmake +++ b/build/module.cmake @@ -25,3 +25,4 @@ list(REMOVE_DUPLICATES QML_IMPORT_PATH) set(QML_IMPORT_PATH ${QML_IMPORT_PATH} CACHE STRING "" FORCE) target_link_libraries(appscratchcpp-player PRIVATE ${MODULE}) +set(MODULE_SRC_DIR ${CMAKE_CURRENT_SOURCE_DIR}) diff --git a/build/module_test.cmake b/build/module_test.cmake new file mode 100644 index 0000000..7cd325e --- /dev/null +++ b/build/module_test.cmake @@ -0,0 +1,24 @@ +if (SCRATCHCPP_PLAYER_BUILD_UNIT_TESTS) + set(TARGET ${MODULE}_test) + set(TEST_MAIN_SRC ${PROJECT_SOURCE_DIR}/test/main.cpp) + set(GTEST_DIR thirdparty/googletest) + add_subdirectory(${PROJECT_SOURCE_DIR}/${GTEST_DIR} ${CMAKE_CURRENT_BINARY_DIR}/${GTEST_DIR}) + + include(GoogleTest) + + add_executable( + ${TARGET} + ${TEST_MAIN_SRC} + ${MODULE_TEST_SRC} + ) + + target_link_libraries( + ${TARGET} + GTest::gtest_main + GTest::gmock_main + Qt6::Gui + Qt6::Test + ) + + target_include_directories(${TARGET} PRIVATE ${MODULE_SRC_DIR}) +endif() diff --git a/test/main.cpp b/test/main.cpp new file mode 100644 index 0000000..65ecd3f --- /dev/null +++ b/test/main.cpp @@ -0,0 +1,9 @@ +#include +#include + +int main(int argc, char **argv) +{ + QGuiApplication a(argc, argv); + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} From 8ac3741c789705940057fe51d823a152c3419bad Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Wed, 21 Feb 2024 20:54:06 +0100 Subject: [PATCH 05/20] Add modularity API --- src/global/CMakeLists.txt | 6 ++ .../modularity/imoduleexportinterface.h | 18 +++++ src/global/modularity/imodulesetup.h | 28 ++++++++ src/global/modularity/ioc.h | 32 +++++++++ src/global/modularity/modulesioc.h | 67 +++++++++++++++++++ src/global/test/CMakeLists.txt | 9 +++ src/global/test/fakedependency.h | 11 +++ src/global/test/fakeexport.h | 16 +++++ .../test/mocks/moduleexportinterfacemock.h | 11 +++ src/global/test/mocks/modulesetupmock.h | 25 +++++++ src/global/test/modularity.cpp | 44 ++++++++++++ 11 files changed, 267 insertions(+) create mode 100644 src/global/modularity/imoduleexportinterface.h create mode 100644 src/global/modularity/imodulesetup.h create mode 100644 src/global/modularity/ioc.h create mode 100644 src/global/modularity/modulesioc.h create mode 100644 src/global/test/CMakeLists.txt create mode 100644 src/global/test/fakedependency.h create mode 100644 src/global/test/fakeexport.h create mode 100644 src/global/test/mocks/moduleexportinterfacemock.h create mode 100644 src/global/test/mocks/modulesetupmock.h create mode 100644 src/global/test/modularity.cpp diff --git a/src/global/CMakeLists.txt b/src/global/CMakeLists.txt index cd9da32..0570c3f 100644 --- a/src/global/CMakeLists.txt +++ b/src/global/CMakeLists.txt @@ -1,6 +1,12 @@ set(MODULE global) set(MODULE_URI Global) set(MODULE_SRC + modularity/ioc.h + modularity/modulesioc.h + modularity/imoduleexportinterface.h + modularity/imodulesetup.h ) include(${PROJECT_SOURCE_DIR}/build/module.cmake) + +add_subdirectory(test) diff --git a/src/global/modularity/imoduleexportinterface.h b/src/global/modularity/imoduleexportinterface.h new file mode 100644 index 0000000..6199141 --- /dev/null +++ b/src/global/modularity/imoduleexportinterface.h @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#define MODULE_EXPORT_INTERFACE \ +public \ + scratchcpp::modularity::IModuleExportInterface + +namespace scratchcpp::modularity +{ + +class IModuleExportInterface +{ + public: + virtual ~IModuleExportInterface() { } +}; + +} // namespace scratchcpp::modularity diff --git a/src/global/modularity/imodulesetup.h b/src/global/modularity/imodulesetup.h new file mode 100644 index 0000000..be4d642 --- /dev/null +++ b/src/global/modularity/imodulesetup.h @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include + +namespace scratchcpp::modularity +{ + +class IModuleSetup +{ + public: + virtual ~IModuleSetup() { } + + virtual std::string moduleName() const = 0; + + virtual void registerExports() { } + virtual void initSettings() { } + + virtual void onPreInit() { } + virtual void onInit() { } + virtual void onDeinit() { } + virtual void onDestroy() { } + + virtual void onStartApp() { } +}; + +} // namespace scratchcpp::modularity diff --git a/src/global/modularity/ioc.h b/src/global/modularity/ioc.h new file mode 100644 index 0000000..38aff64 --- /dev/null +++ b/src/global/modularity/ioc.h @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include "modulesioc.h" + +#define INJECT(Interface, getter) \ +private: \ + static inline std::shared_ptr _##getter = nullptr; \ + \ +public: \ + static std::shared_ptr getter() \ + { \ + if (!_##getter) { \ + _##getter = modularity::ioc()->resolve(); \ + } \ + return _##getter; \ + } \ + static void set##getter(std::shared_ptr impl) \ + { \ + _##getter = impl; \ + } + +namespace scratchcpp::modularity +{ + +inline ModulesIoC *ioc() +{ + return ModulesIoC::instance(); +} + +} // namespace scratchcpp::modularity diff --git a/src/global/modularity/modulesioc.h b/src/global/modularity/modulesioc.h new file mode 100644 index 0000000..ba42540 --- /dev/null +++ b/src/global/modularity/modulesioc.h @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include +#include +#include +#include +#include "imoduleexportinterface.h" + +namespace scratchcpp::modularity +{ + +class ModulesIoC +{ + public: + static ModulesIoC *instance() + { + static ModulesIoC instance; + return &instance; + } + + template + void registerExport(std::shared_ptr instance) + { + // Remove old exports of this class + std::vector> toRemove; + + for (auto ex : m_exports) { + std::shared_ptr exCast = std::dynamic_pointer_cast(ex); + + if (exCast) + toRemove.push_back(exCast); + } + + for (auto ex : toRemove) + m_exports.erase(ex); + + // Register the export + auto iface = std::dynamic_pointer_cast(instance); + assert(m_exports.find(iface) == m_exports.end()); + + if (!iface) { + assert(false); + return; + } + + m_exports.insert(iface); + } + + template + std::shared_ptr resolve() const + { + for (auto ex : m_exports) { + std::shared_ptr exCast = std::dynamic_pointer_cast(ex); + if (exCast) + return exCast; + } + + return nullptr; + } + + private: + std::unordered_set> m_exports; +}; + +} // namespace scratchcpp::modularity diff --git a/src/global/test/CMakeLists.txt b/src/global/test/CMakeLists.txt new file mode 100644 index 0000000..08e9c4d --- /dev/null +++ b/src/global/test/CMakeLists.txt @@ -0,0 +1,9 @@ +set(MODULE_TEST_SRC + modularity.cpp + fakeexport.h + fakedependency.h + mocks/moduleexportinterfacemock.h + mocks/modulesetupmock.h +) + +include(${PROJECT_SOURCE_DIR}/build/module_test.cmake) diff --git a/src/global/test/fakedependency.h b/src/global/test/fakedependency.h new file mode 100644 index 0000000..9d7466f --- /dev/null +++ b/src/global/test/fakedependency.h @@ -0,0 +1,11 @@ +#pragma once + +#include + +namespace scratchcpp +{ + +class FakeDependency : MODULE_EXPORT_INTERFACE +{ }; + +} // namespace scratchcpp diff --git a/src/global/test/fakeexport.h b/src/global/test/fakeexport.h new file mode 100644 index 0000000..77bdc19 --- /dev/null +++ b/src/global/test/fakeexport.h @@ -0,0 +1,16 @@ +#pragma once + +#include +#include + +#include "fakedependency.h" + +namespace scratchcpp +{ + +class FakeExport : MODULE_EXPORT_INTERFACE +{ + INJECT(FakeDependency, dep); +}; + +} // namespace scratchcpp diff --git a/src/global/test/mocks/moduleexportinterfacemock.h b/src/global/test/mocks/moduleexportinterfacemock.h new file mode 100644 index 0000000..ce14ff7 --- /dev/null +++ b/src/global/test/mocks/moduleexportinterfacemock.h @@ -0,0 +1,11 @@ +#pragma once + +#include + +namespace scratchcpp::modularity +{ + +class ModuleExportInterfaceMock : public IModuleExportInterface +{ }; + +} // namespace scratchcpp::modularity diff --git a/src/global/test/mocks/modulesetupmock.h b/src/global/test/mocks/modulesetupmock.h new file mode 100644 index 0000000..4a33b61 --- /dev/null +++ b/src/global/test/mocks/modulesetupmock.h @@ -0,0 +1,25 @@ +#pragma once + +#include +#include + +namespace scratchcpp::modularity +{ + +class ModuleSetupMock : public IModuleSetup +{ + public: + MOCK_METHOD(std::string, moduleName, (), (const, override)); + + MOCK_METHOD(void, registerExports, (), (override)); + MOCK_METHOD(void, initSettings, (), (override)); + + MOCK_METHOD(void, onPreInit, (), (override)); + MOCK_METHOD(void, onInit, (), (override)); + MOCK_METHOD(void, onDeinit, (), (override)); + MOCK_METHOD(void, onDestroy, (), (override)); + + MOCK_METHOD(void, onStartApp, (), (override)); +}; + +} // namespace scratchcpp::modularity diff --git a/src/global/test/modularity.cpp b/src/global/test/modularity.cpp new file mode 100644 index 0000000..e3807f8 --- /dev/null +++ b/src/global/test/modularity.cpp @@ -0,0 +1,44 @@ +#include +#include + +#include "mocks/moduleexportinterfacemock.h" +#include "fakeexport.h" + +using namespace scratchcpp; +using namespace scratchcpp::modularity; + +TEST(ModularityTest, StaticInstance) +{ + ASSERT_TRUE(ioc()); +} + +TEST(ModularityTest, Exports) +{ + auto mock = std::make_shared(); + auto fake = std::make_shared(); + ioc()->registerExport(mock); + ioc()->registerExport(fake); + ASSERT_EQ(ioc()->resolve(), mock); + ASSERT_EQ(ioc()->resolve(), fake); + ASSERT_EQ(ioc()->resolve(), mock); + ASSERT_NE(ioc()->resolve(), std::static_pointer_cast(fake)); + ASSERT_NE(ioc()->resolve(), std::static_pointer_cast(mock)); + + auto another = std::make_shared(); + ioc()->registerExport(another); + ASSERT_EQ(ioc()->resolve(), mock); + ASSERT_EQ(ioc()->resolve(), another); +} + +TEST(ModularityTest, Inject) +{ + auto fake = std::make_shared(); + auto dep1 = std::make_shared(); + ioc()->registerExport(fake); + ioc()->registerExport(dep1); + ASSERT_EQ(fake->dep(), dep1); + + auto dep2 = std::make_shared(); + fake->setdep(dep2); + ASSERT_EQ(fake->dep(), dep2); +} From cb08a5d1688c3d3186986819f0851c90333f6b15 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Wed, 21 Feb 2024 20:56:38 +0100 Subject: [PATCH 06/20] Add unit test workflow --- .github/workflows/utests.yml | 37 ++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 .github/workflows/utests.yml diff --git a/.github/workflows/utests.yml b/.github/workflows/utests.yml new file mode 100644 index 0000000..34cbebc --- /dev/null +++ b/.github/workflows/utests.yml @@ -0,0 +1,37 @@ +name: Unit tests + +on: + push: + branches: '*' + pull_request: + branches: [ "master" ] + +env: + BUILD_TYPE: Debug + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + with: + submodules: 'recursive' + + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install -y nlohmann-json3-dev libutfcpp-dev xvfb libxcb-cursor0 + shell: bash + - name: Install Qt + uses: jurplel/install-qt-action@v3 + with: + version: '6.6.*' + - name: Configure CMake + run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DSCRATCHCPP_PLAYER_BUILD_UNIT_TESTS=ON + + - name: Build + run: xvfb-run cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} -j$(nproc --all) + + - name: Run unit tests + run: xvfb-run ctest --test-dir build -V From 9933a1a6261935e15f706e2f4bcfd272a9e70d29 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Wed, 21 Feb 2024 21:11:18 +0100 Subject: [PATCH 07/20] Fix test discovery --- CMakeLists.txt | 1 + build/module_test.cmake | 1 + 2 files changed, 2 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6b000d1..f5ee60a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,6 +10,7 @@ option(SCRATCHCPP_PLAYER_BUILD_UNIT_TESTS "Build unit tests" ON) find_package(Qt6 6.6 COMPONENTS Quick QuickControls2 REQUIRED) if (SCRATCHCPP_PLAYER_BUILD_UNIT_TESTS) + enable_testing() find_package(Qt6 6.6 COMPONENTS Test REQUIRED) endif() diff --git a/build/module_test.cmake b/build/module_test.cmake index 7cd325e..93e6e99 100644 --- a/build/module_test.cmake +++ b/build/module_test.cmake @@ -21,4 +21,5 @@ if (SCRATCHCPP_PLAYER_BUILD_UNIT_TESTS) ) target_include_directories(${TARGET} PRIVATE ${MODULE_SRC_DIR}) + gtest_discover_tests(${TARGET}) endif() From 956e82c1ff80017b9c29f5f707a6e24a6400c027 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Thu, 22 Feb 2024 08:00:59 +0100 Subject: [PATCH 08/20] Move main source files to app subdir --- CMakeLists.txt | 20 +++----------------- build/module.cmake | 2 +- res/icons/CMakeLists.txt | 4 ++-- src/CMakeLists.txt | 10 +--------- src/app/CMakeLists.txt | 26 ++++++++++++++++++++++++++ src/{ => app}/main.cpp | 0 src/{ => app}/main.qml | 0 7 files changed, 33 insertions(+), 29 deletions(-) create mode 100644 src/app/CMakeLists.txt rename src/{ => app}/main.cpp (100%) rename src/{ => app}/main.qml (100%) diff --git a/CMakeLists.txt b/CMakeLists.txt index f5ee60a..af8487a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,22 +14,8 @@ if (SCRATCHCPP_PLAYER_BUILD_UNIT_TESTS) find_package(Qt6 6.6 COMPONENTS Test REQUIRED) endif() -add_subdirectory(src) -add_subdirectory(res) - -set_target_properties(appscratchcpp-player PROPERTIES - MACOSX_BUNDLE_GUI_IDENTIFIER my.example.com - MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION} - MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR} - MACOSX_BUNDLE TRUE - WIN32_EXECUTABLE TRUE -) - -target_compile_definitions(appscratchcpp-player - PRIVATE $<$,$>:QT_QML_DEBUG>) -target_link_libraries(appscratchcpp-player - PRIVATE Qt6::Quick Qt6::QuickControls2) - set(SCRATCHCPPRENDER_BUILD_UNIT_TESTS OFF) add_subdirectory(scratchcpp-render) -target_link_libraries(appscratchcpp-player PRIVATE scratchcpp-render scratchcpp-renderplugin) + +add_subdirectory(src) +add_subdirectory(res) diff --git a/build/module.cmake b/build/module.cmake index 843b2e7..7319015 100644 --- a/build/module.cmake +++ b/build/module.cmake @@ -24,5 +24,5 @@ list(APPEND QML_IMPORT_PATH ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_D list(REMOVE_DUPLICATES QML_IMPORT_PATH) set(QML_IMPORT_PATH ${QML_IMPORT_PATH} CACHE STRING "" FORCE) -target_link_libraries(appscratchcpp-player PRIVATE ${MODULE}) +target_link_libraries(${APP_TARGET} PRIVATE ${MODULE}) set(MODULE_SRC_DIR ${CMAKE_CURRENT_SOURCE_DIR}) diff --git a/res/icons/CMakeLists.txt b/res/icons/CMakeLists.txt index 4cf83bc..48add58 100644 --- a/res/icons/CMakeLists.txt +++ b/res/icons/CMakeLists.txt @@ -1,12 +1,12 @@ qt_add_resources( - appscratchcpp-player "icon_theme" + ${APP_TARGET} "icon_theme" PREFIX "/icons/scratchcpp" FILES index.theme ) qt_add_resources( - appscratchcpp-player "icons" + ${APP_TARGET} "icons" PREFIX "/icons/scratchcpp/32x32" FILES green_flag.svg diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index d202aa3..e952081 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,12 +1,4 @@ -qt_add_executable(appscratchcpp-player - main.cpp -) - -qt_add_qml_module(appscratchcpp-player - URI ScratchCPP - VERSION 1.0 - QML_FILES main.qml -) +add_subdirectory(app) add_subdirectory(global) add_subdirectory(uicomponents) diff --git a/src/app/CMakeLists.txt b/src/app/CMakeLists.txt new file mode 100644 index 0000000..931bf63 --- /dev/null +++ b/src/app/CMakeLists.txt @@ -0,0 +1,26 @@ +set(APP_TARGET scratchcpp-player CACHE INTERNAL "") + +qt_add_executable(${APP_TARGET} + main.cpp +) + +qt_add_qml_module(${APP_TARGET} + URI ScratchCPP + VERSION 1.0 + QML_FILES main.qml +) + +set_target_properties(${APP_TARGET} PROPERTIES + MACOSX_BUNDLE_GUI_IDENTIFIER my.example.com + MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION} + MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR} + MACOSX_BUNDLE TRUE + WIN32_EXECUTABLE TRUE +) + +target_compile_definitions(${APP_TARGET} + PRIVATE $<$,$>:QT_QML_DEBUG>) +target_link_libraries(${APP_TARGET} + PRIVATE Qt6::Quick Qt6::QuickControls2) + +target_link_libraries(${APP_TARGET} PRIVATE scratchcpp-render scratchcpp-renderplugin) diff --git a/src/main.cpp b/src/app/main.cpp similarity index 100% rename from src/main.cpp rename to src/app/main.cpp diff --git a/src/main.qml b/src/app/main.qml similarity index 100% rename from src/main.qml rename to src/app/main.qml From 3aac490d0b86f98a316b12a1cc3fde6403dcc516 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Thu, 22 Feb 2024 19:13:16 +0100 Subject: [PATCH 09/20] Link with module in module_test.cmake --- build/module_test.cmake | 1 + 1 file changed, 1 insertion(+) diff --git a/build/module_test.cmake b/build/module_test.cmake index 93e6e99..0173d21 100644 --- a/build/module_test.cmake +++ b/build/module_test.cmake @@ -14,6 +14,7 @@ if (SCRATCHCPP_PLAYER_BUILD_UNIT_TESTS) target_link_libraries( ${TARGET} + ${MODULE} GTest::gtest_main GTest::gmock_main Qt6::Gui From 0718a2e344a25f242c5f494def562d8858648ca8 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Thu, 22 Feb 2024 19:44:14 +0100 Subject: [PATCH 10/20] Add reset method to ModulesIoC --- src/global/modularity/modulesioc.h | 2 ++ src/global/test/modularity.cpp | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/src/global/modularity/modulesioc.h b/src/global/modularity/modulesioc.h index ba42540..4aef781 100644 --- a/src/global/modularity/modulesioc.h +++ b/src/global/modularity/modulesioc.h @@ -48,6 +48,8 @@ class ModulesIoC m_exports.insert(iface); } + void reset() { m_exports.clear(); } + template std::shared_ptr resolve() const { diff --git a/src/global/test/modularity.cpp b/src/global/test/modularity.cpp index e3807f8..50ebe99 100644 --- a/src/global/test/modularity.cpp +++ b/src/global/test/modularity.cpp @@ -28,6 +28,10 @@ TEST(ModularityTest, Exports) ioc()->registerExport(another); ASSERT_EQ(ioc()->resolve(), mock); ASSERT_EQ(ioc()->resolve(), another); + + ioc()->reset(); + ASSERT_EQ(ioc()->resolve(), nullptr); + ASSERT_EQ(ioc()->resolve(), nullptr); } TEST(ModularityTest, Inject) @@ -41,4 +45,6 @@ TEST(ModularityTest, Inject) auto dep2 = std::make_shared(); fake->setdep(dep2); ASSERT_EQ(fake->dep(), dep2); + + ioc()->reset(); } From c20bde1c503e4730c31e7a4d1e103c5882ce2524 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Thu, 22 Feb 2024 19:49:51 +0100 Subject: [PATCH 11/20] Add GlobalModule class --- src/global/CMakeLists.txt | 2 ++ src/global/globalmodule.cpp | 10 ++++++++++ src/global/globalmodule.h | 16 ++++++++++++++++ src/global/test/CMakeLists.txt | 1 + src/global/test/setup.cpp | 11 +++++++++++ 5 files changed, 40 insertions(+) create mode 100644 src/global/globalmodule.cpp create mode 100644 src/global/globalmodule.h create mode 100644 src/global/test/setup.cpp diff --git a/src/global/CMakeLists.txt b/src/global/CMakeLists.txt index 0570c3f..8c412b6 100644 --- a/src/global/CMakeLists.txt +++ b/src/global/CMakeLists.txt @@ -1,6 +1,8 @@ set(MODULE global) set(MODULE_URI Global) set(MODULE_SRC + globalmodule.cpp + globalmodule.h modularity/ioc.h modularity/modulesioc.h modularity/imoduleexportinterface.h diff --git a/src/global/globalmodule.cpp b/src/global/globalmodule.cpp new file mode 100644 index 0000000..77a18c0 --- /dev/null +++ b/src/global/globalmodule.cpp @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "globalmodule.h" + +using namespace scratchcpp; + +std::string GlobalModule::moduleName() const +{ + return "global"; +} diff --git a/src/global/globalmodule.h b/src/global/globalmodule.h new file mode 100644 index 0000000..464a7d6 --- /dev/null +++ b/src/global/globalmodule.h @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include "modularity/imodulesetup.h" + +namespace scratchcpp +{ + +class GlobalModule : public modularity::IModuleSetup +{ + public: + std::string moduleName() const override; +}; + +} // namespace scratchcpp diff --git a/src/global/test/CMakeLists.txt b/src/global/test/CMakeLists.txt index 08e9c4d..d4e26da 100644 --- a/src/global/test/CMakeLists.txt +++ b/src/global/test/CMakeLists.txt @@ -1,5 +1,6 @@ set(MODULE_TEST_SRC modularity.cpp + setup.cpp fakeexport.h fakedependency.h mocks/moduleexportinterfacemock.h diff --git a/src/global/test/setup.cpp b/src/global/test/setup.cpp new file mode 100644 index 0000000..8700078 --- /dev/null +++ b/src/global/test/setup.cpp @@ -0,0 +1,11 @@ +#include + +#include "globalmodule.h" + +using namespace scratchcpp; + +TEST(GlobalSetupTest, ModuleName) +{ + GlobalModule module; + ASSERT_EQ(module.moduleName(), "global"); +} From b4d401ff1137b1c15bd1715f380d6d3aee1638a2 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Thu, 22 Feb 2024 19:51:02 +0100 Subject: [PATCH 12/20] Add App class --- src/app/CMakeLists.txt | 5 ++ src/app/app.cpp | 110 +++++++++++++++++++++++++++++++++++++++++ src/app/app.h | 31 ++++++++++++ src/app/main.cpp | 30 ++--------- 4 files changed, 151 insertions(+), 25 deletions(-) create mode 100644 src/app/app.cpp create mode 100644 src/app/app.h diff --git a/src/app/CMakeLists.txt b/src/app/CMakeLists.txt index 931bf63..e487472 100644 --- a/src/app/CMakeLists.txt +++ b/src/app/CMakeLists.txt @@ -2,6 +2,8 @@ set(APP_TARGET scratchcpp-player CACHE INTERNAL "") qt_add_executable(${APP_TARGET} main.cpp + app.cpp + app.h ) qt_add_qml_module(${APP_TARGET} @@ -20,7 +22,10 @@ set_target_properties(${APP_TARGET} PROPERTIES target_compile_definitions(${APP_TARGET} PRIVATE $<$,$>:QT_QML_DEBUG>) +target_compile_definitions(${APP_TARGET} PRIVATE BUILD_VERSION="${CMAKE_PROJECT_VERSION}") target_link_libraries(${APP_TARGET} PRIVATE Qt6::Quick Qt6::QuickControls2) +target_include_directories(${APP_TARGET} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/..) +target_include_directories(${APP_TARGET} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../global) target_link_libraries(${APP_TARGET} PRIVATE scratchcpp-render scratchcpp-renderplugin) diff --git a/src/app/app.cpp b/src/app/app.cpp new file mode 100644 index 0000000..fc500b8 --- /dev/null +++ b/src/app/app.cpp @@ -0,0 +1,110 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include +#include +#include +#include +#include + +#include "app.h" +#include "globalmodule.h" +#include "modularity/ioc.h" + +using namespace scratchcpp; +using namespace scratchcpp::modularity; + +App::App() +{ + addModule(new GlobalModule); +} + +int App::run(int argc, char **argv) +{ + qputenv("QSG_RENDER_LOOP", "basic"); + + // Set up application object + QGuiApplication app(argc, argv); + QCoreApplication::setOrganizationDomain("scratchcpp.github.io"); + QCoreApplication::setOrganizationName("ScratchCPP"); + QCoreApplication::setApplicationName("ScratchCPP"); + QCoreApplication::setApplicationVersion(BUILD_VERSION); + + // Set style and icon theme name + QQuickStyle::setStyle("Material"); + QIcon::setThemeName("scratchcpp"); + + // Register exports + for (IModuleSetup *module : m_modules) + module->registerExports(); + + // Init settings + for (IModuleSetup *module : m_modules) + module->initSettings(); + + // Setup modules: onPreInit + for (IModuleSetup *module : m_modules) + module->onPreInit(); + + /* Splash screen should show now, if any. */ + + // Setup modules: onInit + for (IModuleSetup *module : m_modules) + module->onInit(); + + // Setup modules: onStartApp (on next event loop) + QMetaObject::invokeMethod( + qApp, + [this]() { + for (IModuleSetup *m : m_modules) { + m->onStartApp(); + } + }, + Qt::QueuedConnection); + + // Load main.qml + QQmlApplicationEngine engine; + engine.addImportPath(":/"); + + const QUrl url(u"qrc:/ScratchCPP/main.qml"_qs); + QObject::connect( + &engine, + &QQmlApplicationEngine::objectCreated, + &app, + [url](QObject *obj, const QUrl &objUrl) { + if (!obj && url == objUrl) + QCoreApplication::exit(-1); + }, + Qt::QueuedConnection); + engine.load(url); + + // Run the event loop + int exitCode = app.exec(); + + // Deinit modules + for (IModuleSetup *module : m_modules) + module->onDeinit(); + + for (IModuleSetup *module : m_modules) + module->onDestroy(); + + // Remove all modules + removeModules(); + + return exitCode; +} + +void App::addModule(IModuleSetup *module) +{ + assert(module); + assert(std::find_if(m_modules.begin(), m_modules.end(), [module](IModuleSetup *m) { return m->moduleName() == module->moduleName(); }) == m_modules.end()); + m_modules.push_back(module); +} + +void App::removeModules() +{ + for (IModuleSetup *module : m_modules) + delete module; + + m_modules.clear(); + ioc()->reset(); +} diff --git a/src/app/app.h b/src/app/app.h new file mode 100644 index 0000000..ffff521 --- /dev/null +++ b/src/app/app.h @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include + +namespace scratchcpp +{ + +namespace modularity +{ + +class IModuleSetup; + +} + +class App +{ + public: + App(); + + int run(int argc, char **argv); + void addModule(modularity::IModuleSetup *module); + + private: + void removeModules(); + + std::vector m_modules; +}; + +} // namespace scratchcpp diff --git a/src/app/main.cpp b/src/app/main.cpp index 013eb40..0f2df14 100644 --- a/src/app/main.cpp +++ b/src/app/main.cpp @@ -1,32 +1,12 @@ // SPDX-License-Identifier: GPL-3.0-or-later -#include -#include -#include -#include +#include "app.h" + +using namespace scratchcpp; int main(int argc, char *argv[]) { - qputenv("QSG_RENDER_LOOP", "basic"); - - QGuiApplication app(argc, argv); - QQuickStyle::setStyle("Material"); - QIcon::setThemeName("scratchcpp"); - - QQmlApplicationEngine engine; - engine.addImportPath(":/"); - - const QUrl url(u"qrc:/ScratchCPP/main.qml"_qs); - QObject::connect( - &engine, - &QQmlApplicationEngine::objectCreated, - &app, - [url](QObject *obj, const QUrl &objUrl) { - if (!obj && url == objUrl) - QCoreApplication::exit(-1); - }, - Qt::QueuedConnection); - engine.load(url); + App app; - return app.exec(); + return app.run(argc, argv); } From de1686d07db653eb5bcd5a50d300404136859c91 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Fri, 23 Feb 2024 22:34:28 +0100 Subject: [PATCH 13/20] Add googletest from main CMakeLists.txt file --- CMakeLists.txt | 2 ++ build/module_test.cmake | 2 -- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index af8487a..5cb642d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,6 +10,8 @@ option(SCRATCHCPP_PLAYER_BUILD_UNIT_TESTS "Build unit tests" ON) find_package(Qt6 6.6 COMPONENTS Quick QuickControls2 REQUIRED) if (SCRATCHCPP_PLAYER_BUILD_UNIT_TESTS) + set(GTEST_DIR thirdparty/googletest) + add_subdirectory(${PROJECT_SOURCE_DIR}/${GTEST_DIR} ${CMAKE_CURRENT_BINARY_DIR}/${GTEST_DIR}) enable_testing() find_package(Qt6 6.6 COMPONENTS Test REQUIRED) endif() diff --git a/build/module_test.cmake b/build/module_test.cmake index 0173d21..93633f3 100644 --- a/build/module_test.cmake +++ b/build/module_test.cmake @@ -1,8 +1,6 @@ if (SCRATCHCPP_PLAYER_BUILD_UNIT_TESTS) set(TARGET ${MODULE}_test) set(TEST_MAIN_SRC ${PROJECT_SOURCE_DIR}/test/main.cpp) - set(GTEST_DIR thirdparty/googletest) - add_subdirectory(${PROJECT_SOURCE_DIR}/${GTEST_DIR} ${CMAKE_CURRENT_BINARY_DIR}/${GTEST_DIR}) include(GoogleTest) From 613b8aacb8c308fd30caddc2517fec8c2c8eb8f9 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Fri, 23 Feb 2024 22:35:52 +0100 Subject: [PATCH 14/20] Link tests against Qt QML --- build/module_test.cmake | 1 + 1 file changed, 1 insertion(+) diff --git a/build/module_test.cmake b/build/module_test.cmake index 93633f3..56e7e29 100644 --- a/build/module_test.cmake +++ b/build/module_test.cmake @@ -16,6 +16,7 @@ if (SCRATCHCPP_PLAYER_BUILD_UNIT_TESTS) GTest::gtest_main GTest::gmock_main Qt6::Gui + Qt6::Qml Qt6::Test ) From f6d50279ee47ef74870c3495fa7d0f58e94588d9 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Fri, 23 Feb 2024 22:36:37 +0100 Subject: [PATCH 15/20] Remove NO_PLUGIN from modules --- build/module.cmake | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/build/module.cmake b/build/module.cmake index 7319015..f479ea5 100644 --- a/build/module.cmake +++ b/build/module.cmake @@ -5,7 +5,6 @@ set_target_properties(${MODULE} PROPERTIES AUTOMOC ON) qt_add_qml_module(${MODULE} URI ScratchCPP.${MODULE_URI} VERSION 1.0 - NO_PLUGIN OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/ScratchCPP/${MODULE_URI} QML_FILES ${MODULE_QML_FILES} @@ -24,5 +23,5 @@ list(APPEND QML_IMPORT_PATH ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_D list(REMOVE_DUPLICATES QML_IMPORT_PATH) set(QML_IMPORT_PATH ${QML_IMPORT_PATH} CACHE STRING "" FORCE) -target_link_libraries(${APP_TARGET} PRIVATE ${MODULE}) +target_link_libraries(${APP_TARGET} PRIVATE ${MODULE} ${MODULE}plugin) set(MODULE_SRC_DIR ${CMAKE_CURRENT_SOURCE_DIR}) From 2e770422949fdd2bc398ff6a44656f9085cfa9da Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Fri, 23 Feb 2024 23:23:29 +0100 Subject: [PATCH 16/20] Add models for menu bar components --- src/uicomponents/CMakeLists.txt | 10 ++ src/uicomponents/menubarmodel.cpp | 38 ++++++++ src/uicomponents/menubarmodel.h | 37 +++++++ src/uicomponents/menuitemmodel.cpp | 94 ++++++++++++++++++ src/uicomponents/menuitemmodel.h | 62 ++++++++++++ src/uicomponents/menumodel.cpp | 52 ++++++++++ src/uicomponents/menumodel.h | 41 ++++++++ src/uicomponents/test/CMakeLists.txt | 7 ++ src/uicomponents/test/menubarmodel.cpp | 57 +++++++++++ src/uicomponents/test/menuitemmodel.cpp | 122 ++++++++++++++++++++++++ src/uicomponents/test/menumodel.cpp | 68 +++++++++++++ 11 files changed, 588 insertions(+) create mode 100644 src/uicomponents/menubarmodel.cpp create mode 100644 src/uicomponents/menubarmodel.h create mode 100644 src/uicomponents/menuitemmodel.cpp create mode 100644 src/uicomponents/menuitemmodel.h create mode 100644 src/uicomponents/menumodel.cpp create mode 100644 src/uicomponents/menumodel.h create mode 100644 src/uicomponents/test/CMakeLists.txt create mode 100644 src/uicomponents/test/menubarmodel.cpp create mode 100644 src/uicomponents/test/menuitemmodel.cpp create mode 100644 src/uicomponents/test/menumodel.cpp diff --git a/src/uicomponents/CMakeLists.txt b/src/uicomponents/CMakeLists.txt index 3f2ab62..7fc6f3c 100644 --- a/src/uicomponents/CMakeLists.txt +++ b/src/uicomponents/CMakeLists.txt @@ -5,5 +5,15 @@ set(MODULE_QML_FILES CustomToolButton.qml HoverToolTip.qml ) +set(MODULE_SRC + menubarmodel.cpp + menubarmodel.h + menumodel.cpp + menumodel.h + menuitemmodel.cpp + menuitemmodel.h +) include(${PROJECT_SOURCE_DIR}/build/module.cmake) + +add_subdirectory(test) diff --git a/src/uicomponents/menubarmodel.cpp b/src/uicomponents/menubarmodel.cpp new file mode 100644 index 0000000..02c7318 --- /dev/null +++ b/src/uicomponents/menubarmodel.cpp @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "menubarmodel.h" + +using namespace scratchcpp::uicomponents; + +MenuBarModel::MenuBarModel(QObject *parent) : + QObject(parent) +{ +} + +QQmlListProperty MenuBarModel::menus() +{ + return QQmlListProperty(this, &m_menus); +} + +const QList &MenuBarModel::getMenus() const +{ + return m_menus; +} + +void MenuBarModel::addMenu(MenuModel *menu) +{ + m_menus.push_back(menu); + emit menusChanged(); +} + +void MenuBarModel::removeMenu(MenuModel *menu) +{ + m_menus.erase(std::remove(m_menus.begin(), m_menus.end(), menu), m_menus.end()); + emit menusChanged(); +} + +void MenuBarModel::clear() +{ + m_menus.clear(); + emit menusChanged(); +} diff --git a/src/uicomponents/menubarmodel.h b/src/uicomponents/menubarmodel.h new file mode 100644 index 0000000..c8e7ec2 --- /dev/null +++ b/src/uicomponents/menubarmodel.h @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include + +Q_MOC_INCLUDE("menumodel.h") + +namespace scratchcpp::uicomponents +{ + +class MenuModel; + +class MenuBarModel : public QObject +{ + Q_OBJECT + QML_ELEMENT + Q_PROPERTY(QQmlListProperty menus READ menus NOTIFY menusChanged) + + public: + explicit MenuBarModel(QObject *parent = nullptr); + + QQmlListProperty menus(); + const QList &getMenus() const; + + void addMenu(MenuModel *menu); + void removeMenu(MenuModel *menu); + void clear(); + + signals: + void menusChanged(); + + private: + QList m_menus; +}; + +} // namespace scratchcpp::uicomponents diff --git a/src/uicomponents/menuitemmodel.cpp b/src/uicomponents/menuitemmodel.cpp new file mode 100644 index 0000000..1ffed63 --- /dev/null +++ b/src/uicomponents/menuitemmodel.cpp @@ -0,0 +1,94 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "menuitemmodel.h" + +using namespace scratchcpp::uicomponents; + +MenuItemModel::MenuItemModel(QObject *parent) : + QObject(parent) +{ +} + +const QString &MenuItemModel::text() const +{ + return m_text; +} + +void MenuItemModel::setText(const QString &newText) +{ + if (m_text == newText) + return; + + m_text = newText; + emit textChanged(); +} + +MenuModel *MenuItemModel::submenu() const +{ + return m_submenu; +} + +void MenuItemModel::setSubmenu(MenuModel *newSubmenu) +{ + if (m_submenu == newSubmenu) + return; + + m_submenu = newSubmenu; + emit submenuChanged(); +} + +bool MenuItemModel::isSeparator() const +{ + return m_isSeparator; +} + +void MenuItemModel::setIsSeparator(bool newIsSeparator) +{ + if (m_isSeparator == newIsSeparator) + return; + + m_isSeparator = newIsSeparator; + emit isSeparatorChanged(); +} + +bool MenuItemModel::checkable() const +{ + return m_checkable; +} + +void MenuItemModel::setCheckable(bool newCheckable) +{ + if (m_checkable == newCheckable) + return; + + m_checkable = newCheckable; + emit checkableChanged(); +} + +bool MenuItemModel::checked() const +{ + return m_checked; +} + +void MenuItemModel::setChecked(bool newChecked) +{ + if (m_checked == newChecked) + return; + + m_checked = newChecked; + emit checkedChanged(); +} + +bool MenuItemModel::enabled() const +{ + return m_enabled; +} + +void MenuItemModel::setEnabled(bool newEnabled) +{ + if (m_enabled == newEnabled) + return; + + m_enabled = newEnabled; + emit enabledChanged(); +} diff --git a/src/uicomponents/menuitemmodel.h b/src/uicomponents/menuitemmodel.h new file mode 100644 index 0000000..f9d847c --- /dev/null +++ b/src/uicomponents/menuitemmodel.h @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include + +namespace scratchcpp::uicomponents +{ + +class MenuModel; + +class MenuItemModel : public QObject +{ + Q_OBJECT + QML_ELEMENT + Q_PROPERTY(QString text READ text WRITE setText NOTIFY textChanged) + Q_PROPERTY(MenuModel *submenu READ submenu WRITE setSubmenu NOTIFY submenuChanged) + Q_PROPERTY(bool isSeparator READ isSeparator WRITE setIsSeparator NOTIFY isSeparatorChanged) + Q_PROPERTY(bool checkable READ checkable WRITE setCheckable NOTIFY checkableChanged) + Q_PROPERTY(bool checked READ checked WRITE setChecked NOTIFY checkedChanged) + Q_PROPERTY(bool enabled READ enabled WRITE setEnabled NOTIFY enabledChanged) + + public: + explicit MenuItemModel(QObject *parent = nullptr); + + const QString &text() const; + void setText(const QString &newText); + + MenuModel *submenu() const; + void setSubmenu(MenuModel *newSubmenu); + + bool isSeparator() const; + void setIsSeparator(bool newIsSeparator); + + bool checkable() const; + void setCheckable(bool newCheckable); + + bool checked() const; + void setChecked(bool newChecked); + + bool enabled() const; + void setEnabled(bool newEnabled); + + signals: + void textChanged(); + void submenuChanged(); + void isSeparatorChanged(); + void checkableChanged(); + void checkedChanged(); + void enabledChanged(); + void clicked(); + + private: + QString m_text; + MenuModel *m_submenu = nullptr; + bool m_isSeparator = false; + bool m_checkable = false; + bool m_checked = false; + bool m_enabled = true; +}; + +} // namespace scratchcpp::uicomponents diff --git a/src/uicomponents/menumodel.cpp b/src/uicomponents/menumodel.cpp new file mode 100644 index 0000000..a985e0a --- /dev/null +++ b/src/uicomponents/menumodel.cpp @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "menumodel.h" + +using namespace scratchcpp::uicomponents; + +MenuModel::MenuModel(QObject *parent) : + QObject(parent) +{ +} + +const QString &MenuModel::title() const +{ + return m_title; +} + +void MenuModel::setTitle(const QString &newTitle) +{ + if (m_title == newTitle) + return; + + m_title = newTitle; + emit titleChanged(); +} + +QQmlListProperty MenuModel::items() +{ + return QQmlListProperty(this, &m_items); +} + +const QList &MenuModel::getItems() const +{ + return m_items; +} + +void MenuModel::addItem(MenuItemModel *item) +{ + m_items.push_back(item); + emit itemsChanged(); +} + +void MenuModel::removeItem(MenuItemModel *item) +{ + m_items.erase(std::remove(m_items.begin(), m_items.end(), item), m_items.end()); + emit itemsChanged(); +} + +void MenuModel::clear() +{ + m_items.clear(); + emit itemsChanged(); +} diff --git a/src/uicomponents/menumodel.h b/src/uicomponents/menumodel.h new file mode 100644 index 0000000..a922418 --- /dev/null +++ b/src/uicomponents/menumodel.h @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include + +namespace scratchcpp::uicomponents +{ + +class MenuItemModel; + +class MenuModel : public QObject +{ + Q_OBJECT + QML_ELEMENT + Q_PROPERTY(QString title READ title WRITE setTitle NOTIFY titleChanged) + Q_PROPERTY(QQmlListProperty items READ items NOTIFY itemsChanged) + + public: + explicit MenuModel(QObject *parent = nullptr); + + const QString &title() const; + void setTitle(const QString &newTitle); + + QQmlListProperty items(); + const QList &getItems() const; + + void addItem(MenuItemModel *item); + void removeItem(MenuItemModel *item); + void clear(); + + signals: + void titleChanged(); + void itemsChanged(); + + private: + QString m_title; + QList m_items; +}; + +} // namespace scratchcpp::uicomponents diff --git a/src/uicomponents/test/CMakeLists.txt b/src/uicomponents/test/CMakeLists.txt new file mode 100644 index 0000000..1ae4450 --- /dev/null +++ b/src/uicomponents/test/CMakeLists.txt @@ -0,0 +1,7 @@ +set(MODULE_TEST_SRC + menuitemmodel.cpp + menumodel.cpp + menubarmodel.cpp +) + +include(${PROJECT_SOURCE_DIR}/build/module_test.cmake) diff --git a/src/uicomponents/test/menubarmodel.cpp b/src/uicomponents/test/menubarmodel.cpp new file mode 100644 index 0000000..d9abfb3 --- /dev/null +++ b/src/uicomponents/test/menubarmodel.cpp @@ -0,0 +1,57 @@ +#include +#include + +#include "menubarmodel.h" +#include "menumodel.h" + +using namespace scratchcpp::uicomponents; + +TEST(MenuBarModelTest, Constructor) +{ + MenuBarModel menuBar1; + MenuBarModel menuBar2(&menuBar1); + ASSERT_EQ(menuBar1.parent(), nullptr); + ASSERT_EQ(menuBar2.parent(), &menuBar1); +} + +TEST(MenuBarModelTest, Menus) +{ + MenuBarModel menuBar; + QSignalSpy spy(&menuBar, &MenuBarModel::menusChanged); + ASSERT_TRUE(menuBar.getMenus().isEmpty()); + + MenuModel menu1; + menuBar.addMenu(&menu1); + ASSERT_EQ(spy.count(), 1); + + MenuModel menu2; + menuBar.addMenu(&menu2); + ASSERT_EQ(spy.count(), 2); + + MenuModel menu3; + menuBar.addMenu(&menu3); + ASSERT_EQ(spy.count(), 3); + + auto menus = menuBar.menus(); + ASSERT_EQ(menuBar.getMenus(), QList({ &menu1, &menu2, &menu3 })); + ASSERT_EQ(menus.count(&menus), 3); + ASSERT_EQ(menus.at(&menus, 0), &menu1); + ASSERT_EQ(menus.at(&menus, 1), &menu2); + ASSERT_EQ(menus.at(&menus, 2), &menu3); + + menuBar.removeMenu(&menu2); + ASSERT_EQ(spy.count(), 4); + + menus = menuBar.menus(); + ASSERT_EQ(menuBar.getMenus(), QList({ &menu1, &menu3 })); + ASSERT_EQ(menus.count(&menus), 2); + ASSERT_EQ(menus.at(&menus, 0), &menu1); + ASSERT_EQ(menus.at(&menus, 1), &menu3); + + menuBar.clear(); + ASSERT_EQ(spy.count(), 5); + + menus = menuBar.menus(); + ASSERT_TRUE(menuBar.getMenus().isEmpty()); + ASSERT_EQ(menus.count(&menus), 0); +} diff --git a/src/uicomponents/test/menuitemmodel.cpp b/src/uicomponents/test/menuitemmodel.cpp new file mode 100644 index 0000000..8e501ce --- /dev/null +++ b/src/uicomponents/test/menuitemmodel.cpp @@ -0,0 +1,122 @@ +#include +#include + +#include "menuitemmodel.h" +#include "menumodel.h" + +using namespace scratchcpp::uicomponents; + +TEST(MenuItemModelTest, Constructor) +{ + MenuItemModel item1; + MenuItemModel item2(&item1); + ASSERT_EQ(item1.parent(), nullptr); + ASSERT_EQ(item2.parent(), &item1); +} + +TEST(MenuItemModelTest, Text) +{ + MenuItemModel item; + QSignalSpy spy(&item, &MenuItemModel::textChanged); + ASSERT_TRUE(item.text().isEmpty()); + + item.setText("Test"); + ASSERT_EQ(item.text(), "Test"); + ASSERT_EQ(spy.count(), 1); +} + +TEST(MenuItemModelTest, Submenu) +{ + MenuItemModel item; + QSignalSpy spy(&item, &MenuItemModel::submenuChanged); + ASSERT_EQ(item.submenu(), nullptr); + + MenuModel menu; + item.setSubmenu(&menu); + ASSERT_EQ(item.submenu(), &menu); + ASSERT_EQ(spy.count(), 1); +} + +TEST(MenuItemModelTest, IsSeparator) +{ + MenuItemModel item; + QSignalSpy spy(&item, &MenuItemModel::isSeparatorChanged); + ASSERT_FALSE(item.isSeparator()); + + item.setIsSeparator(true); + ASSERT_TRUE(item.isSeparator()); + ASSERT_EQ(spy.count(), 1); + + item.setIsSeparator(true); + ASSERT_TRUE(item.isSeparator()); + ASSERT_EQ(spy.count(), 1); + + item.setIsSeparator(false); + ASSERT_FALSE(item.isSeparator()); + ASSERT_EQ(spy.count(), 2); +} + +TEST(MenuItemModelTest, Checkable) +{ + MenuItemModel item; + QSignalSpy spy(&item, &MenuItemModel::checkableChanged); + ASSERT_FALSE(item.checkable()); + + item.setCheckable(true); + ASSERT_TRUE(item.checkable()); + ASSERT_EQ(spy.count(), 1); + + item.setCheckable(true); + ASSERT_TRUE(item.checkable()); + ASSERT_EQ(spy.count(), 1); + + item.setCheckable(false); + ASSERT_FALSE(item.checkable()); + ASSERT_EQ(spy.count(), 2); +} + +TEST(MenuItemModelTest, Checked) +{ + MenuItemModel item; + QSignalSpy spy(&item, &MenuItemModel::checkedChanged); + ASSERT_FALSE(item.checked()); + + item.setChecked(true); + ASSERT_TRUE(item.checked()); + ASSERT_EQ(spy.count(), 1); + + item.setChecked(true); + ASSERT_TRUE(item.checked()); + ASSERT_EQ(spy.count(), 1); + + item.setChecked(false); + ASSERT_FALSE(item.checked()); + ASSERT_EQ(spy.count(), 2); +} + +TEST(MMenuItemModelTest, Enabled) +{ + MenuItemModel item; + ASSERT_TRUE(item.enabled()); + QSignalSpy spy(&item, &MenuItemModel::enabledChanged); + + item.setEnabled(false); + ASSERT_FALSE(item.enabled()); + ASSERT_EQ(spy.count(), 1); + + item.setEnabled(false); + ASSERT_FALSE(item.enabled()); + ASSERT_EQ(spy.count(), 1); + + item.setEnabled(true); + ASSERT_TRUE(item.enabled()); + ASSERT_EQ(spy.count(), 2); +} + +TEST(MenuItemModelTest, Clicked) +{ + MenuItemModel item; + QSignalSpy spy(&item, &MenuItemModel::clicked); + emit item.clicked(); + ASSERT_EQ(spy.count(), 1); +} diff --git a/src/uicomponents/test/menumodel.cpp b/src/uicomponents/test/menumodel.cpp new file mode 100644 index 0000000..6a178a9 --- /dev/null +++ b/src/uicomponents/test/menumodel.cpp @@ -0,0 +1,68 @@ +#include +#include + +#include "menumodel.h" +#include "menuitemmodel.h" + +using namespace scratchcpp::uicomponents; + +TEST(MenuModelTest, Constructor) +{ + MenuModel menu1; + MenuModel menu2(&menu1); + ASSERT_EQ(menu1.parent(), nullptr); + ASSERT_EQ(menu2.parent(), &menu1); +} + +TEST(MenuModelTest, Title) +{ + MenuModel menu; + QSignalSpy spy(&menu, &MenuModel::titleChanged); + ASSERT_TRUE(menu.title().isEmpty()); + + menu.setTitle("Test"); + ASSERT_EQ(menu.title(), "Test"); + ASSERT_EQ(spy.count(), 1); +} + +TEST(MenuModelTest, Items) +{ + MenuModel menu; + QSignalSpy spy(&menu, &MenuModel::itemsChanged); + ASSERT_TRUE(menu.getItems().isEmpty()); + + MenuItemModel item1; + menu.addItem(&item1); + ASSERT_EQ(spy.count(), 1); + + MenuItemModel item2; + menu.addItem(&item2); + ASSERT_EQ(spy.count(), 2); + + MenuItemModel item3; + menu.addItem(&item3); + ASSERT_EQ(spy.count(), 3); + + auto items = menu.items(); + ASSERT_EQ(menu.getItems(), QList({ &item1, &item2, &item3 })); + ASSERT_EQ(items.count(&items), 3); + ASSERT_EQ(items.at(&items, 0), &item1); + ASSERT_EQ(items.at(&items, 1), &item2); + ASSERT_EQ(items.at(&items, 2), &item3); + + menu.removeItem(&item2); + ASSERT_EQ(spy.count(), 4); + + items = menu.items(); + ASSERT_EQ(menu.getItems(), QList({ &item1, &item3 })); + ASSERT_EQ(items.count(&items), 2); + ASSERT_EQ(items.at(&items, 0), &item1); + ASSERT_EQ(items.at(&items, 1), &item3); + + menu.clear(); + ASSERT_EQ(spy.count(), 5); + + items = menu.items(); + ASSERT_TRUE(menu.getItems().isEmpty()); + ASSERT_EQ(items.count(&items), 0); +} From f9020b2b6f037af583b639b648c5ec01a622fab2 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Fri, 23 Feb 2024 23:24:29 +0100 Subject: [PATCH 17/20] Add components for menu bar --- src/uicomponents/CMakeLists.txt | 4 + src/uicomponents/CustomMenu.qml | 37 ++++++ src/uicomponents/CustomMenuBar.qml | 162 +++++++++++++++++++++++ src/uicomponents/CustomMenuItem.qml | 9 ++ src/uicomponents/CustomMenuSeparator.qml | 6 + 5 files changed, 218 insertions(+) create mode 100644 src/uicomponents/CustomMenu.qml create mode 100644 src/uicomponents/CustomMenuBar.qml create mode 100644 src/uicomponents/CustomMenuItem.qml create mode 100644 src/uicomponents/CustomMenuSeparator.qml diff --git a/src/uicomponents/CMakeLists.txt b/src/uicomponents/CMakeLists.txt index 7fc6f3c..ec5fece 100644 --- a/src/uicomponents/CMakeLists.txt +++ b/src/uicomponents/CMakeLists.txt @@ -4,6 +4,10 @@ set(MODULE_QML_FILES CustomButton.qml CustomToolButton.qml HoverToolTip.qml + CustomMenuBar.qml + CustomMenu.qml + CustomMenuItem.qml + CustomMenuSeparator.qml ) set(MODULE_SRC menubarmodel.cpp diff --git a/src/uicomponents/CustomMenu.qml b/src/uicomponents/CustomMenu.qml new file mode 100644 index 0000000..a7340c9 --- /dev/null +++ b/src/uicomponents/CustomMenu.qml @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +import QtQuick +import QtQuick.Controls + +Menu { + property bool isSubMenu: false + + function updateWidth() { + let maxWidth = 0; + + for (let i = 0; i < count; i++) + maxWidth = Math.max(maxWidth, itemAt(i).implicitWidth); + + background.implicitWidth = maxWidth; + } + + function toggle() { + if (visible) + close(); + else + open(); + } + + x: isSubMenu ? parent.width : 0 + y: isSubMenu ? 0 : parent.height + font.pointSize: 10 + background: Rectangle { + // Load colors from theme + color: /*ThemeEngine.bgColor*/ Material.background + border.color: /*ThemeEngine.borderColor*/ Qt.rgba(1, 1, 1, 0.25) + radius: 10 + implicitHeight: 40 + } + delegate: CustomMenuItem {} + onAboutToShow: updateWidth() +} diff --git a/src/uicomponents/CustomMenuBar.qml b/src/uicomponents/CustomMenuBar.qml new file mode 100644 index 0000000..4085dfa --- /dev/null +++ b/src/uicomponents/CustomMenuBar.qml @@ -0,0 +1,162 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +import QtQuick +import QtQuick.Controls +import ScratchCPP.UiComponents + +MenuBar { + id: root + property MenuBarModel model: MenuBarModel {} + + QtObject { + id: priv + readonly property alias menus: root.model.menus + readonly property var menuObjects: [] + } + + Connections { + target: root.model + + function onMenusChanged() { reload() } + } + + function getComponentString(typeName) { + var imports = "import QtQuick; import QtQuick.Controls; import Qt.labs.platform as Platform;" + return imports + " " + typeName + " {}"; + } + + function createMenuBar(parentItem, menuType, menuItemType, menuSeparatorType) { + for(var i = 0; i < priv.menus.length; i++) { + var menuParent = parentItem; + var menu = Qt.createQmlObject(getComponentString(menuType), menuParent); + menu.title = priv.menus[i].title; + priv.menuObjects[priv.menuObjects.length] = menu; + parentItem.addMenu(menu); + createMenu(menu, priv.menus[i].items, menuType, menuItemType, menuSeparatorType); + } + } + + function createMenu(parentItem, itemList, menuType, menuItemType, menuSeparatorType) { + for (let i = 0; i < itemList.length; i++) { + let itemData = itemList[i]; + let itemComponent; + let item; + let overrideAddItem = false; + + if (itemData.isSeparator) { + item = Qt.createQmlObject(getComponentString(menuSeparatorType), parentItem); + } else if (itemData.submenu === null) { + item = Qt.createQmlObject(getComponentString(menuItemType), parentItem); + item.text = itemData.text; + item.checkable = itemData.checkable; + item.checked = itemData.checked; + item.enabled = itemData.enabled; + item.onCheckedChanged.connect(function() { itemData.checked = item.checked; }); + itemData.onCheckedChanged.connect(function() { item.checked = itemData.checked; }); + itemData.onEnabledChanged.connect(function() { item.enabled = itemData.enabled; }); + + if (typeof itemData.onClicked != "undefined") + item.onTriggered.connect(function() { itemData.onClicked() }); + } else { + item = Qt.createQmlObject(getComponentString(menuType), parentItem) + item.title = itemData.text; + parentItem.addMenu(item); + createMenu(item, itemData.submenu.items, menuType, menuItemType, menuSeparatorType); + overrideAddItem = true; + } + + if (!overrideAddItem) + parentItem.addItem(item); + } + } + + background: Rectangle { + color: Material.backgroundColor // Load the color from the theme + } + + delegate: MenuBarItem { + id: menuBarItem + + function replaceText(txt) + { + var index = txt.indexOf("&"); + + if (index >= 0) + txt = txt.replace(txt.substr(index, 2), ("" + txt.substr(index + 1, 1) +"")); + + return txt; + } + + implicitHeight: contentItem.implicitHeight + topPadding + bottomPadding + leftPadding: 10 + rightPadding: 10 + topPadding: 5 + bottomPadding: 5 + font.pointSize: 10 + Material.background: Qt.rgba(0, 0, 0, 0) + Material.foreground: Material.theme == Material.Dark ? "white" : "black" + contentItem: Label { + text: replaceText(menuBarItem.text) + font: menuBarItem.font + } + onDoubleClicked: clicked() + } + + function reload() { + /*if(nativeMenuBarEnabled) + { + root.visible = false; + return; + }*/ + + var oldObjects = []; + + for (var i = 0; i < priv.menuObjects.length; i++) + oldObjects.push(priv.menuObjects[i]); + + priv.menuObjects.length = 0; + createMenuBar(root, "CustomMenu", "CustomMenuItem", "CustomMenuSeparator"); + + for (i = 0; i < oldObjects.length; i++) + removeMenu(oldObjects[i]); + } + + /*Connections { + target: // TODO: Add a class for the menu bar reload signal + function onMenuBarReloadTriggered() { + for(var i = 0; i < root.count; i++) + root.menuAt(i).close(); + root.reload(); + } + }*/ + + Component.onCompleted: reload(); + + /*onEnabledChanged: { + if(platformMenuBarLoader.active) + platformMenuBarLoader.item.reload(); + } + + Loader { + id: platformMenuBarLoader + active: // whether the native menu bar is active + + sourceComponent: Platform.MenuBar { + id: platformMenuBar + function reload() { + clear(); + if(root.enabled) + createMenuBar(platformMenuBar, "Platform.Menu", "Platform.MenuItem", "Platform.MenuSeparator"); + } + + Connections { + target: QmlUtils + function onMenuBarReloadTriggered() { + platformMenuBar.reload(); + } + } + + Component.onCompleted: reload(); + } + }*/ +} diff --git a/src/uicomponents/CustomMenuItem.qml b/src/uicomponents/CustomMenuItem.qml new file mode 100644 index 0000000..c4a2a47 --- /dev/null +++ b/src/uicomponents/CustomMenuItem.qml @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +import QtQuick +import QtQuick.Controls + +MenuItem { + font.pointSize: 10; + implicitHeight: 36; +} diff --git a/src/uicomponents/CustomMenuSeparator.qml b/src/uicomponents/CustomMenuSeparator.qml new file mode 100644 index 0000000..536abf8 --- /dev/null +++ b/src/uicomponents/CustomMenuSeparator.qml @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +import QtQuick +import QtQuick.Controls + +MenuSeparator {} From 5f2f0ad3c817975cd0ccfdafbee8e6997f3d450e Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Fri, 23 Feb 2024 23:24:41 +0100 Subject: [PATCH 18/20] Add AppMenuBar singleton --- src/app/CMakeLists.txt | 2 ++ src/app/appmenubar.cpp | 20 ++++++++++++++++++++ src/app/appmenubar.h | 38 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 60 insertions(+) create mode 100644 src/app/appmenubar.cpp create mode 100644 src/app/appmenubar.h diff --git a/src/app/CMakeLists.txt b/src/app/CMakeLists.txt index e487472..62cc82b 100644 --- a/src/app/CMakeLists.txt +++ b/src/app/CMakeLists.txt @@ -4,6 +4,8 @@ qt_add_executable(${APP_TARGET} main.cpp app.cpp app.h + appmenubar.cpp + appmenubar.h ) qt_add_qml_module(${APP_TARGET} diff --git a/src/app/appmenubar.cpp b/src/app/appmenubar.cpp new file mode 100644 index 0000000..841fd58 --- /dev/null +++ b/src/app/appmenubar.cpp @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "appmenubar.h" +#include "uicomponents/menubarmodel.h" +#include "uicomponents/menumodel.h" +#include "uicomponents/menuitemmodel.h" + +using namespace scratchcpp; +using namespace scratchcpp::uicomponents; + +AppMenuBar::AppMenuBar(QObject *parent) : + QObject(parent), + m_model(new MenuBarModel(this)) +{ +} + +MenuBarModel *AppMenuBar::model() const +{ + return m_model; +} diff --git a/src/app/appmenubar.h b/src/app/appmenubar.h new file mode 100644 index 0000000..3c883b9 --- /dev/null +++ b/src/app/appmenubar.h @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include + +Q_MOC_INCLUDE("uicomponents/menubarmodel.h") + +namespace scratchcpp +{ + +namespace uicomponents +{ + +class MenuBarModel; + +} + +class AppMenuBar : public QObject +{ + Q_OBJECT + QML_ELEMENT + QML_SINGLETON + Q_PROPERTY(uicomponents::MenuBarModel *model READ model NOTIFY modelChanged) + + public: + explicit AppMenuBar(QObject *parent = nullptr); + + uicomponents::MenuBarModel *model() const; + + signals: + void modelChanged(); + + private: + uicomponents::MenuBarModel *m_model = nullptr; +}; + +} // namespace scratchcpp From 9abb1f848a67709409b4351a7c6e84ca1d2ec173 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Fri, 23 Feb 2024 23:24:56 +0100 Subject: [PATCH 19/20] Add menu bar to the main window --- src/app/main.qml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/app/main.qml b/src/app/main.qml index 7fb0920..3b0714d 100644 --- a/src/app/main.qml +++ b/src/app/main.qml @@ -3,10 +3,12 @@ import QtQuick import QtQuick.Controls.Material import QtQuick.Layouts +import ScratchCPP import ScratchCPP.UiComponents import ScratchCPP.Render -Window { +ApplicationWindow { + id: root minimumWidth: layout.implicitWidth + layout.anchors.margins * 2 minimumHeight: layout.implicitHeight + layout.anchors.margins * 2 visible: true @@ -15,6 +17,11 @@ Window { Material.accent: "orange" Material.theme: Material.Dark + menuBar: CustomMenuBar { + width: root.width + model: AppMenuBar.model + } + ColumnLayout { id: layout anchors.fill: parent From 45fbc2c1dc2df3698161d09c5e46665e57baa941 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Fri, 23 Feb 2024 23:28:43 +0100 Subject: [PATCH 20/20] Add QML import paths for app classes --- src/app/CMakeLists.txt | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/app/CMakeLists.txt b/src/app/CMakeLists.txt index 62cc82b..b2e2977 100644 --- a/src/app/CMakeLists.txt +++ b/src/app/CMakeLists.txt @@ -14,6 +14,15 @@ qt_add_qml_module(${APP_TARGET} QML_FILES main.qml ) +set(QML_IMPORT_PATH "${QML_IMPORT_PATH};${CMAKE_CURRENT_LIST_DIR}" + CACHE STRING "Qt Creator extra QML import paths" + FORCE +) + +list(APPEND QML_IMPORT_PATH ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR}) +list(REMOVE_DUPLICATES QML_IMPORT_PATH) +set(QML_IMPORT_PATH ${QML_IMPORT_PATH} CACHE STRING "" FORCE) + set_target_properties(${APP_TARGET} PROPERTIES MACOSX_BUNDLE_GUI_IDENTIFIER my.example.com MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION}