├── .clang-format ├── .envrc ├── .gitattributes ├── .github ├── dependabot.yml └── workflows │ ├── clang-format.yml │ ├── linux-appimage.yml │ ├── macos-dmg.yml │ ├── nix.yml │ ├── remove-old-artifacts.yml │ └── windows-installer.yml ├── .gitignore ├── .jsbeautifyrc ├── CMakeLists.txt ├── CMakePresets.json ├── License ├── Readme.md ├── cmake ├── CPM.cmake ├── Dependencies.cmake ├── FetchQaterial.cmake ├── FetchQtLinuxCMake.cmake ├── FetchQtMacCMake.cmake ├── FetchQtWindowsCMake.cmake ├── PrintConfiguration.cmake └── Version.cmake ├── docs └── dependencies.svg ├── flake.lock ├── flake.nix ├── nix └── get-project-version.nix ├── platforms ├── Deploy.cmake ├── fire.png ├── fire.svg ├── linux │ └── AppDir │ │ └── usr │ │ └── share │ │ ├── applications │ │ └── QaterialHotReloadApp.desktop │ │ └── icons │ │ └── hicolor │ │ ├── 256x256 │ │ └── apps │ │ │ └── QaterialHotReloadApp.png │ │ └── scalable │ │ └── apps │ │ └── QaterialHotReloadApp.svg ├── macos │ ├── Assets.xcassets │ │ └── AppIcon.appiconset │ │ │ ├── 1024.png │ │ │ ├── 128.png │ │ │ ├── 16.png │ │ │ ├── 256.png │ │ │ ├── 32.png │ │ │ ├── 512.png │ │ │ ├── 64.png │ │ │ └── Contents.json │ ├── Entitlements.entitlements │ └── Info.plist.in └── windows │ ├── icon.ico │ └── icon.rc ├── qml └── Qaterial │ └── HotReload │ ├── CMakeLists.txt │ ├── HotReload.qml │ ├── HotReloadWindow.qml │ ├── IconsMenu.qml │ ├── Images.qml │ ├── Images │ ├── code.svg │ ├── icon.svg │ ├── material-icons-light.svg │ ├── material-palette.png │ ├── qaterial-hotreload-black.png │ └── qaterial-hotreload-white.png │ ├── ImportPathMenu.qml │ ├── Imports.qml │ ├── Main.qml │ ├── MaterialPaletteMenu.qml │ ├── SplashScreenApplication.qml │ ├── SplashScreenWindow.qml │ ├── StatusView.qml │ └── TypoMenu.qml └── src └── Qaterial ├── HotReload ├── HotReload.cpp ├── HotReload.hpp └── Pch │ └── Pch.hpp └── HotReloadApp └── Main.cpp /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | BasedOnStyle: Microsoft 3 | AccessModifierOffset: -4 4 | Language: Cpp 5 | AlignAfterOpenBracket: DontAlign 6 | AlignConsecutiveAssignments: false 7 | AlignConsecutiveDeclarations: false 8 | AlignConsecutiveMacros: false 9 | AlignEscapedNewlines: Right 10 | AlignOperands: true 11 | ColumnLimit: 140 12 | AlignTrailingComments: false 13 | AllowAllArgumentsOnNextLine: false 14 | AllowAllConstructorInitializersOnNextLine: true 15 | AllowAllParametersOfDeclarationOnNextLine: true 16 | AllowShortBlocksOnASingleLine: Empty 17 | AllowShortFunctionsOnASingleLine: All 18 | AllowShortLoopsOnASingleLine: true 19 | AllowShortIfStatementsOnASingleLine: Never 20 | AlwaysBreakAfterReturnType: None 21 | AlwaysBreakBeforeMultilineStrings: false 22 | AllowShortCaseLabelsOnASingleLine: true 23 | AlwaysBreakTemplateDeclarations: true 24 | BinPackArguments: false 25 | BinPackParameters: false 26 | 27 | BreakBeforeBraces: Custom 28 | BraceWrapping: 29 | AfterCaseLabel: true 30 | AfterClass: true 31 | AfterControlStatement: true 32 | AfterEnum: true 33 | AfterFunction: true 34 | AfterNamespace: false 35 | AfterStruct: true 36 | AfterUnion: true 37 | AfterExternBlock: false 38 | BeforeCatch: true 39 | BeforeElse: true 40 | BeforeLambdaBody: true 41 | SplitEmptyFunction: true 42 | SplitEmptyRecord: true 43 | SplitEmptyNamespace: true 44 | 45 | BreakBeforeBinaryOperators: None 46 | BreakBeforeTernaryOperators: false 47 | BreakConstructorInitializers: AfterColon 48 | BreakInheritanceList: AfterColon 49 | BreakStringLiterals: true 50 | CompactNamespaces: false 51 | ConstructorInitializerAllOnOneLineOrOnePerLine: true 52 | Cpp11BracedListStyle: true 53 | DeriveLineEnding: true 54 | DerivePointerAlignment: false 55 | IncludeBlocks: Regroup 56 | IndentCaseBlocks: false 57 | IndentCaseLabels: false 58 | IndentWidth: 4 59 | IndentWrappedFunctionNames: true 60 | KeepEmptyLinesAtTheStartOfBlocks: false 61 | MaxEmptyLinesToKeep: 1 62 | NamespaceIndentation: None 63 | PointerAlignment: Left 64 | ReflowComments: false 65 | SortIncludes: false 66 | SortUsingDeclarations: false 67 | SpaceAfterCStyleCast: false 68 | SpaceAfterLogicalNot: false 69 | SpaceAfterTemplateKeyword: false 70 | SpaceBeforeAssignmentOperators: true 71 | SpaceBeforeCpp11BracedList: true 72 | SpaceBeforeCtorInitializerColon: true 73 | SpaceBeforeParens: Never 74 | SpaceBeforeRangeBasedForLoopColon: false 75 | SpaceBeforeSquareBrackets: false 76 | SpaceInEmptyBlock: false 77 | SpaceInEmptyParentheses: false 78 | SpacesInAngles: false 79 | SpacesInCStyleCastParentheses: false 80 | SpacesInConditionalStatement: false 81 | SpacesInContainerLiterals: false 82 | SpacesInParentheses: false 83 | SpacesInSquareBrackets: false 84 | UseTab: Never 85 | FixNamespaceComments: false 86 | 87 | IndentPPDirectives: AfterHash -------------------------------------------------------------------------------- /.envrc: -------------------------------------------------------------------------------- 1 | use flake; 2 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################### 2 | # Git Line Endings # 3 | ############################### 4 | 5 | # Set default behaviour to automatically normalize line endings. 6 | * text=auto 7 | 8 | # Force batch scripts to always use CRLF line endings so that if a repo is accessed 9 | # in Windows via a file share from Linux, the scripts will work. 10 | *.{cmd,[cC][mM][dD]} text eol=crlf 11 | *.{bat,[bB][aA][tT]} text eol=crlf 12 | 13 | # Let's keep our code consistent accross platform. 14 | *.{cpp,hpp,qml,js,cmake,txt} text eol=crlf 15 | 16 | # Force bash scripts to always use LF line endings so that if a repo is accessed 17 | # in Unix via a file share from Windows, the scripts will work. 18 | *.sh text eol=lf 19 | 20 | ############################### 21 | # Git Large File System (LFS) # 22 | ############################### 23 | 24 | # Archives 25 | *.7z filter=lfs diff=lfs merge=lfs -text 26 | *.br filter=lfs diff=lfs merge=lfs -text 27 | *.gz filter=lfs diff=lfs merge=lfs -text 28 | *.tar filter=lfs diff=lfs merge=lfs -text 29 | *.zip filter=lfs diff=lfs merge=lfs -text 30 | 31 | # Documents 32 | *.pdf filter=lfs diff=lfs merge=lfs -text 33 | 34 | # Images 35 | *.gif filter=lfs diff=lfs merge=lfs -text 36 | *.ico filter=lfs diff=lfs merge=lfs -text 37 | *.jpg filter=lfs diff=lfs merge=lfs -text 38 | *.pdf filter=lfs diff=lfs merge=lfs -text 39 | *.png filter=lfs diff=lfs merge=lfs -text 40 | *.psd filter=lfs diff=lfs merge=lfs -text 41 | *.webp filter=lfs diff=lfs merge=lfs -text 42 | *.svg filter=lfs diff=lfs merge=lfs -text 43 | 44 | # Fonts 45 | *.woff2 filter=lfs diff=lfs merge=lfs -text 46 | 47 | # Other 48 | *.exe filter=lfs diff=lfs merge=lfs -text 49 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | -------------------------------------------------------------------------------- /.github/workflows/clang-format.yml: -------------------------------------------------------------------------------- 1 | name: 🎨 Run Linter 2 | 3 | on: [push] 4 | 5 | jobs: 6 | clang-format: 7 | runs-on: ubuntu-latest 8 | if: "!contains(github.event.head_commit.message, '🎨')" 9 | 10 | steps: 11 | - uses: actions/checkout@v4 12 | - uses: DoozyX/clang-format-lint-action@v0.20 13 | name: "Run clang-format" 14 | with: 15 | source: '.' 16 | extensions: 'hpp,cpp' 17 | clangFormatVersion: 11 18 | inplace: True 19 | - uses: EndBug/add-and-commit@v9 20 | name: "Commit clang-format Change" 21 | with: 22 | author_name: Clang Robot 23 | author_email: robot@clang-format.com 24 | message: '🎨 Run clang-format' 25 | env: 26 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 27 | 28 | js-beautify: 29 | needs: clang-format 30 | runs-on: ubuntu-latest 31 | 32 | steps: 33 | - uses: actions/checkout@v4 34 | - name: "Install js-beautify" 35 | run: sudo npm -g install js-beautify 36 | - name: "🎨 Run js-beautify" 37 | run: find . -regex '.*\.\(qml\|js\)' -exec js-beautify -r {} \; 38 | - uses: EndBug/add-and-commit@v9 39 | name: "Commit js-beautify Change" 40 | with: 41 | author_name: Beautify Robot 42 | author_email: robot@js-beautify.com 43 | message: '🎨 Run js-beautify' 44 | env: 45 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 46 | 47 | -------------------------------------------------------------------------------- /.github/workflows/linux-appimage.yml: -------------------------------------------------------------------------------- 1 | name: 👷 AppImage CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | tags: 8 | - v* 9 | pull_request: 10 | types: [opened, synchronize, labeled] 11 | workflow_dispatch: 12 | 13 | jobs: 14 | BuildAppImage: 15 | 16 | runs-on: ubuntu-latest 17 | 18 | steps: 19 | - 20 | uses: actions/checkout@v4 21 | with: 22 | lfs: true 23 | - 24 | name: 🔧 Configure 25 | run: | 26 | mkdir -p build 27 | docker run --rm -v $(pwd):/src/ --device /dev/fuse --cap-add SYS_ADMIN --security-opt apparmor:unconfined reivilo1234/qt-linux-cmake:ubuntu20-qt6.6.2 \ 28 | cmake \ 29 | -DQATERIALHOTRELOAD_IGNORE_ENV=ON \ 30 | -B ./build/ -S . 31 | - 32 | name: 🔨 Build QaterialHotReloadApp 33 | run: | 34 | docker run --rm -v $(pwd):/src/ --device /dev/fuse --cap-add SYS_ADMIN --security-opt apparmor:unconfined reivilo1234/qt-linux-cmake:ubuntu20-qt6.6.2 \ 35 | cmake --build build --target "QaterialHotReloadApp" --config "Release" -j $(nproc) 36 | - 37 | name: 🚀 Deploy QaterialHotReload AppImage 38 | run: | 39 | docker run --rm -v $(pwd):/src/ --device /dev/fuse --cap-add SYS_ADMIN --security-opt apparmor:unconfined reivilo1234/qt-linux-cmake:ubuntu20-qt6.6.2 \ 40 | cmake --build build --target "QaterialHotReloadAppAppImage" --config "Release" -j $(nproc) 41 | - 42 | name: Get Name of Artifact 43 | run: | 44 | ARTIFACT_PATHNAME=$(ls build/*.AppImage | head -n 1) 45 | ARTIFACT_NAME=$(basename $ARTIFACT_PATHNAME) 46 | echo "ARTIFACT_NAME=${ARTIFACT_NAME}" >> $GITHUB_ENV 47 | echo "ARTIFACT_PATHNAME=${ARTIFACT_PATHNAME}" >> $GITHUB_ENV 48 | - 49 | name: 📦 Upload QaterialHotReload AppImage 50 | uses: actions/upload-artifact@v4 51 | with: 52 | name: QaterialHotReload.AppImage 53 | path: ${{ env.ARTIFACT_PATHNAME }} 54 | - 55 | name: 🚀 Upload Release Asset 56 | if: github.event_name == 'release' && startsWith(github.ref, 'refs/tags/v') 57 | uses: actions/upload-release-asset@v1 58 | env: 59 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 60 | with: 61 | upload_url: ${{ github.event.release.upload_url }} 62 | asset_path: ${{ env.ARTIFACT_PATHNAME }} 63 | asset_name: QaterialHotReload.AppImage 64 | asset_content_type: application/vnd.appimage 65 | -------------------------------------------------------------------------------- /.github/workflows/macos-dmg.yml: -------------------------------------------------------------------------------- 1 | name: 👷 MacOs CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | - ci 8 | 9 | release: 10 | types: 11 | - created 12 | 13 | pull_request: 14 | branches: 15 | - master 16 | 17 | jobs: 18 | BuildDmg: 19 | 20 | runs-on: macOS-latest 21 | strategy: 22 | matrix: 23 | min-osx-target: ['10.13'] 24 | qt-version: ['6.6.2'] 25 | build-type: ['Release'] 26 | build-system: ['12'] 27 | 28 | steps: 29 | - 30 | uses: actions/checkout@v4 31 | with: 32 | lfs: true 33 | - 34 | uses: OlivierLDff/import-codesign-certs@v1 35 | with: 36 | p12-file-base64: ${{ secrets.MAC_DMG_CERTIFICATES_FILE_BASE64 }} 37 | p12-password: ${{ secrets.MAC_DMG_CERTIFICATES_PASSWORD }} 38 | - 39 | uses: OlivierLDff/download-provisioning-profiles@master 40 | with: 41 | bundle-id: com.qaterial.hotreload 42 | profile-type: 'MAC_APP_DIRECT' 43 | issuer-id: ${{ secrets.MAC_APPSTORE_ISSUER_ID }} 44 | api-key-id: ${{ secrets.MAC_APPSTORE_KEY_ID }} 45 | api-private-key: ${{ secrets.MAC_APPSTORE_PRIVATE_KEY }} 46 | - 47 | name: ⬆ Install Qt 48 | uses: jurplel/install-qt-action@v4 49 | with: 50 | version: ${{ matrix.qt-version }} 51 | cache: true 52 | modules: 'qtcharts qtdatavis3d qtquick3d qt5compat qtshadertools' 53 | - 54 | name: 🔧 Configure 55 | run: | 56 | mkdir -p build 57 | cmake \ 58 | -G "Xcode" \ 59 | -Tbuildsystem=${{ matrix.build-system }} \ 60 | -DCMAKE_OSX_DEPLOYMENT_TARGET=${{ matrix.min-osx-target }} \ 61 | -DQT_MAC_TEAM_ID=${{ secrets.MAC_TEAM_ID }} \ 62 | -DQT_MAC_CODE_SIGN_IDENTITY="${{ secrets.MAC_DMG_CODE_SIGN_IDENTITY }}" \ 63 | -DQT_MAC_PROVISIONING_PROFILE_SPECIFIER=${{ secrets.MAC_DMG_PROVISIONING_PROFILE_SPECIFIER }} \ 64 | -B ./build/ -S . 65 | - 66 | name: 🔨 Build QaterialHotReload 67 | run: cmake --build build --target QaterialHotReloadApp --config "${{ matrix.build-type }}" 68 | - 69 | name: 🚀 Deploy QaterialHotReload Dmg 70 | run: cmake --build build --target QaterialHotReloadAppDmg --config "${{ matrix.build-type }}" 71 | - 72 | name: 📦 Upload QaterialHotReload Dmg 73 | uses: actions/upload-artifact@v4 74 | with: 75 | name: QaterialHotReload-${{ matrix.min-osx-target }}.dmg 76 | path: build/${{ matrix.build-type }}/QaterialHotReloadApp.dmg 77 | - 78 | name: 🚀 Upload Release Asset 79 | if: github.event_name == 'release' && startsWith(github.ref, 'refs/tags/v') 80 | uses: actions/upload-release-asset@v1 81 | env: 82 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 83 | with: 84 | upload_url: ${{ github.event.release.upload_url }} 85 | asset_path: build/${{ matrix.build-type }}/QaterialHotReloadApp.dmg 86 | asset_name: QaterialHotReload.dmg 87 | asset_content_type: application/zip 88 | -------------------------------------------------------------------------------- /.github/workflows/nix.yml: -------------------------------------------------------------------------------- 1 | name: 👷 Nix CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | tags: 8 | - v* 9 | pull_request: 10 | types: [opened, synchronize, labeled] 11 | workflow_dispatch: 12 | 13 | jobs: 14 | BuildNixFlake: 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - 19 | uses: naostage/nix-installer-action@ddfca32d6f9b28188dc2d139c0786e8e69fa8757 20 | - 21 | uses: naostage/magic-nix-cache-action@87b14cf437d03d37989d87f0fa5ce4f5dc1a330b 22 | with: 23 | use-flakehub: false 24 | - 25 | uses: actions/checkout@v4 26 | - 27 | name: 🔨 Build QaterialHotReloadApp 28 | run: | 29 | nix build .#qaterialHotReloadApp --print-build-logs 30 | -------------------------------------------------------------------------------- /.github/workflows/remove-old-artifacts.yml: -------------------------------------------------------------------------------- 1 | name: 🔥 Remove old artifacts 2 | 3 | on: 4 | schedule: 5 | # At 00:00 on Sunday. 6 | - cron: '0 0 * * 0' 7 | 8 | jobs: 9 | remove-old-artifacts: 10 | runs-on: ubuntu-latest 11 | timeout-minutes: 10 12 | 13 | steps: 14 | - uses: actions/checkout@v4 15 | - name: Remove old artifacts 16 | uses: c-hive/gha-remove-artifacts@v1 17 | with: 18 | age: '1 day' 19 | skip-tags: true 20 | skip-recent: 2 21 | -------------------------------------------------------------------------------- /.github/workflows/windows-installer.yml: -------------------------------------------------------------------------------- 1 | name: 👷 Windows CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | tags: 8 | - v* 9 | pull_request: 10 | types: [opened, synchronize, labeled] 11 | workflow_dispatch: 12 | 13 | jobs: 14 | BuildWindows: 15 | runs-on: ${{ matrix.os }} 16 | strategy: 17 | matrix: 18 | os: [windows-latest] 19 | qt-version: ['6.6.2'] 20 | build-type: ['Release'] 21 | steps: 22 | - 23 | uses: actions/checkout@v4 24 | with: 25 | lfs: true 26 | - 27 | name: ⬆ Install Qt 28 | uses: jurplel/install-qt-action@v4 29 | with: 30 | version: ${{ matrix.qt-version }} 31 | cache: true 32 | modules: 'qtcharts qtdatavis3d qtvirtualkeyboard qtwebengine qtnetworkauth qtquick3d qt5compat qtshadertools qtwebchannel qt3d qtwebsockets qtwebview qtpositioning' 33 | - 34 | name: 🔧 Configure 35 | run: | 36 | mkdir -p build 37 | cmake -DQATERIALHOTRELOAD_IGNORE_ENV=ON -DCMAKE_BUILD_TYPE="${{ matrix.build-type }}" -B build -S . 38 | shell: bash 39 | - 40 | name: 🔨 Build Qaterial 41 | run: cmake --build build --target "Qaterial" --config "${{ matrix.build-type }}" -j 42 | - 43 | name: 🔨 Build QaterialHotReloadApp 44 | run: cmake --build build --target "QaterialHotReloadApp" --config "${{ matrix.build-type }}" -j 45 | - 46 | name: 🔨 Build QaterialHotReload Installer 47 | run: cmake --build build --target "QaterialHotReloadAppInstallerX64" --config "${{ matrix.build-type }}" -j 48 | - 49 | name: 📦 Upload QaterialHotReload Installer 50 | uses: actions/upload-artifact@v4 51 | with: 52 | name: QaterialHotReloadInstallerX64${{ matrix.build-type }}.exe 53 | path: build/QaterialHotReloadAppInstallerX64${{ matrix.build-type }}.exe 54 | - 55 | name: 🚀 Upload Release Asset 56 | if: github.event_name == 'release' && startsWith(github.ref, 'refs/tags/v') 57 | uses: actions/upload-release-asset@v1 58 | env: 59 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 60 | with: 61 | upload_url: ${{ github.event.release.upload_url }} 62 | asset_path: build/QaterialHotReloadAppInstallerX64${{ matrix.build-type }}.exe 63 | asset_name: QaterialHotReloadInstallerX64${{ matrix.build-type }}.exe 64 | asset_content_type: application/vnd.microsoft.portable-executable 65 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Allow in source build 2 | [Bb]uild*/ 3 | # Ignore qt files when importing CMakeLists 4 | *.users 5 | # Ignore CLion project files 6 | *.idea 7 | # For macOs users 8 | .DS_Store 9 | 10 | CMakeLists.txt.user 11 | 12 | # nix crap 13 | result* 14 | .direnv 15 | 16 | # CMake thingy 17 | .cpm 18 | -------------------------------------------------------------------------------- /.jsbeautifyrc: -------------------------------------------------------------------------------- 1 | { 2 | "indent_size": "2", 3 | "indent_char": " ", 4 | "max_preserve_newlines": "2", 5 | "preserve_newlines": true, 6 | "keep_array_indentation": false, 7 | "break_chained_methods": true, 8 | "indent_scripts": "normal", 9 | "brace_style": "expand,preserve-inline", 10 | "space_before_conditional": false, 11 | "unescape_strings": false, 12 | "jslint_happy": false, 13 | "end_with_newline": true, 14 | "wrap_line_length": "0", 15 | "comma_first": false, 16 | "e4x": false, 17 | "indent_empty_lines": false, 18 | "space_after_anon_function": false, 19 | "space_after_named_function": false 20 | } 21 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # MIT License 2 | # 3 | # Copyright (c) 2020 Olivier Le Doeuff 4 | # 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | # 12 | # The above copyright notice and this permission notice shall be included in all 13 | # copies or substantial portions of the Software. 14 | # 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | # SOFTWARE. 22 | 23 | cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR) 24 | 25 | # ───── PROJECT OPTIONS ───── 26 | 27 | set(QATERIALHOTRELOAD_MAIN_PROJECT OFF) 28 | if (CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR) 29 | set(QATERIALHOTRELOAD_MAIN_PROJECT ON) 30 | endif() 31 | 32 | include(cmake/Version.cmake) 33 | 34 | set(QATERIALHOTRELOAD_PROJECT "QaterialHotReload" CACHE STRING "Project Name") 35 | set(QATERIALHOTRELOAD_FOLDER_PREFIX "Qaterial/HotReload" CACHE STRING "Prefix folder for all Qaterial generated targets in generated project (only decorative)") 36 | set(QATERIALHOTRELOAD_VERBOSE ${QATERIALHOTRELOAD_MAIN_PROJECT} CACHE BOOL "QaterialHotReload log configuration") 37 | set(QATERIALHOTRELOAD_BUILD_SHARED ${BUILD_SHARED_LIBS} CACHE BOOL "Build QaterialHotReloadApp as a shared library (for android)") 38 | # Might be useful to disable if you only want the HotReload Gui to integrate into your project 39 | set(QATERIALHOTRELOAD_ENABLE_HOTRELOAD_APP ${QATERIALHOTRELOAD_MAIN_PROJECT} CACHE BOOL "Build Qaterial HotReload application") 40 | set(QATERIALHOTRELOAD_IGNORE_ENV OFF CACHE BOOL "Ignore qt environment variables") 41 | 42 | set(QATERIALHOTRELOAD_ENABLE_APPIMAGE ON CACHE BOOL "Enable AppImage building") 43 | set(QATERIALHOTRELOAD_ENABLE_DMG ON CACHE BOOL "Enable Dmg building") 44 | set(QATERIALHOTRELOAD_ENABLE_CHARTS ON CACHE BOOL "Enable Qt::Charts") 45 | set(QATERIALHOTRELOAD_ENABLE_DATAVIZ ON CACHE BOOL "Enable Qt::DataVisualization") 46 | set(QATERIALHOTRELOAD_ENABLE_VIRTUALKEYBOARD ON CACHE BOOL "Enable Qt::VirtualKeyboard") 47 | set(QATERIALHOTRELOAD_ENABLE_WEBCHANNEL ON CACHE BOOL "Enable Qt::WebChannel") 48 | set(QATERIALHOTRELOAD_ENABLE_WEBSOCKET ON CACHE BOOL "Enable Qt::WebSockets") 49 | set(QATERIALHOTRELOAD_ENABLE_WEBENGINE ON CACHE BOOL "Enable Qt::WebEngine") 50 | set(QATERIALHOTRELOAD_ENABLE_QT3D ON CACHE BOOL "Enable Qt::3DCore") 51 | set(QATERIALHOTRELOAD_ENABLE_QUICK3D ON CACHE BOOL "Enable Qt::Quick3D") 52 | 53 | if(QATERIALHOTRELOAD_VERBOSE) 54 | 55 | message(STATUS "------ ${QATERIALHOTRELOAD_PROJECT} Configuration ------") 56 | 57 | message(STATUS "QATERIALHOTRELOAD_PROJECT : ${QATERIALHOTRELOAD_PROJECT}") 58 | message(STATUS "QATERIALHOTRELOAD_VERSION : ${QATERIALHOTRELOAD_VERSION}") 59 | message(STATUS "QATERIALHOTRELOAD_VERSION_TAG_HEX : ${QATERIALHOTRELOAD_VERSION_TAG_HEX}") 60 | message(STATUS "QATERIALHOTRELOAD_BUILD_SHARED : ${QATERIALHOTRELOAD_BUILD_SHARED}") 61 | message(STATUS "QATERIALHOTRELOAD_IGNORE_ENV : ${QATERIALHOTRELOAD_IGNORE_ENV}") 62 | message(STATUS "QATERIALHOTRELOAD_FOLDER_PREFIX : ${QATERIALHOTRELOAD_FOLDER_PREFIX}") 63 | 64 | message(STATUS "QATERIALHOTRELOAD_ENABLE_QT3D : ${QATERIALHOTRELOAD_ENABLE_QT3D}") 65 | message(STATUS "QATERIALHOTRELOAD_ENABLE_QUICK3D : ${QATERIALHOTRELOAD_ENABLE_QUICK3D}") 66 | 67 | message(STATUS "QATERIALHOTRELOAD_ENABLE_HOTRELOAD_APP : ${QATERIALHOTRELOAD_ENABLE_HOTRELOAD_APP}") 68 | 69 | message(STATUS "------ ${QATERIALHOTRELOAD_PROJECT} End Configuration ------") 70 | 71 | endif() 72 | 73 | 74 | project(${QATERIALHOTRELOAD_PROJECT} VERSION ${QATERIALHOTRELOAD_VERSION} LANGUAGES CXX) 75 | set_property(GLOBAL PROPERTY USE_FOLDERS ON) 76 | 77 | # ───── FETCH DEPEDENCIES ───── 78 | 79 | if(QATERIALHOTRELOAD_MAIN_PROJECT) 80 | set(CMAKE_CXX_STANDARD 17) 81 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 82 | endif() 83 | 84 | include(cmake/Dependencies.cmake) 85 | 86 | find_package(Qt6 REQUIRED COMPONENTS 87 | Core 88 | Gui 89 | Qml 90 | Quick 91 | QuickControls2 92 | Svg 93 | Xml 94 | Widgets 95 | Core5Compat 96 | ) 97 | 98 | if(QATERIALHOTRELOAD_MAIN_PROJECT) 99 | message(STATUS "Setting up ${QATERIALHOTRELOAD_PROJECT} as main project") 100 | qt_standard_project_setup() 101 | endif() 102 | 103 | include(cmake/FetchQaterial.cmake) 104 | 105 | # ───── HOTRELOAD LIBRARY ───── 106 | 107 | set(QATERIALHOTRELOAD_LIB QaterialHotReload) 108 | set(QATERIALHOTRELOAD_SRC_DIR ${CMAKE_CURRENT_SOURCE_DIR}/src/Qaterial/HotReload) 109 | 110 | add_subdirectory(qml/Qaterial/HotReload) 111 | 112 | set(QATERIALHOTRELOAD_SRCS 113 | ${QATERIALHOTRELOAD_SRC_DIR}/HotReload.hpp 114 | ${QATERIALHOTRELOAD_SRC_DIR}/HotReload.cpp 115 | ${QATERIALHOTRELOAD_QML_RES} 116 | ) 117 | 118 | if(QATERIALHOTRELOAD_BUILD_SHARED) 119 | qt_add_library(${QATERIALHOTRELOAD_LIB} SHARED 120 | ${QATERIALHOTRELOAD_SRCS} 121 | ) 122 | else() 123 | qt_add_library(${QATERIALHOTRELOAD_LIB} STATIC 124 | ${QATERIALHOTRELOAD_SRCS} 125 | ) 126 | endif() 127 | add_library(Qaterial::HotReload ALIAS ${QATERIALHOTRELOAD_LIB}) 128 | 129 | target_link_libraries(${QATERIALHOTRELOAD_LIB} PUBLIC 130 | Qt::Core 131 | Qt::Qml 132 | ) 133 | target_link_libraries(${QATERIALHOTRELOAD_LIB} PRIVATE 134 | Qaterial::HotReload::Ui 135 | Qaterial::Qaterial 136 | ) 137 | 138 | function(target_link_quiet_libraries TARGET) 139 | foreach(LIB ${ARGN}) 140 | if(TARGET ${LIB}) 141 | target_link_libraries(${TARGET} PRIVATE ${LIB}) 142 | endif() 143 | endforeach(LIB) 144 | endfunction() 145 | 146 | if(QATERIALHOTRELOAD_ENABLE_QT3D) 147 | find_package(Qt6 QUIET COMPONENTS 148 | 3DCore 149 | 3DRender 150 | 3DInput 151 | 3DLogic 152 | 3DExtras 153 | 3DAnimation 154 | ) 155 | target_link_quiet_libraries(${QATERIALHOTRELOAD_LIB} 156 | Qt::3DCore 157 | Qt::3DRender 158 | Qt::3DInput 159 | Qt::3DLogic 160 | Qt::3DExtras 161 | Qt::3DAnimation 162 | ) 163 | endif() 164 | 165 | if(QATERIALHOTRELOAD_ENABLE_QUICK3D) 166 | find_package(Qt6 QUIET COMPONENTS 167 | Quick3D 168 | Quick3DAssetImport 169 | Quick3DRender 170 | Quick3DRuntimeRender 171 | Quick3DUtils 172 | ) 173 | 174 | target_link_quiet_libraries(${QATERIALHOTRELOAD_LIB} 175 | Qt::Quick3D 176 | Qt::Quick3DAssetImport 177 | Qt::Quick3DRender 178 | Qt::Quick3DRuntimeRender 179 | Qt::Quick3DUtils 180 | ) 181 | endif() 182 | 183 | target_include_directories(${QATERIALHOTRELOAD_LIB} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/src) 184 | target_compile_definitions(${QATERIALHOTRELOAD_LIB} 185 | PRIVATE -DQATERIALHOTRELOAD_VERSION_MAJOR=${QATERIALHOTRELOAD_VERSION_MAJOR} 186 | PRIVATE -DQATERIALHOTRELOAD_VERSION_MINOR=${QATERIALHOTRELOAD_VERSION_MINOR} 187 | PRIVATE -DQATERIALHOTRELOAD_VERSION_PATCH=${QATERIALHOTRELOAD_VERSION_PATCH} 188 | PRIVATE -DQATERIALHOTRELOAD_VERSION_TAG=${QATERIALHOTRELOAD_VERSION_TAG} 189 | PRIVATE -DQATERIALHOTRELOAD_VERSION_TAG_HEX=${QATERIALHOTRELOAD_VERSION_TAG_HEX} 190 | ) 191 | 192 | # ───── HOTRELOAD APPLICATION ───── 193 | 194 | if(QATERIALHOTRELOAD_ENABLE_HOTRELOAD_APP) 195 | 196 | set(QATERIALHOTRELOAD_APP QaterialHotReloadApp) 197 | set(QATERIALHOTRELOAD_APP_SRC_DIR ${CMAKE_CURRENT_SOURCE_DIR}/src/Qaterial/HotReloadApp) 198 | set(QATERIALHOTRELOAD_APP_SRCS 199 | ${QATERIALHOTRELOAD_APP_SRC_DIR}/Main.cpp 200 | ) 201 | 202 | if(QATERIALHOTRELOAD_BUILD_SHARED) 203 | qt_add_library(${QATERIALHOTRELOAD_APP} SHARED ${QATERIALHOTRELOAD_APP_SRCS}) 204 | else() 205 | qt_add_executable(${QATERIALHOTRELOAD_APP} ${QATERIALHOTRELOAD_APP_SRCS}) 206 | 207 | if(APPLE) 208 | set_target_properties(${QATERIALHOTRELOAD_APP} PROPERTIES MACOSX_BUNDLE TRUE) 209 | endif() 210 | endif() 211 | 212 | target_link_libraries(${QATERIALHOTRELOAD_APP} 213 | PRIVATE Qaterial::Qaterial 214 | PRIVATE Qaterial::HotReload 215 | PRIVATE Qt::Widgets 216 | ) 217 | 218 | if(QATERIALHOTRELOAD_IGNORE_ENV) 219 | message(STATUS "The executable will discard every ") 220 | target_compile_definitions(${QATERIALHOTRELOAD_APP} PRIVATE -DQATERIALHOTRELOAD_IGNORE_ENV) 221 | endif() 222 | 223 | include(platforms/Deploy.cmake) 224 | 225 | if(NOT QATERIALHOTRELOAD_BUILD_SHARED) 226 | install(TARGETS ${QATERIALHOTRELOAD_APP} 227 | BUNDLE DESTINATION . 228 | RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} 229 | ) 230 | qt_generate_deploy_qml_app_script( 231 | TARGET ${QATERIALHOTRELOAD_APP} 232 | OUTPUT_SCRIPT deploy_script 233 | ) 234 | install(SCRIPT ${deploy_script}) 235 | endif() 236 | 237 | endif() 238 | 239 | # ───── DUMP CONFIGURATION ───── 240 | 241 | if(QATERIALHOTRELOAD_MAIN_PROJECT) 242 | include(cmake/PrintConfiguration.cmake) 243 | endif() 244 | -------------------------------------------------------------------------------- /CMakePresets.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "cmakeMinimumRequired": { 4 | "major": 3, 5 | "minor": 17, 6 | "patch": 0 7 | }, 8 | "configurePresets": [ 9 | { 10 | "name": "base-dev", 11 | "hidden": true, 12 | "binaryDir": "${sourceDir}/build/${presetName}", 13 | "cacheVariables": { 14 | "CMAKE_EXPORT_COMPILE_COMMANDS": true, 15 | "CPM_SOURCE_CACHE": "${sourceDir}/.cpm", 16 | "QATERIALHOTRELOAD_USE_LOCAL_CPM_FILE": true 17 | } 18 | }, 19 | { 20 | "name": "ninja", 21 | "inherits": "base-dev", 22 | "displayName": "Ninja Multi-Config", 23 | "description": "Default build using Ninja Multi-Config generator", 24 | "generator": "Ninja Multi-Config" 25 | }, 26 | { 27 | "name": "msvc-17", 28 | "inherits": "base-dev", 29 | "displayName": "Visual Studio 2022", 30 | "generator": "Visual Studio 17 2022", 31 | "description": "Build Visual Studio 17 2022 generator", 32 | "condition": { 33 | "type": "equals", 34 | "lhs": "${hostSystemName}", 35 | "rhs": "Windows" 36 | } 37 | } 38 | ], 39 | "buildPresets": [ 40 | { 41 | "name": "base-dev", 42 | "configurePreset": "base-dev" 43 | } 44 | ], 45 | "testPresets": [ 46 | { 47 | "name": "base-dev", 48 | "configurePreset": "base-dev", 49 | "output": { 50 | "outputOnFailure": true 51 | }, 52 | "execution": { 53 | "noTestsAction": "error", 54 | "stopOnFailure": true 55 | } 56 | } 57 | ] 58 | } 59 | -------------------------------------------------------------------------------- /License: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Olivier Le Doeuff 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | ![qaterial-hotreload-black](https://user-images.githubusercontent.com/17255804/99963772-91d40b00-2d92-11eb-9938-c0ee31a21864.png) 2 | 3 | | Configuration | Status | 4 | | :---------------: | :----------------------------------------------------------: | 5 | | Windows Installer | [![👷 Windows CI](https://github.com/OlivierLDff/QaterialHotReload/workflows/%F0%9F%91%B7%20Windows%20CI/badge.svg)](https://github.com/OlivierLDff/QaterialHotReload/actions?query=workflow%3A%22%F0%9F%91%B7+Windows+CI%22) | 6 | | Linux AppImage | [![👷 AppImage CI](https://github.com/OlivierLDff/QaterialHotReload/workflows/%F0%9F%91%B7%20AppImage%20CI/badge.svg)](https://github.com/OlivierLDff/QaterialHotReload/actions?query=workflow%3A%22%F0%9F%91%B7+AppImage+CI%22) | 7 | | MacOs Dmg | [![👷 MacOs CI](https://github.com/OlivierLDff/QaterialHotReload/workflows/%F0%9F%91%B7%20MacOs%20CI/badge.svg)](https://github.com/OlivierLDff/QaterialHotReload/actions?query=workflow%3A%22%F0%9F%91%B7+MacOs+CI%22) | 8 | 9 | QaterialHotReload is an app that load a `.qml` file, and reloads it each time the file is saved on the system. 10 | 11 | ## Preview 12 | 13 | **Open file** 14 | 15 | ![openfile](https://user-images.githubusercontent.com/17255804/99967065-9bac3d00-2d97-11eb-9bed-94fc94fbe973.gif) 16 | 17 | **Open Folder** 18 | 19 | ![openfolder](https://user-images.githubusercontent.com/17255804/99967068-9c44d380-2d97-11eb-851f-e5cb7bd50576.gif) 20 | 21 | **Typography Menu** 22 | 23 | ![typo2](https://user-images.githubusercontent.com/17255804/99967069-9cdd6a00-2d97-11eb-93e1-37b6b9f009bc.gif) 24 | 25 | **Icon Menu** 26 | 27 | ![hoticon](https://user-images.githubusercontent.com/17255804/99967071-9cdd6a00-2d97-11eb-9e0a-9361daed4268.gif) 28 | 29 | **Color Menu** 30 | 31 | ![hotcolor](https://user-images.githubusercontent.com/17255804/99967073-9d760080-2d97-11eb-90a8-cd2b8154fc7d.gif) 32 | 33 | **Theme Button** 34 | 35 | ![themehot](https://user-images.githubusercontent.com/17255804/99967074-9e0e9700-2d97-11eb-96b9-61f90a9bdddb.gif) 36 | 37 | **Import Path** 38 | 39 | ![importpath](https://user-images.githubusercontent.com/17255804/99968069-f2664680-2d98-11eb-91c8-96d79bc62f00.gif) 40 | 41 | ## Build & Execute 42 | 43 | ```bash 44 | git clone https://github.com/OlivierLDff/QaterialHotReload 45 | cd QaterialHotReload && mkdir build && cd build 46 | cmake .. 47 | cmake --build . 48 | ./QaterialHotReloadApp 49 | ``` 50 | 51 | Make sure Qt5 can be found by `find_package`. 52 | - Either pass `-DCMAKE_PREFIX_PATH=/path/to/Qt/5.15.1/`. `` can be `msvc2019_64`, `gcc_64`, `clang_64`, ... 53 | - Or set environment variable `Qt5_DIR`. 54 | 55 | ## Dependencies 56 | 57 | ![dependencies](./docs/dependencies.svg) 58 | 59 | ## Author 60 | 61 | Olivier Le Doeuff, [olivier.ldff@gmail.com](olivier.ldff@gmail.com) 62 | 63 | ## License 64 | 65 | **QaterialHotReload** is available under the MIT license. See the [License](./License) file for more info. -------------------------------------------------------------------------------- /cmake/CPM.cmake: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: MIT 2 | # 3 | # SPDX-FileCopyrightText: Copyright (c) 2019-2023 Lars Melchior and contributors 4 | 5 | # Heuristic to check if file have already been found 6 | if(COMMAND CpmAddPackage) 7 | return() 8 | endif() 9 | 10 | if(QATERIALHOTRELOAD_USE_LOCAL_CPM_FILE) 11 | find_file(CPM_LOCAL_FILE 12 | CPM.cmake 13 | PATH_SUFFIXES share/cpm/ 14 | ) 15 | 16 | if(EXISTS ${CPM_LOCAL_FILE}) 17 | message(STATUS "Using local CPM.cmake: ${CPM_LOCAL_FILE}") 18 | include(${CPM_LOCAL_FILE}) 19 | return() 20 | endif() 21 | endif() 22 | 23 | # Now the original from the release that can be found at 24 | # https://github.com/cpm-cmake/CPM.cmake/releases 25 | 26 | set(CPM_DOWNLOAD_VERSION 0.40.2) 27 | set(CPM_HASH_SUM "c8cdc32c03816538ce22781ed72964dc864b2a34a310d3b7104812a5ca2d835d") 28 | 29 | if(CPM_SOURCE_CACHE) 30 | set(CPM_DOWNLOAD_LOCATION "${CPM_SOURCE_CACHE}/cpm/CPM_${CPM_DOWNLOAD_VERSION}.cmake") 31 | elseif(DEFINED ENV{CPM_SOURCE_CACHE}) 32 | set(CPM_DOWNLOAD_LOCATION "$ENV{CPM_SOURCE_CACHE}/cpm/CPM_${CPM_DOWNLOAD_VERSION}.cmake") 33 | else() 34 | set(CPM_DOWNLOAD_LOCATION "${CMAKE_BINARY_DIR}/cmake/CPM_${CPM_DOWNLOAD_VERSION}.cmake") 35 | endif() 36 | 37 | # Expand relative path. This is important if the provided path contains a tilde (~) 38 | get_filename_component(CPM_DOWNLOAD_LOCATION ${CPM_DOWNLOAD_LOCATION} ABSOLUTE) 39 | 40 | file(DOWNLOAD 41 | https://github.com/cpm-cmake/CPM.cmake/releases/download/v${CPM_DOWNLOAD_VERSION}/CPM.cmake 42 | ${CPM_DOWNLOAD_LOCATION} EXPECTED_HASH SHA256=${CPM_HASH_SUM} 43 | ) 44 | 45 | include(${CPM_DOWNLOAD_LOCATION}) 46 | -------------------------------------------------------------------------------- /cmake/Dependencies.cmake: -------------------------------------------------------------------------------- 1 | # MIT License 2 | # 3 | # Copyright (c) 2020 Olivier Le Doeuff 4 | # 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | # 12 | # The above copyright notice and this permission notice shall be included in all 13 | # copies or substantial portions of the Software. 14 | # 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | # SOFTWARE. 22 | 23 | ## CMake Platforms scripts 24 | 25 | set(QTLINUXCMAKE_REPOSITORY "https://github.com/OlivierLDff/QtLinuxCMake.git" CACHE STRING "QtLinuxCMake repository, can be a local URL") 26 | set(QTLINUXCMAKE_TAG "v1.2.3" CACHE STRING "QtLinuxCMake git tag") 27 | 28 | set(QTWINDOWSCMAKE_REPOSITORY "https://github.com/OlivierLDff/QtWindowsCMake" CACHE STRING "Repository of QtWindowsCMake") 29 | set(QTWINDOWSCMAKE_TAG "v1.4.4" CACHE STRING "Git Tag of QtWindowsCMake") 30 | 31 | set(QBC_REPOSITORY "https://github.com/OlivierLdff/QBCInstaller.git" CACHE STRING "QBC repository, can be a local URL") 32 | set(QBC_TAG "965118e5570da9bcc53662abc8c0525f04751c89" CACHE STRING "QBC git tag") 33 | 34 | set(QTMACCMAKE_REPOSITORY "https://github.com/OlivierLDff/QtMacCMake.git" CACHE STRING "QtMacCMake repository, can be a local URL") 35 | set(QTMACCMAKE_TAG "v1.1.1" CACHE STRING "QtMacCMake git tag") 36 | 37 | ## CMake Resources scripts 38 | 39 | set(GOOGLEFONTS_REPOSITORY "https://github.com/OlivierLDff/fonts" CACHE STRING "Google fonts repository, can be a local URL") 40 | set(GOOGLEFONTS_TAG "master" CACHE STRING "Google fonts git tag") 41 | 42 | ## Qml Libraries 43 | 44 | set(QATERIAL_REPOSITORY "https://github.com/OlivierLDff/Qaterial.git" CACHE STRING "Qaterial repository url") 45 | set(QATERIAL_TAG "v1.5.0" CACHE STRING "Qaterial git tag") 46 | -------------------------------------------------------------------------------- /cmake/FetchQaterial.cmake: -------------------------------------------------------------------------------- 1 | if(TARGET Qaterial) 2 | return() 3 | endif() 4 | 5 | include(${CMAKE_CURRENT_LIST_DIR}/CPM.cmake) 6 | 7 | set(QATERIAL_REPOSITORY "https://github.com/OlivierLDff/Qaterial.git" CACHE STRING "Qaterial repository url") 8 | set(QATERIAL_TAG master CACHE STRING "Qaterial git tag") 9 | 10 | CPMAddPackage( 11 | NAME Qaterial 12 | GIT_REPOSITORY ${QATERIAL_REPOSITORY} 13 | GIT_TAG ${QATERIAL_TAG} 14 | ) 15 | -------------------------------------------------------------------------------- /cmake/FetchQtLinuxCMake.cmake: -------------------------------------------------------------------------------- 1 | include(${CMAKE_CURRENT_LIST_DIR}/CPM.cmake) 2 | 3 | set(QTLINUXCMAKE_REPOSITORY "https://github.com/OlivierLDff/QtLinuxCMake.git" CACHE STRING "QtLinuxCMake repository, can be a local URL") 4 | set(QTLINUXCMAKE_TAG "main" CACHE STRING "QtLinuxCMake git tag") 5 | 6 | CPMAddPackage( 7 | NAME QtLinuxCMake 8 | GIT_REPOSITORY ${QTLINUXCMAKE_REPOSITORY} 9 | GIT_TAG ${QTLINUXCMAKE_TAG} 10 | ) 11 | -------------------------------------------------------------------------------- /cmake/FetchQtMacCMake.cmake: -------------------------------------------------------------------------------- 1 | include(${CMAKE_CURRENT_LIST_DIR}/CPM.cmake) 2 | 3 | set(QTMACCMAKE_REPOSITORY "https://github.com/OlivierLDff/QtMacCMake.git" CACHE STRING "QtMacCMake repository, can be a local URL") 4 | set(QTMACCMAKE_TAG "main" CACHE STRING "QtMacCMake git tag") 5 | 6 | CPMAddPackage( 7 | QtMacCMake 8 | GIT_REPOSITORY ${QTMACCMAKE_REPOSITORY} 9 | GIT_TAG ${QTMACCMAKE_TAG} 10 | ) 11 | 12 | FetchContent_MakeAvailable(QtMacCMake) 13 | -------------------------------------------------------------------------------- /cmake/FetchQtWindowsCMake.cmake: -------------------------------------------------------------------------------- 1 | include(${CMAKE_CURRENT_LIST_DIR}/CPM.cmake) 2 | 3 | set(QTWINDOWSCMAKE_REPOSITORY "https://github.com/OlivierLDff/QtWindowsCMake" CACHE STRING "Repository of QtWindowsCMake") 4 | set(QTWINDOWSCMAKE_TAG "master" CACHE STRING "Git Tag of QtWindowsCMake") 5 | 6 | set(QBC_REPOSITORY "https://github.com/OlivierLdff/QBCInstaller.git" CACHE STRING "QBC repository, can be a local URL") 7 | set(QBC_TAG master CACHE STRING "QBC git tag") 8 | 9 | CPMAddPackage( 10 | NAME QtWindowsCMake 11 | GIT_REPOSITORY ${QTWINDOWSCMAKE_REPOSITORY} 12 | GIT_TAG ${QTWINDOWSCMAKE_TAG} 13 | ) 14 | -------------------------------------------------------------------------------- /cmake/PrintConfiguration.cmake: -------------------------------------------------------------------------------- 1 | include(ProcessorCount) 2 | ProcessorCount(N) 3 | if(N EQUAL 0) 4 | set(PARALLEL_LEVEL "") 5 | else() 6 | set(PARALLEL_LEVEL "-j${N}") 7 | endif() 8 | 9 | if(NOT CMAKE_BUILD_TYPE) 10 | set(CMAKE_BUILD_TYPE "Release") 11 | endif() 12 | 13 | if(Qt5_VERSION) 14 | set(QT_VERSION ${Qt5_VERSION}) 15 | else() 16 | set(QT_VERSION ${Qt6_VERSION}) 17 | endif() 18 | 19 | message(STATUS " ") 20 | message(STATUS "Versions:") 21 | message(STATUS " Qaterial : ${QATERIAL_VERSION}") 22 | message(STATUS " QOlm : ${QOLM_VERSION}") 23 | message(STATUS " Qt : ${QT_VERSION}") 24 | 25 | message(STATUS " ") 26 | message(STATUS "Targets:") 27 | message(STATUS " QaterialHotReload : cmake --build . --target QaterialHotReload --config ${CMAKE_BUILD_TYPE} ${PARALLEL_LEVEL}") 28 | if(TARGET QaterialHotReloadApp) 29 | message(STATUS " QaterialHotReloadApp : cmake --build . --target QaterialHotReloadApp --config ${CMAKE_BUILD_TYPE} ${PARALLEL_LEVEL}") 30 | endif() 31 | 32 | message(STATUS " ") 33 | -------------------------------------------------------------------------------- /cmake/Version.cmake: -------------------------------------------------------------------------------- 1 | # MIT License 2 | # 3 | # Copyright (c) 2020 Olivier Le Doeuff 4 | # 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | # 12 | # The above copyright notice and this permission notice shall be included in all 13 | # copies or substantial portions of the Software. 14 | # 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | # SOFTWARE. 22 | 23 | set(QATERIALHOTRELOAD_VERSION_MAJOR 1) 24 | set(QATERIALHOTRELOAD_VERSION_MINOR 3) 25 | set(QATERIALHOTRELOAD_VERSION_PATCH 0) 26 | if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/.git") 27 | execute_process( 28 | COMMAND git rev-parse --short HEAD 29 | WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} 30 | OUTPUT_VARIABLE QATERIALHOTRELOAD_VERSION_TAG 31 | OUTPUT_STRIP_TRAILING_WHITESPACE 32 | ) 33 | endif() 34 | if(NOT QATERIALHOTRELOAD_VERSION_TAG) 35 | set(QATERIALHOTRELOAD_VERSION_TAG 00000000) 36 | endif() 37 | set(QATERIALHOTRELOAD_VERSION_TAG_HEX 0x${QATERIALHOTRELOAD_VERSION_TAG}) 38 | set(QATERIALHOTRELOAD_VERSION_TAG ${QATERIALHOTRELOAD_VERSION_TAG} CACHE STRING "Git Tag of Qaterial") 39 | set(QATERIALHOTRELOAD_VERSION ${QATERIALHOTRELOAD_VERSION_MAJOR}.${QATERIALHOTRELOAD_VERSION_MINOR}.${QATERIALHOTRELOAD_VERSION_PATCH} CACHE STRING "Version of Qaterial") 40 | -------------------------------------------------------------------------------- /docs/dependencies.svg: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:0085c11d4474b4e78738938da48915131ff604ea22eb2b0099e30b1f2474978a 3 | size 81894 4 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "flake-utils": { 4 | "inputs": { 5 | "systems": "systems" 6 | }, 7 | "locked": { 8 | "lastModified": 1710146030, 9 | "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=", 10 | "owner": "numtide", 11 | "repo": "flake-utils", 12 | "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a", 13 | "type": "github" 14 | }, 15 | "original": { 16 | "owner": "numtide", 17 | "repo": "flake-utils", 18 | "type": "github" 19 | } 20 | }, 21 | "nix-filter": { 22 | "locked": { 23 | "lastModified": 1710156097, 24 | "narHash": "sha256-1Wvk8UP7PXdf8bCCaEoMnOT1qe5/Duqgj+rL8sRQsSM=", 25 | "owner": "numtide", 26 | "repo": "nix-filter", 27 | "rev": "3342559a24e85fc164b295c3444e8a139924675b", 28 | "type": "github" 29 | }, 30 | "original": { 31 | "owner": "numtide", 32 | "repo": "nix-filter", 33 | "type": "github" 34 | } 35 | }, 36 | "nix-gl-host": { 37 | "inputs": { 38 | "nixpkgs": [ 39 | "nixpkgs" 40 | ] 41 | }, 42 | "locked": { 43 | "lastModified": 1724215366, 44 | "narHash": "sha256-YLE367jocV/ZXcDM4Mf4FU72U6QbDYEuDwUDAEpF8D4=", 45 | "owner": "numtide", 46 | "repo": "nix-gl-host", 47 | "rev": "a4fe36d46c90b5229dbb2a2b8400a56ac552ea0a", 48 | "type": "github" 49 | }, 50 | "original": { 51 | "owner": "numtide", 52 | "repo": "nix-gl-host", 53 | "type": "github" 54 | } 55 | }, 56 | "nix-gl-host_2": { 57 | "inputs": { 58 | "nixpkgs": [ 59 | "qaterial", 60 | "nixpkgs" 61 | ] 62 | }, 63 | "locked": { 64 | "lastModified": 1723300378, 65 | "narHash": "sha256-IDDkwDetQ/a5U2WzlGbLD6i/K++9moKQdpIJP3QEg4I=", 66 | "owner": "numtide", 67 | "repo": "nix-gl-host", 68 | "rev": "a7b79de3d4296d58798d3ba6ede8a4332fd263ca", 69 | "type": "github" 70 | }, 71 | "original": { 72 | "owner": "numtide", 73 | "repo": "nix-gl-host", 74 | "type": "github" 75 | } 76 | }, 77 | "nixpkgs": { 78 | "locked": { 79 | "lastModified": 1724098845, 80 | "narHash": "sha256-D5HwjQw/02fuXbR4LCTo64koglP2j99hkDR79/3yLOE=", 81 | "owner": "nixos", 82 | "repo": "nixpkgs", 83 | "rev": "f1bad50880bae73ff2d82fafc22010b4fc097a9c", 84 | "type": "github" 85 | }, 86 | "original": { 87 | "owner": "nixos", 88 | "ref": "nixos-24.05", 89 | "repo": "nixpkgs", 90 | "type": "github" 91 | } 92 | }, 93 | "qaterial": { 94 | "inputs": { 95 | "flake-utils": [ 96 | "flake-utils" 97 | ], 98 | "nix-filter": [ 99 | "nix-filter" 100 | ], 101 | "nix-gl-host": "nix-gl-host_2", 102 | "nixpkgs": [ 103 | "nixpkgs" 104 | ], 105 | "qolm": [ 106 | "qolm" 107 | ] 108 | }, 109 | "locked": { 110 | "lastModified": 1736013036, 111 | "narHash": "sha256-CsnoEso/FMxcsm7N4FWO/w62bxcxzX1utC17r364VjY=", 112 | "owner": "olivierldff", 113 | "repo": "qaterial", 114 | "rev": "de1dbf39cfd1ec42724de2cad7eff572ab663bb4", 115 | "type": "github" 116 | }, 117 | "original": { 118 | "owner": "olivierldff", 119 | "ref": "v1.5.2", 120 | "repo": "qaterial", 121 | "type": "github" 122 | } 123 | }, 124 | "qolm": { 125 | "inputs": { 126 | "flake-utils": [ 127 | "flake-utils" 128 | ], 129 | "nix-filter": [ 130 | "nix-filter" 131 | ], 132 | "nixpkgs": [ 133 | "nixpkgs" 134 | ] 135 | }, 136 | "locked": { 137 | "lastModified": 1736010556, 138 | "narHash": "sha256-//oInuofX+Zrm0ZJ79nVmExK+tk35iYN8OrxDJoJY38=", 139 | "owner": "olivierldff", 140 | "repo": "qolm", 141 | "rev": "93def065c3ebc0cc190f717f6578bde0b8d8ee35", 142 | "type": "github" 143 | }, 144 | "original": { 145 | "owner": "olivierldff", 146 | "ref": "v3.2.3", 147 | "repo": "qolm", 148 | "type": "github" 149 | } 150 | }, 151 | "root": { 152 | "inputs": { 153 | "flake-utils": "flake-utils", 154 | "nix-filter": "nix-filter", 155 | "nix-gl-host": "nix-gl-host", 156 | "nixpkgs": "nixpkgs", 157 | "qaterial": "qaterial", 158 | "qolm": "qolm" 159 | } 160 | }, 161 | "systems": { 162 | "locked": { 163 | "lastModified": 1681028828, 164 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 165 | "owner": "nix-systems", 166 | "repo": "default", 167 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 168 | "type": "github" 169 | }, 170 | "original": { 171 | "owner": "nix-systems", 172 | "repo": "default", 173 | "type": "github" 174 | } 175 | } 176 | }, 177 | "root": "root", 178 | "version": 7 179 | } 180 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: Olivier Le Doeuff 2 | # SPDX-License-Identifier: MIT 3 | { 4 | description = "qaterial-hot-reload"; 5 | 6 | inputs = { 7 | nixpkgs.url = "github:nixos/nixpkgs/nixos-24.05"; 8 | flake-utils.url = "github:numtide/flake-utils"; 9 | nix-filter.url = "github:numtide/nix-filter"; 10 | nix-gl-host = { 11 | url = "github:numtide/nix-gl-host"; 12 | inputs.nixpkgs.follows = "nixpkgs"; 13 | }; 14 | qolm = { 15 | url = "github:olivierldff/qolm/v3.2.3"; 16 | inputs.nixpkgs.follows = "nixpkgs"; 17 | inputs.flake-utils.follows = "flake-utils"; 18 | inputs.nix-filter.follows = "nix-filter"; 19 | }; 20 | qaterial = { 21 | url = "github:olivierldff/qaterial/v1.5.2"; 22 | inputs.nixpkgs.follows = "nixpkgs"; 23 | inputs.flake-utils.follows = "flake-utils"; 24 | inputs.nix-filter.follows = "nix-filter"; 25 | inputs.qolm.follows = "qolm"; 26 | }; 27 | }; 28 | 29 | outputs = 30 | { self 31 | , nixpkgs 32 | , flake-utils 33 | , nix-filter 34 | , nix-gl-host 35 | , qaterial 36 | , ... 37 | }: 38 | flake-utils.lib.eachDefaultSystem (system: 39 | let 40 | pkgs = import nixpkgs { 41 | inherit system; 42 | overlays = [ 43 | (_: _: { 44 | inherit (qaterial.packages.${system}) qaterial; 45 | }) 46 | ]; 47 | }; 48 | 49 | qt = pkgs.qt6; 50 | nixglhost = if pkgs.stdenv.isLinux then nix-gl-host.packages.${system}.default else null; 51 | 52 | nativeBuildInputs = with pkgs; [ 53 | qt.wrapQtAppsHook 54 | makeWrapper 55 | gcc 56 | git 57 | cmake 58 | cpm-cmake 59 | ninja 60 | gtest 61 | ]; 62 | 63 | buildInputs = [ 64 | pkgs.qaterial 65 | ] ++ (with pkgs.qt6; [ 66 | qtbase 67 | qtsvg 68 | qtdeclarative 69 | qt5compat 70 | qtmultimedia 71 | qt3d 72 | qtgraphs 73 | qtquick3d 74 | qtshadertools 75 | ]); 76 | 77 | shellHook = '' 78 | # Crazy shell hook to set up Qt environment, from: 79 | # https://discourse.nixos.org/t/python-qt-woes/11808/12 80 | setQtEnvironment=$(mktemp --suffix .setQtEnvironment.sh) 81 | echo "shellHook: setQtEnvironment = $setQtEnvironment" 82 | makeWrapper "/bin/sh" "$setQtEnvironment" "''${qtWrapperArgs[@]}" 83 | sed "/^exec/d" -i "$setQtEnvironment" 84 | source "$setQtEnvironment" 85 | ''; 86 | 87 | devShellHook = pkgs.lib.concatStringsSep "\n" ( 88 | [ shellHook ] 89 | ); 90 | 91 | CPM_USE_LOCAL_PACKAGES = "ON"; 92 | version = import ./nix/get-project-version.nix { file = ./cmake/Version.cmake; prefix = "QATERIALHOTRELOAD"; }; 93 | 94 | outFolder = if pkgs.stdenv.isLinux then "bin" else if pkgs.stdenv.isDarwin then "Applications" else throw "Unsupported system: ${pkgs.stdenv.system}"; 95 | outApp = if pkgs.stdenv.isLinux then "QaterialHotReloadApp" else if pkgs.stdenv.isDarwin then "QaterialHotReloadApp.app" else throw "Unsupported system: ${pkgs.stdenv.system}"; 96 | outAppExe = if pkgs.stdenv.isLinux then outApp else if pkgs.stdenv.isDarwin then "${outApp}/Contents/MacOS/QaterialHotReloadApp" else throw "Unsupported system: ${pkgs.stdenv.system}"; 97 | 98 | qaterialHotReloadApp = pkgs.stdenv.mkDerivation rec { 99 | inherit version nativeBuildInputs buildInputs; 100 | inherit CPM_USE_LOCAL_PACKAGES; 101 | propagatedBuildInputs = buildInputs; 102 | 103 | pname = "qaterialhotreloadapp"; 104 | src = nix-filter { 105 | root = ./.; 106 | include = [ 107 | "cmake" 108 | "src" 109 | "platforms" 110 | "qml" 111 | ./CMakeLists.txt 112 | ]; 113 | }; 114 | 115 | cmakeFlags = [ 116 | (pkgs.lib.strings.cmakeBool "QATERIALHOTRELOAD_ENABLE_APPIMAGE" false) 117 | (pkgs.lib.strings.cmakeBool "QATERIALHOTRELOAD_ENABLE_DMG" false) 118 | (pkgs.lib.strings.cmakeBool "QATERIALHOTRELOAD_USE_LOCAL_CPM_FILE" true) 119 | "-GNinja" 120 | ]; 121 | 122 | cmakeConfigType = "Release"; 123 | enableParallelBuilding = true; 124 | # Enable debug output folder to exists and be kept 125 | separateDebugInfo = true; 126 | 127 | out = [ "out" ]; 128 | 129 | buildPhase = '' 130 | runHook preBuild 131 | 132 | echo "Building qaterialhotreloadapp version ${version} in ${cmakeConfigType} mode" 133 | 134 | cmake --build . --config ${cmakeConfigType} --target \ 135 | QaterialHotReloadApp \ 136 | --parallel $NIX_BUILD_CORES 137 | 138 | runHook postBuild 139 | ''; 140 | 141 | doCheck = pkgs.stdenv.hostPlatform == pkgs.stdenv.buildPlatform; 142 | 143 | installPhase = '' 144 | runHook preInstall 145 | 146 | echo "Installing qaterialhotreloadapp version ${version} in ${cmakeConfigType} mode into $out" 147 | mkdir -p $out/${outFolder} 148 | cp -r ${outApp} $out/${outFolder} 149 | ${pkgs.lib.optionalString pkgs.stdenv.isDarwin '' 150 | mkdir -p $out/bin 151 | ln -s $out/${outFolder}/${outAppExe} $out/bin/qaterialhotreloadapp 152 | ''} 153 | 154 | runHook postInstall 155 | ''; 156 | 157 | doInstallCheck = doCheck; 158 | installCheckPhase = pkgs.lib.optionalString doInstallCheck '' 159 | runHook preInstallCheck 160 | 161 | echo "Run shell hook" 162 | ${shellHook} 163 | 164 | export QT_QPA_PLATFORM=offscreen 165 | $out/${outFolder}/${outAppExe} --help 166 | 167 | runHook postInstallCheck 168 | ''; 169 | }; 170 | 171 | qaterialHotReloadAppGlHost = 172 | if pkgs.stdenv.isLinux then 173 | (pkgs.stdenv.mkDerivation { 174 | pname = "qaterialHotReloadAppGlHost"; 175 | inherit version; 176 | 177 | dontUnpack = true; 178 | 179 | installPhase = '' 180 | mkdir -p $out/bin 181 | cat > $out/bin/qaterialHotReloadAppGlHost < 2 | # SPDX-License-Identifier: MIT 3 | { file, prefix }: 4 | let 5 | versionFileContent = builtins.readFile file; 6 | versionMajorParse = builtins.match ".*set\\(${prefix}_VERSION_MAJOR ([0-9]+)\\).*" versionFileContent; 7 | versionMajor = builtins.elemAt versionMajorParse 0; 8 | versionMinorParse = builtins.match ".*set\\(${prefix}_VERSION_MINOR ([0-9]+)\\).*" versionFileContent; 9 | versionMinor = builtins.elemAt versionMinorParse 0; 10 | versionPatchParse = builtins.match ".*set\\(${prefix}_VERSION_PATCH ([0-9]+)\\).*" versionFileContent; 11 | versionPatch = builtins.elemAt versionPatchParse 0; 12 | in 13 | "${versionMajor}.${versionMinor}.${versionPatch}" 14 | -------------------------------------------------------------------------------- /platforms/Deploy.cmake: -------------------------------------------------------------------------------- 1 | MESSAGE(STATUS "Platform deploy to ${CMAKE_SYSTEM_NAME}") 2 | 3 | set(QATERIALHOTRELOAD_PLATFORMS_DIR ${PROJECT_SOURCE_DIR}/platforms) 4 | 5 | # ──── WINDOWS ──── 6 | 7 | if(${CMAKE_SYSTEM_NAME} STREQUAL "Windows") 8 | 9 | if(NOT QATERIALHOTRELOAD_BUILD_SHARED) 10 | 11 | include(${PROJECT_SOURCE_DIR}/cmake/FetchQtWindowsCMake.cmake) 12 | 13 | add_qt_windows_exe(QaterialHotReloadApp 14 | NAME "Qaterial HotReload" 15 | PUBLISHER "Qaterial" 16 | PRODUCT_URL "https://github.com/OlivierLdff/QaterialHotReload" 17 | PACKAGE "com.qaterial.hotreload" 18 | ICON ${QATERIALHOTRELOAD_PLATFORMS_DIR}/windows/icon.ico 19 | ICON_RC ${QATERIALHOTRELOAD_PLATFORMS_DIR}/windows/icon.rc 20 | QML_DIR ${PROJECT_SOURCE_DIR}/qml 21 | NO_TRANSLATIONS 22 | NO_OPENGL_SW 23 | VERBOSE_LEVEL_DEPLOY 1 24 | VERBOSE_INSTALLER 25 | ) 26 | 27 | if(MSVC) 28 | set_property(DIRECTORY ${PROJECT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT QaterialHotReloadApp) 29 | endif() 30 | 31 | endif() 32 | endif() 33 | 34 | # ──── LINUX ──── 35 | 36 | if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux" AND QATERIALHOTRELOAD_ENABLE_APPIMAGE) 37 | 38 | if(NOT QATERIALHOTRELOAD_BUILD_SHARED) 39 | 40 | include(${PROJECT_SOURCE_DIR}/cmake/FetchQtLinuxCMake.cmake) 41 | 42 | if(NOT QATERIALHOTRELOAD_IGNORE_ENV) 43 | set(QATERIALHOTRELOAD_ALLOW_ENVIRONMENT_VARIABLE "ALLOW_ENVIRONMENT_VARIABLE") 44 | endif() 45 | 46 | add_qt_linux_appimage(QaterialHotReloadApp 47 | APP_DIR ${QATERIALHOTRELOAD_PLATFORMS_DIR}/linux/AppDir 48 | QML_DIR ${PROJECT_SOURCE_DIR}/qml 49 | NO_TRANSLATIONS 50 | ${QATERIALHOTRELOAD_ALLOW_ENVIRONMENT_VARIABLE} 51 | EXTRA_PLUGINS 52 | "platformthemes/libqgtk3.so" 53 | "renderers/libopenglrenderer.so" 54 | VERBOSE_LEVEL 1 55 | ) 56 | 57 | endif() 58 | 59 | endif() 60 | 61 | # ──── MACOS ──── 62 | 63 | if(${CMAKE_SYSTEM_NAME} STREQUAL "Darwin" AND QATERIALHOTRELOAD_ENABLE_DMG) 64 | 65 | if(NOT QATERIALHOTRELOAD_BUILD_SHARED) 66 | 67 | include(${PROJECT_SOURCE_DIR}/cmake/FetchQtMacCMake.cmake) 68 | 69 | # Deeplabel isn't compatible with mac app store due to disable sandbox mode. 70 | add_qt_mac_app(QaterialHotReloadApp 71 | NAME "Qaterial HotReload" 72 | BUNDLE_IDENTIFIER "com.qaterial.hotreload" 73 | LONG_VERSION ${QATERIALHOTRELOAD_VERSION}.${QATERIALHOTRELOAD_VERSION_TAG} 74 | COPYRIGHT "Copyright OlivierLdff 2020" 75 | APPLICATION_CATEGORY_TYPE "public.app-category.developer-tools" 76 | QML_DIR ${PROJECT_SOURCE_DIR}/qml 77 | RESOURCES 78 | "${QATERIALHOTRELOAD_PLATFORMS_DIR}/macos/Assets.xcassets" 79 | CUSTOM_ENTITLEMENTS "${QATERIALHOTRELOAD_PLATFORMS_DIR}/macos/Entitlements.entitlements" 80 | CUSTOM_PLIST "${QATERIALHOTRELOAD_PLATFORMS_DIR}/macos/Info.plist.in" 81 | DMG 82 | VERBOSE 83 | VERBOSE_LEVEL 3 84 | ) 85 | 86 | endif() 87 | 88 | endif() 89 | -------------------------------------------------------------------------------- /platforms/fire.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:c6dea3ba19a3201bc71aedff4afcaf64a21f07a0e27c176f5468ca4e631d0b91 3 | size 25984 4 | -------------------------------------------------------------------------------- /platforms/fire.svg: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:5aeda43f63c342faa9d2e787d1d6bccc2be55e22a1a107e3351f5db4a90d1cce 3 | size 1276 4 | -------------------------------------------------------------------------------- /platforms/linux/AppDir/usr/share/applications/QaterialHotReloadApp.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Type=Application 3 | Name=QaterialHotReloadApp 4 | Comment=Qml HotReload software. 5 | Exec=QaterialHotReloadApp 6 | Icon=QaterialHotReloadApp 7 | Categories=Development; 8 | -------------------------------------------------------------------------------- /platforms/linux/AppDir/usr/share/icons/hicolor/256x256/apps/QaterialHotReloadApp.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:e4eb341090f6a367e2d8abbba5b697fa70fc35caa97d864af69f07a0e94c975e 3 | size 8611 4 | -------------------------------------------------------------------------------- /platforms/linux/AppDir/usr/share/icons/hicolor/scalable/apps/QaterialHotReloadApp.svg: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:5aeda43f63c342faa9d2e787d1d6bccc2be55e22a1a107e3351f5db4a90d1cce 3 | size 1276 4 | -------------------------------------------------------------------------------- /platforms/macos/Assets.xcassets/AppIcon.appiconset/1024.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:88ce4f8f77f54c212f2dc07a902e84ab9592ef691fbb08c26917bef91ba8b3c9 3 | size 56268 4 | -------------------------------------------------------------------------------- /platforms/macos/Assets.xcassets/AppIcon.appiconset/128.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:ec1be964d2234271bb6fdf6a421991c9f9e3dd5034ae33df2b8d334f1cbf27e5 3 | size 4285 4 | -------------------------------------------------------------------------------- /platforms/macos/Assets.xcassets/AppIcon.appiconset/16.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:795e3b12f3db8332723cec1541a8d6db7c47e1f45086f1f9a238a2e313c9adc8 3 | size 489 4 | -------------------------------------------------------------------------------- /platforms/macos/Assets.xcassets/AppIcon.appiconset/256.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:18f8a90e21358e20824fcd469993c0100d84038ea713250a4e86180977147468 3 | size 9501 4 | -------------------------------------------------------------------------------- /platforms/macos/Assets.xcassets/AppIcon.appiconset/32.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:ef23b44fe9f0374d15354ef0b586f2fb7a9ab783bd1356092f6980c6515a27d0 3 | size 946 4 | -------------------------------------------------------------------------------- /platforms/macos/Assets.xcassets/AppIcon.appiconset/512.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:61d80e27bc54f1c97cd76c511e36b1957524d5c8f6674df4322232e77211b9f5 3 | size 22064 4 | -------------------------------------------------------------------------------- /platforms/macos/Assets.xcassets/AppIcon.appiconset/64.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:a63af6c17ce1c7bf63d889dd09ffff32a20cb4ad2c34b7537590495c5993c61b 3 | size 1987 4 | -------------------------------------------------------------------------------- /platforms/macos/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | {"images":[{"size":"128x128","expected-size":"128","filename":"128.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"1x"},{"size":"256x256","expected-size":"256","filename":"256.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"1x"},{"size":"128x128","expected-size":"256","filename":"256.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"2x"},{"size":"256x256","expected-size":"512","filename":"512.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"2x"},{"size":"32x32","expected-size":"32","filename":"32.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"1x"},{"size":"512x512","expected-size":"512","filename":"512.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"1x"},{"size":"16x16","expected-size":"16","filename":"16.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"1x"},{"size":"16x16","expected-size":"32","filename":"32.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"2x"},{"size":"32x32","expected-size":"64","filename":"64.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"2x"},{"size":"512x512","expected-size":"1024","filename":"1024.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"2x"}]} -------------------------------------------------------------------------------- /platforms/macos/Entitlements.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | com.apple.security.app-sandbox 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /platforms/macos/Info.plist.in: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | English 7 | 8 | 9 | CFBundleExecutable 10 | ${MACOSX_BUNDLE_EXECUTABLE_NAME} 11 | 12 | 13 | CFBundleGetInfoString 14 | ${MACOSX_BUNDLE_INFO_STRING} 15 | 16 | 17 | CFBundleIdentifier 18 | ${MACOSX_BUNDLE_GUI_IDENTIFIER} 19 | 20 | CFBundleInfoDictionaryVersion 21 | 6.0 22 | 23 | 24 | CFBundleLongVersionString 25 | ${MACOSX_BUNDLE_LONG_VERSION_STRING} 26 | 27 | 28 | CFBundleName 29 | ${MACOSX_BUNDLE_BUNDLE_NAME} 30 | 31 | CFBundlePackageType 32 | APPL 33 | 34 | 35 | CFBundleShortVersionString 36 | ${MACOSX_BUNDLE_SHORT_VERSION_STRING} 37 | 38 | CFBundleSignature 39 | ???? 40 | 41 | 42 | CFBundleVersion 43 | ${MACOSX_BUNDLE_BUNDLE_VERSION} 44 | 45 | CSResourcesFileMapped 46 | 47 | 48 | 49 | NSHumanReadableCopyright 50 | ${MACOSX_BUNDLE_COPYRIGHT} 51 | 52 | LSApplicationCategoryType 53 | ${QT_MAC_APPLICATION_CATEGORY_TYPE} 54 | 55 | ${QT_MAC_ITS_ENCRYPTION_KEYS} 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /platforms/windows/icon.ico: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:2f0f5b09c95562706b64cfbca87059c83676785fa07452cd165546332fd9b8f4 3 | size 12074 4 | -------------------------------------------------------------------------------- /platforms/windows/icon.rc: -------------------------------------------------------------------------------- 1 | IDI_ICON1 ICON DISCARDABLE "icon.ico" -------------------------------------------------------------------------------- /qml/Qaterial/HotReload/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright 2019-2020 Olivier Le Doeuff 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation 4 | # files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, 5 | # modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software 6 | # is furnished to do so, subject to the following conditions: 7 | # 8 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 9 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 10 | # DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE 11 | # OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 12 | 13 | qt_add_library(${QATERIALHOTRELOAD_LIB}Ui STATIC ${QATERIALHOTRELOAD_QML_RES}) 14 | add_library(Qaterial::HotReload::Ui ALIAS ${QATERIALHOTRELOAD_LIB}Ui) 15 | 16 | set(SINGLETON_FILES 17 | Images.qml 18 | ) 19 | 20 | set_source_files_properties(${SINGLETON_FILES} PROPERTIES QT_QML_SINGLETON_TYPE TRUE) 21 | 22 | qt_add_qml_module(${QATERIALHOTRELOAD_LIB}Ui 23 | URI "Qaterial.HotReload" 24 | RESOURCE_PREFIX "/" 25 | QML_FILES 26 | HotReload.qml 27 | HotReloadWindow.qml 28 | IconsMenu.qml 29 | Images.qml 30 | ImportPathMenu.qml 31 | Main.qml 32 | MaterialPaletteMenu.qml 33 | SplashScreenApplication.qml 34 | SplashScreenWindow.qml 35 | StatusView.qml 36 | TypoMenu.qml 37 | RESOURCES 38 | Images/code.svg 39 | Images/icon.svg 40 | Images/material-icons-light.svg 41 | Images/material-palette.png 42 | Images/qaterial-hotreload-black.png 43 | Images/qaterial-hotreload-white.png 44 | OUTPUT_TARGETS QATERIAL_HR_QML_TARGETS 45 | NO_PLUGIN 46 | ) 47 | 48 | target_link_libraries(${QATERIALHOTRELOAD_LIB}Ui PRIVATE 49 | Qt::Core 50 | Qt::Gui 51 | Qt::Svg 52 | Qt::Xml 53 | Qt::Qml 54 | Qt::Quick 55 | Qt::QuickControls2 56 | ) 57 | 58 | if(QATERIALHOTRELOAD_FOLDER_PREFIX) 59 | set_target_properties(${QATERIALHOTRELOAD_LIB}Ui PROPERTIES FOLDER ${QATERIALHOTRELOAD_FOLDER_PREFIX}) 60 | endif() 61 | -------------------------------------------------------------------------------- /qml/Qaterial/HotReload/HotReload.qml: -------------------------------------------------------------------------------- 1 | // Copyright Olivier Le Doeuff 2020 (C) 2 | 3 | import QtCore 4 | import QtQml 5 | 6 | import QtQuick 7 | import QtQuick.Layouts 8 | import QtQuick.Controls 9 | 10 | import Qaterial as Qaterial 11 | import Qaterial.HotReload as HR 12 | 13 | Qaterial.Page 14 | { 15 | id: root 16 | 17 | property string currentFolderPath 18 | property string currentFilePath 19 | property string currentFileUrl 20 | property string currentFileName: currentFilePath.substring(currentFilePath.lastIndexOf('/') + 1) 21 | 22 | property bool showFolderExplorer: true 23 | 24 | property 25 | var currentImportPath: [] 26 | 27 | property string errorString 28 | 29 | property int theme: Qaterial.Style.theme 30 | 31 | signal newObjectLoaded() 32 | 33 | implicitWidth: 1280 34 | implicitHeight: 600 35 | 36 | function loadFileInLoader(source) 37 | { 38 | Qaterial.DialogManager.close() 39 | loader.create(source) 40 | } 41 | 42 | function reload() 43 | { 44 | root.loadFileInLoader(currentFileUrl) 45 | } 46 | 47 | function loadFile(path) 48 | { 49 | Qaterial.HotReload.unWatchFile(root.currentFilePath) 50 | currentFileUrl = `file:/${path}` 51 | currentFilePath = path 52 | Qaterial.HotReload.watchFile(root.currentFilePath) 53 | 54 | loadFileInLoader(root.currentFileUrl) 55 | } 56 | 57 | Settings 58 | { 59 | id: settings 60 | 61 | category: "General" 62 | 63 | property alias currentFolderPath: root.currentFolderPath 64 | property alias currentFilePath: root.currentFilePath 65 | property alias currentFileUrl: root.currentFileUrl 66 | property alias currentImportPath: root.currentImportPath 67 | 68 | property alias showFolderExplorer: root.showFolderExplorer 69 | property 70 | var folderSplitView 71 | 72 | property alias formatHorizontalAlignCenter: formatHorizontalAlignCenter.checked 73 | property alias formatVerticalAlignCenter: formatVerticalAlignCenter.checked 74 | property alias formatHorizontalAlignLeft: formatHorizontalAlignLeft.checked 75 | property alias formatHorizontalAlignRight: formatHorizontalAlignRight.checked 76 | property alias formatVerticalAlignBottom: formatVerticalAlignBottom.checked 77 | property alias formatVerticalAlignTop: formatVerticalAlignTop.checked 78 | property alias fullScreen: fullScreen.checked 79 | 80 | property alias theme: root.theme 81 | } 82 | 83 | Shortcut 84 | { 85 | sequence: "F5" 86 | onActivated: () => root.reload() 87 | } 88 | 89 | Shortcut 90 | { 91 | sequence: "Ctrl+O" 92 | onActivated: () => root.openFilePicker() 93 | } 94 | 95 | function openFilePicker() 96 | { 97 | Qaterial.DialogManager.showOpenFileDialog( 98 | { 99 | title: "Please choose a file", 100 | nameFilters: ["Qml Files (*.qml)"], 101 | onAccepted: function(path) 102 | { 103 | root.loadFile(path) 104 | } 105 | }) 106 | } 107 | 108 | function openFolderPicker() 109 | { 110 | Qaterial.DialogManager.showFolderDialog( 111 | { 112 | title: "Please choose a folder", 113 | onAccepted: function(path) 114 | { 115 | root.currentFolderPath = path 116 | root.showFolderExplorer = true 117 | } 118 | }) 119 | } 120 | 121 | Connections 122 | { 123 | target: Qaterial.HotReload 124 | function onWatchedFileChanged() { root.reload() } 125 | } 126 | 127 | header: Qaterial.ToolBar 128 | { 129 | implicitHeight: flow.implicitHeight 130 | Flow 131 | { 132 | id: flow 133 | width: parent.width - menuButton.implicitWidth 134 | Qaterial.SquareButton 135 | { 136 | ToolTip.visible: hovered || pressed 137 | ToolTip.text: "Open File to Hot Reload" 138 | icon.source: Qaterial.Icons.fileOutline 139 | useSecondaryColor: true 140 | 141 | onClicked: () => root.openFilePicker() 142 | } 143 | 144 | Qaterial.SquareButton 145 | { 146 | ToolTip.visible: hovered || pressed 147 | ToolTip.text: "Open Folder to Hot Reload" 148 | icon.source: Qaterial.Icons.folderOutline 149 | useSecondaryColor: true 150 | 151 | onClicked: () => root.openFolderPicker() 152 | } 153 | 154 | Qaterial.SquareButton 155 | { 156 | ToolTip.visible: hovered || pressed 157 | ToolTip.text: checked ? "Hide folder explorer" : "Show folder explorer" 158 | icon.source: Qaterial.Icons.pageLayoutSidebarLeft 159 | useSecondaryColor: true 160 | 161 | checked: root.showFolderExplorer 162 | 163 | onReleased: () => root.showFolderExplorer = checked 164 | } 165 | 166 | Qaterial.ToolSeparator {} 167 | 168 | Qaterial.SquareButton 169 | { 170 | ToolTip.visible: hovered || pressed 171 | ToolTip.text: "Reload (F5)" 172 | icon.source: Qaterial.Icons.sync 173 | useSecondaryColor: true 174 | 175 | onClicked: () => root.reload() 176 | } 177 | 178 | Qaterial.SquareButton 179 | { 180 | ToolTip.visible: hovered || pressed 181 | ToolTip.text: "Qml Import Path" 182 | icon.source: Qaterial.Icons.fileTree 183 | useSecondaryColor: true 184 | 185 | onClicked: function() 186 | { 187 | if(!_importPathMenu.visible) 188 | _importPathMenu.open() 189 | } 190 | 191 | ImportPathMenu 192 | { 193 | id: _importPathMenu 194 | y: parent.height 195 | 196 | model: Qaterial.HotReload.importPaths 197 | 198 | function setImportPathsAndReload(paths) 199 | { 200 | Qaterial.HotReload.importPaths = paths 201 | root.currentImportPath = paths 202 | root.reload() 203 | } 204 | 205 | onResetPathEntries: function() 206 | { 207 | Qaterial.HotReload.importPaths = undefined 208 | console.log(`Reset Path Entries to ${Qaterial.HotReload.importPaths}`) 209 | root.currentImportPath = Qaterial.HotReload.importPaths.toString() 210 | .split(',') 211 | root.reload() 212 | } 213 | 214 | onAddPathEntry: function(index) 215 | { 216 | Qaterial.DialogManager.showTextFieldDialog( 217 | { 218 | context: root, 219 | width: 800, 220 | title: "Enter Qml import path", 221 | textTitle: "Path", 222 | validator: null, 223 | inputMethodHints: Qt.ImhSensitiveData, 224 | standardButtons: Dialog.Ok | Dialog.Cancel, 225 | onAccepted: function(text, acceptable) 226 | { 227 | if(index <= -1) 228 | index = Qaterial.HotReload.importPaths.length 229 | console.log(`Append Path ${text} at ${index}`) 230 | let tempPaths = Qaterial.HotReload.importPaths.toString() 231 | .split(',') 232 | tempPaths.splice(index, 0, text) 233 | _importPathMenu.setImportPathsAndReload(tempPaths) 234 | }, 235 | }) 236 | } 237 | 238 | onEditPathEntry: function(index) 239 | { 240 | Qaterial.DialogManager.showTextFieldDialog( 241 | { 242 | context: root, 243 | width: 800, 244 | title: "Edit Qml import path", 245 | textTitle: "Path", 246 | text: Qaterial.HotReload.importPaths[index], 247 | validator: null, 248 | inputMethodHints: Qt.ImhSensitiveData, 249 | standardButtons: Dialog.Ok | Dialog.Cancel, 250 | onAccepted: function(text, acceptable) 251 | { 252 | console.log(`Edit Path ${index} to ${text}`) 253 | let tempPaths = Qaterial.HotReload.importPaths.toString() 254 | .split(',') 255 | tempPaths.splice(index, 1, text) 256 | _importPathMenu.setImportPathsAndReload(tempPaths) 257 | }, 258 | }) 259 | } 260 | 261 | onDeletePathEntry: function(index) 262 | { 263 | if(index >= 0 && index < Qaterial.HotReload.importPaths.length) 264 | { 265 | Qaterial.DialogManager.showDialog( 266 | { 267 | context: root, 268 | width: 500, 269 | title: "Warning", 270 | text: `Are you sure to delete "${Qaterial.HotReload.importPaths[index]}"`, 271 | iconSource: Qaterial.Icons.alertOutline, 272 | standardButtons: Dialog.Ok | Dialog.Cancel, 273 | onAccepted: function() 274 | { 275 | console.log(`Remove Path ${Qaterial.HotReload.importPaths[index]}`) 276 | let tempPaths = Qaterial.HotReload.importPaths.toString() 277 | .split(',') 278 | tempPaths.splice(index, 1) 279 | _importPathMenu.setImportPathsAndReload(tempPaths) 280 | } 281 | }) 282 | } 283 | } 284 | 285 | onMovePathUp: function(index) 286 | { 287 | let tempPaths = Qaterial.HotReload.importPaths.toString() 288 | .split(',') 289 | tempPaths.splice(index, 0, tempPaths.splice(index - 1, 1)[0]) 290 | _importPathMenu.setImportPathsAndReload(tempPaths) 291 | } 292 | 293 | onMovePathDown: function(index) 294 | { 295 | let tempPaths = Qaterial.HotReload.importPaths.toString() 296 | .split(',') 297 | tempPaths.splice(index, 0, tempPaths.splice(index + 1, 1)[0]) 298 | _importPathMenu.setImportPathsAndReload(tempPaths) 299 | } 300 | } 301 | } 302 | 303 | Qaterial.ToolButton 304 | { 305 | id: formatHorizontalAlignCenter 306 | 307 | enabled: !fullScreen.checked && !formatHorizontalAlignLeft.checked && !formatHorizontalAlignRight.checked 308 | icon.source: Qaterial.Icons.formatHorizontalAlignCenter 309 | 310 | ToolTip.visible: hovered || pressed 311 | ToolTip.text: "Align Horizontal Center" 312 | 313 | onClicked: () => root.reload() 314 | } 315 | 316 | Qaterial.ToolButton 317 | { 318 | id: formatVerticalAlignCenter 319 | 320 | enabled: !fullScreen.checked && !formatVerticalAlignBottom.checked && !formatVerticalAlignTop.checked 321 | icon.source: Qaterial.Icons.formatVerticalAlignCenter 322 | 323 | ToolTip.visible: hovered || pressed 324 | ToolTip.text: "Align Vertical Center" 325 | 326 | onClicked: () => root.reload() 327 | } 328 | 329 | Qaterial.ToolSeparator {} 330 | 331 | Qaterial.ToolButton 332 | { 333 | id: formatHorizontalAlignLeft 334 | 335 | enabled: !fullScreen.checked && !formatHorizontalAlignCenter.checked 336 | icon.source: Qaterial.Icons.formatHorizontalAlignLeft 337 | 338 | ToolTip.visible: hovered || pressed 339 | ToolTip.text: "Align Left" 340 | 341 | onClicked: () => root.reload() 342 | } 343 | 344 | Qaterial.ToolButton 345 | { 346 | id: formatHorizontalAlignRight 347 | 348 | enabled: !fullScreen.checked && !formatHorizontalAlignCenter.checked 349 | icon.source: Qaterial.Icons.formatHorizontalAlignRight 350 | 351 | ToolTip.visible: hovered || pressed 352 | ToolTip.text: "Align Right" 353 | 354 | onClicked: () => root.reload() 355 | } 356 | 357 | Qaterial.ToolButton 358 | { 359 | id: formatVerticalAlignBottom 360 | 361 | enabled: !fullScreen.checked && !formatVerticalAlignCenter.checked 362 | icon.source: Qaterial.Icons.formatVerticalAlignBottom 363 | 364 | ToolTip.visible: hovered || pressed 365 | ToolTip.text: "Align Bottom" 366 | 367 | onClicked: () => root.reload() 368 | } 369 | 370 | Qaterial.ToolButton 371 | { 372 | id: formatVerticalAlignTop 373 | 374 | enabled: !fullScreen.checked && !formatVerticalAlignCenter.checked 375 | icon.source: Qaterial.Icons.formatVerticalAlignTop 376 | 377 | ToolTip.visible: hovered || pressed 378 | ToolTip.text: "Align Top" 379 | 380 | onClicked: () => root.reload() 381 | } 382 | 383 | Qaterial.ToolSeparator {} 384 | 385 | Qaterial.ToolButton 386 | { 387 | id: fullScreen 388 | 389 | enabled: !formatHorizontalAlignCenter.checked && 390 | !formatVerticalAlignCenter.checked && 391 | !formatHorizontalAlignLeft.checked && 392 | !formatHorizontalAlignRight.checked && 393 | !formatVerticalAlignBottom.checked && 394 | !formatVerticalAlignTop.checked 395 | icon.source: checked ? Qaterial.Icons.fullscreen : Qaterial.Icons.fullscreenExit 396 | 397 | ToolTip.visible: hovered || pressed 398 | ToolTip.text: checked ? "Fullscreen" : "Fullscreen Exit" 399 | 400 | onClicked: () => root.reload() 401 | } 402 | 403 | Qaterial.ToolSeparator {} 404 | 405 | Qaterial.SquareButton 406 | { 407 | readonly property bool lightTheme: root.theme === Qaterial.Style.Theme.Light 408 | icon.source: lightTheme ? Qaterial.Icons.weatherSunny : Qaterial.Icons.moonWaningCrescent 409 | useSecondaryColor: true 410 | ToolTip.visible: hovered || pressed 411 | ToolTip.text: lightTheme ? "Theme Light" : "Theme Dark" 412 | 413 | onClicked: function() 414 | { 415 | theme = lightTheme ? Qaterial.Style.Theme.Dark : Qaterial.Style.Theme.Light 416 | Qaterial.Style.theme = theme 417 | } 418 | } 419 | 420 | Qaterial.ToolSeparator {} 421 | 422 | Qaterial.SquareButton 423 | { 424 | icon.source: Qaterial.Icons.formatLetterCase 425 | 426 | onClicked: function() 427 | { 428 | if(!_typoMenu.visible) 429 | _typoMenu.open() 430 | } 431 | 432 | TypoMenu 433 | { 434 | id: _typoMenu 435 | y: parent.height 436 | } 437 | 438 | ToolTip.visible: hovered || pressed 439 | ToolTip.text: "Typography" 440 | } 441 | 442 | Qaterial.SquareButton 443 | { 444 | contentItem: Item 445 | { 446 | Image 447 | { 448 | anchors.centerIn: parent 449 | source: HR.Images.materialIconsLight 450 | sourceSize.width: Qaterial.Style.toolButton.iconWidth 451 | sourceSize.height: Qaterial.Style.toolButton.iconWidth 452 | } 453 | } 454 | 455 | onClicked: function() 456 | { 457 | if(!_iconsMenu.visible) 458 | _iconsMenu.open() 459 | } 460 | 461 | IconsMenu 462 | { 463 | id: _iconsMenu 464 | y: parent.height 465 | } 466 | 467 | ToolTip.visible: hovered || pressed 468 | ToolTip.text: "Icons Explorer" 469 | } 470 | 471 | Qaterial.SquareButton 472 | { 473 | contentItem: Item 474 | { 475 | Image 476 | { 477 | anchors.centerIn: parent 478 | source: HR.Images.materialPalette 479 | } 480 | } 481 | 482 | onClicked: function() 483 | { 484 | if(!_paletteMenu.visible) 485 | _paletteMenu.open() 486 | } 487 | 488 | MaterialPaletteMenu 489 | { 490 | id: _paletteMenu 491 | y: parent.height 492 | } 493 | 494 | ToolTip.visible: hovered || pressed 495 | ToolTip.text: "Material Color Palette" 496 | } 497 | 498 | } // Flow 499 | 500 | Qaterial.AppBarButton 501 | { 502 | id: menuButton 503 | x: parent.width - width 504 | icon.source: Qaterial.Icons.dotsVertical 505 | 506 | onClicked: function() 507 | { 508 | if(!menu.visible) 509 | menu.open() 510 | } 511 | 512 | Qaterial.Menu 513 | { 514 | id: menu 515 | y: parent.height 516 | 517 | Qaterial.MenuItem 518 | { 519 | icon.source: Qaterial.Icons.earth 520 | text: "Qaterial Online" 521 | onClicked: () => Qt.openUrlExternally("https://olivierldff.github.io/QaterialOnline/") 522 | } 523 | 524 | Qaterial.MenuItem 525 | { 526 | icon.source: Qaterial.Icons.helpCircle 527 | text: "Documentation" 528 | onClicked: () => Qt.openUrlExternally("https://olivierldff.github.io/Qaterial/") 529 | } 530 | 531 | Qaterial.MenuItem 532 | { 533 | icon.source: Qaterial.Icons.github 534 | text: "Qaterial on Github" 535 | onClicked: () => Qt.openUrlExternally("https://github.com/OlivierLDff/Qaterial") 536 | } 537 | } 538 | } 539 | } // ToolBar 540 | 541 | SplitView 542 | { 543 | anchors.fill: parent 544 | orientation: Qt.Vertical 545 | SplitView.fillWidth: true 546 | 547 | handleBackgroundColor: folderSplitView.handleBackgroundColor 548 | 549 | SplitView 550 | { 551 | id: folderSplitView 552 | orientation: Qt.Horizontal 553 | SplitView.fillHeight: true 554 | SplitView.minimumHeight: 100 555 | 556 | handleBackgroundColor: Qt.darker(Qaterial.Style.colorTheme.background0, 1.1) 557 | 558 | Rectangle 559 | { 560 | visible: root.showFolderExplorer 561 | color: Qaterial.Style.colorTheme.background1 562 | z: 10 563 | 564 | SplitView.preferredWidth: 200 565 | SplitView.minimumWidth: 0 566 | 567 | Qaterial.FolderTreeView 568 | { 569 | id: treeView 570 | anchors.fill: parent 571 | 572 | header: Qaterial.LabelButton 573 | { 574 | text: treeView.model.fileName 575 | padding: 8 576 | elide: Text.ElideRight 577 | width: treeView.width 578 | } 579 | 580 | ScrollIndicator.vertical: Qaterial.ScrollIndicator {} 581 | 582 | property QtObject selectedElement 583 | 584 | nameFilters: ["*.qml"] 585 | path: `file:${root.currentFolderPath}` 586 | 587 | itemDelegate: Qaterial.FolderTreeViewItem 588 | { 589 | id: control 590 | highlighted: model && model.filePath === root.currentFilePath 591 | onAccepted: function(path) 592 | { 593 | function urlToLocalFile(url) 594 | { 595 | let path = url.toString() 596 | const isWindows = Qt.platform.os === "windows" 597 | path = isWindows ? path.replace(/^(file:\/{3})/, "") : path.replace(/^(file:\/{2})/, "") 598 | return decodeURIComponent(path) 599 | } 600 | root.loadFile(urlToLocalFile(path)) 601 | } 602 | } 603 | } // TreeView 604 | } // Pane 605 | 606 | Item 607 | { 608 | id: loader 609 | width: parent.width 610 | property 611 | var loadedObject: null 612 | property url createUrl 613 | 614 | function create(url) 615 | { 616 | createUrl = url 617 | Qaterial.DialogManager.openBusyIndicator({ text: `Loading ${root.currentFileName}` }) 618 | createLaterTimer.start() 619 | } 620 | 621 | Timer 622 | { 623 | id: createLaterTimer 624 | interval: 230 625 | onTriggered: () => loader.createLater(loader.createUrl) 626 | } 627 | 628 | function assignAnchors() 629 | { 630 | if(loadedObject.anchors) 631 | { 632 | if(fullScreen.checked) 633 | loadedObject.anchors.fill = loader 634 | if(formatHorizontalAlignCenter.checked) 635 | loadedObject.anchors.horizontalCenter = loader.horizontalCenter 636 | if(formatVerticalAlignCenter.checked) 637 | loadedObject.anchors.verticalCenter = loader.verticalCenter 638 | if(formatHorizontalAlignLeft.checked) 639 | loadedObject.anchors.left = loader.left 640 | if(formatHorizontalAlignRight.checked) 641 | loadedObject.anchors.right = loader.right 642 | if(formatVerticalAlignBottom.checked) 643 | loadedObject.anchors.bottom = loader.bottom 644 | if(formatVerticalAlignTop.checked) 645 | loadedObject.anchors.top = loader.top 646 | } 647 | } 648 | 649 | function createLater(url) 650 | { 651 | console.log(`Load from ${url}`) 652 | 653 | // Destroy previous item 654 | if(loadedObject) 655 | { 656 | loadedObject.destroy() 657 | loadedObject = null 658 | } 659 | 660 | console.time("hotReload"); 661 | 662 | console.time("clearCache"); 663 | Qaterial.HotReload.clearCache() 664 | console.timeEnd("clearCache"); 665 | 666 | console.time("createComponent"); 667 | let component = Qt.createComponent(url) 668 | console.timeEnd("createComponent"); 669 | 670 | // This case can happen if no url is set, ie when app is in on-boarding screen 671 | if(!component) 672 | { 673 | Qaterial.DialogManager.close() 674 | return 675 | } 676 | 677 | console.time("incubateObject"); 678 | if(component.status === Component.Ready) 679 | { 680 | //loadedObject = component.createObject(loader) 681 | 682 | var incubator = component.incubateObject(loader, { visible: false }); 683 | if(incubator.status != Component.Ready) 684 | { 685 | incubator.onStatusChanged = function(status) 686 | { 687 | if(status == Component.Ready) 688 | { 689 | loadedObject = incubator.object 690 | assignAnchors() 691 | loadedObject.visible = true 692 | Qaterial.DialogManager.close() 693 | 694 | root.newObjectLoaded() 695 | } 696 | } 697 | } 698 | else 699 | { 700 | loadedObject = incubator.object 701 | assignAnchors() 702 | loadedObject.visible = true 703 | Qaterial.DialogManager.close() 704 | 705 | root.newObjectLoaded() 706 | } 707 | 708 | root.errorString = "" 709 | } 710 | else 711 | { 712 | root.errorString = component.errorString() 713 | console.exception(`Fail to load with error ${root.errorString}`) 714 | Qaterial.DialogManager.close() 715 | root.newObjectLoaded() 716 | } 717 | 718 | console.timeEnd("incubateObject"); 719 | console.timeEnd("hotReload"); 720 | } 721 | 722 | Column 723 | { 724 | anchors.centerIn: parent 725 | spacing: 16 726 | visible: !root.currentFilePath 727 | 728 | Image 729 | { 730 | anchors.horizontalCenter: parent.horizontalCenter 731 | width: 128 732 | height: 128 733 | source: HR.Images.code 734 | } 735 | 736 | Qaterial.Label 737 | { 738 | anchors.horizontalCenter: parent.horizontalCenter 739 | text: "Please Pick a qml file or folder to get started" 740 | } 741 | 742 | Row 743 | { 744 | anchors.horizontalCenter: parent.horizontalCenter 745 | 746 | Qaterial.RaisedButton 747 | { 748 | text: "Open File" 749 | icon.source: Qaterial.Icons.fileOutline 750 | 751 | onClicked: () => root.openFilePicker() 752 | } 753 | 754 | Qaterial.OutlineButton 755 | { 756 | text: "Open Folder" 757 | icon.source: Qaterial.Icons.folderOutline 758 | 759 | onClicked: () => root.openFolderPicker() 760 | } 761 | } 762 | } 763 | } 764 | } 765 | 766 | StatusView 767 | { 768 | id: _statusView 769 | anchors.bottom: parent.bottom 770 | width: parent.width 771 | SplitView.minimumHeight: 40 772 | 773 | errorString: root.errorString 774 | file: root.currentFileName 775 | 776 | Connections 777 | { 778 | target: Qaterial.HotReload 779 | function onNewLog(msg) 780 | { 781 | const color = function() 782 | { 783 | if(msg.includes("debug")) 784 | return Qaterial.Style.blue 785 | if(msg.includes("info")) 786 | return Qaterial.Style.green 787 | if(msg.includes("warning")) 788 | return Qaterial.Style.orange 789 | if(msg.includes("error")) 790 | return Qaterial.Style.red 791 | return Qaterial.Style.primaryTextColor() 792 | }(); 793 | 794 | _statusView.log(msg, color) 795 | } 796 | } 797 | 798 | function log(msg, color) 799 | { 800 | _statusView.append(`${msg}`) 801 | } 802 | } // StatusView 803 | } // SplitView 804 | 805 | Component.onCompleted: function() 806 | { 807 | console.log(`Load configuration from ${settings.location}`) 808 | folderSplitView.restoreState(settings.folderSplitView) 809 | Qaterial.Style.theme = root.theme 810 | 811 | if(Qaterial.HotReload.resetCurrentFile) 812 | { 813 | console.log('Reset current file path') 814 | currentFilePath = "" 815 | currentFileUrl = "" 816 | } 817 | 818 | if(Qaterial.HotReload.importPaths !== root.currentImportPath) 819 | { 820 | if(root.currentImportPath.length && !Qaterial.HotReload.resetImportPath) 821 | { 822 | console.log(`Set qml import path to Qaterial.HotReload.importPaths`) 823 | Qaterial.HotReload.importPaths = root.currentImportPath 824 | } 825 | else 826 | { 827 | root.currentImportPath = Qaterial.HotReload.importPaths 828 | } 829 | } 830 | 831 | if(root.currentFileUrl) 832 | { 833 | loader.create(root.currentFileUrl) 834 | Qaterial.HotReload.watchFile(root.currentFilePath) 835 | } 836 | } 837 | 838 | Component.onDestruction: function() 839 | { 840 | settings.folderSplitView = folderSplitView.saveState() 841 | } 842 | } // ApplicationWindow 843 | -------------------------------------------------------------------------------- /qml/Qaterial/HotReload/HotReloadWindow.qml: -------------------------------------------------------------------------------- 1 | // Copyright Olivier Le Doeuff 2020 (C) 2 | 3 | import QtQuick 2.0 4 | import Qaterial as Qaterial 5 | 6 | Qaterial.ApplicationWindow 7 | { 8 | id: window 9 | 10 | property alias currentFilePath: hr.currentFilePath 11 | 12 | signal newObjectLoaded() 13 | 14 | title: `Qaterial Hot Reload - ${currentFilePath}` 15 | 16 | width: 1280 17 | height: 600 18 | 19 | minimumWidth: 200 20 | minimumHeight: 200 21 | 22 | HotReload 23 | { 24 | id: hr 25 | anchors.fill: parent 26 | 27 | onNewObjectLoaded: window.newObjectLoaded() 28 | } 29 | 30 | Qaterial.WindowLayoutSave { name: "Reloader" } 31 | } 32 | -------------------------------------------------------------------------------- /qml/Qaterial/HotReload/IconsMenu.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.12 2 | import QtQuick.Controls 2.12 3 | import Qt.labs.folderlistmodel 4 | import Qaterial as Qaterial 5 | 6 | Qaterial.Menu 7 | { 8 | id: root 9 | 10 | implicitWidth: 320 11 | 12 | Qaterial.TextField 13 | { 14 | id: textField 15 | anchors.horizontalCenter: parent.horizontalCenter 16 | title: titleUp ? "Search" : "Search ..." 17 | leadingIconSource: Qaterial.Icons.magnify 18 | leadingIconInline: true 19 | focus: true 20 | 21 | onTextEdited: () => delaySearch.start() 22 | } // TextField 23 | 24 | GridView 25 | { 26 | id: grid 27 | clip: true 28 | 29 | width: parent.width 30 | height: 200 31 | 32 | cellWidth: 50 33 | cellHeight: 50 34 | 35 | ScrollBar.vertical: Qaterial.ScrollBar { x: 32;policy: ScrollBar.AlwaysOn } 36 | 37 | Timer 38 | { 39 | id: delaySearch 40 | interval: 100 41 | onTriggered: function() 42 | { 43 | const words = textField.text.split(' ') 44 | let filter1 = '' 45 | let filter2 = '*' 46 | let filter3 = '' 47 | words.forEach( 48 | function(word) 49 | { 50 | const lowerCaseWord = word.toLowerCase() 51 | filter1 += `${lowerCaseWord}*` 52 | filter2 += `${lowerCaseWord}*` 53 | filter3 += `*${lowerCaseWord}` 54 | }) 55 | //console.log(`filter: ${filter}`) 56 | folderModel.nameFilters = [`${filter1}.svg`, `${filter2}.svg`, `${filter3}.svg`] 57 | } 58 | } // Timer 59 | 60 | model: FolderListModel 61 | { 62 | id: folderModel 63 | folder: 'qrc:/Qaterial/Icons/' 64 | nameFilters: ['*.svg'] 65 | 66 | //property string iconSearch 67 | } // FolderListModel 68 | 69 | delegate: Qaterial.AppBarButton 70 | { 71 | id: icon 72 | 73 | readonly property 74 | var forbiddenKeywords: ['id', 'index', 'model', 'modelData', 'console', 'do', 'if', 'in', 'for', 'let', 'new', 75 | 'try', 'var', 'case', 'else', 'enum', 'eval', 'null', 'this', 'true', 'void', 'with', 'await', 'break', 76 | 'catch', 'class', 'const', 'false', 'super', 'throw', 'while', 'yield', 'delete', 'export', 'import', 77 | 'public', 'return', 'static', 'switch', 'typeof', 'default', 'extends', 'finally', 'package', 'private', 78 | 'continue', 'debugger', 'function', 'arguments', 'interface', 'protected', 'implements', 'instanceof' 79 | ] 80 | 81 | function fileNameToProperty(str) 82 | { 83 | // snake to camel 84 | str = str.replace(/([-_][a-z0-9])/g, 85 | (group) => group.toUpperCase() 86 | .replace('-', '') 87 | .replace('_', '') 88 | ); 89 | 90 | // remove extension 91 | str = str.replace(/\.[^/.]+$/, "") 92 | 93 | // append _ to forbidden keywords 94 | if(forbiddenKeywords.includes(str)) 95 | return `${str}_` 96 | return str 97 | } 98 | 99 | icon.source: `qrc:/Qaterial/Icons/${fileName}` 100 | 101 | onClicked: function() 102 | { 103 | const textToCopy = `Qaterial.Icons.${fileNameToProperty(fileName)}` 104 | Qaterial.Clipboard.text = textToCopy 105 | Qaterial.SnackbarManager.show(`Icon Name Copied! \n"${textToCopy}"`) 106 | } 107 | 108 | Qaterial.ToolTip 109 | { 110 | 111 | text: `${fileNameToProperty(fileName)}` 112 | delay: 50 113 | visible: icon.hovered 114 | } // ToolTip 115 | } // AppBarButton 116 | } // GridView 117 | } 118 | -------------------------------------------------------------------------------- /qml/Qaterial/HotReload/Images.qml: -------------------------------------------------------------------------------- 1 | // File auto generated with CMake qt_generate_qrc_alias_singleton. 2 | // Everything written here will be lost. 3 | 4 | pragma Singleton 5 | 6 | import QtQml 2.0 7 | 8 | QtObject 9 | { 10 | readonly property string code: 'qrc:/Qaterial/HotReload/Images/code.svg' 11 | readonly property string icon: 'qrc:/Qaterial/HotReload/Images/icon.svg' 12 | readonly property string materialIconsLight: 'qrc:/Qaterial/HotReload/Images/material-icons-light.svg' 13 | readonly property string materialPalette: 'qrc:/Qaterial/HotReload/Images/material-palette.png' 14 | readonly property string qaterialHotreloadBlack: 'qrc:/Qaterial/HotReload/Images/qaterial-hotreload-black.png' 15 | readonly property string qaterialHotreloadWhite: 'qrc:/Qaterial/HotReload/Images/qaterial-hotreload-white.png' 16 | } 17 | -------------------------------------------------------------------------------- /qml/Qaterial/HotReload/Images/code.svg: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:5f2281b778f995916a2b105494bf05fecff4ddf386e901921fc945232a06a0a9 3 | size 2205 4 | -------------------------------------------------------------------------------- /qml/Qaterial/HotReload/Images/icon.svg: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:5aeda43f63c342faa9d2e787d1d6bccc2be55e22a1a107e3351f5db4a90d1cce 3 | size 1276 4 | -------------------------------------------------------------------------------- /qml/Qaterial/HotReload/Images/material-icons-light.svg: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:f646d30533c241018506700b22b462b717170bc75cda7073815f55e63263a9d7 3 | size 341 4 | -------------------------------------------------------------------------------- /qml/Qaterial/HotReload/Images/material-palette.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:df6f6337cee13374b96a940716cc83b9ec6beed5ae03cf6d22991a87a790f1e4 3 | size 220 4 | -------------------------------------------------------------------------------- /qml/Qaterial/HotReload/Images/qaterial-hotreload-black.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:59de7ac37e412b3878ded7efa8124c3274cc29724b85d15fea0fde664eedbd0c 3 | size 51049 4 | -------------------------------------------------------------------------------- /qml/Qaterial/HotReload/Images/qaterial-hotreload-white.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:7593aeb9027bc7dd3b34991c462d358e33eea1da0e6f8a975f5c904eb4a758a8 3 | size 55018 4 | -------------------------------------------------------------------------------- /qml/Qaterial/HotReload/ImportPathMenu.qml: -------------------------------------------------------------------------------- 1 | import QtQml 2.12 2 | import QtQuick 2.12 3 | import QtQuick.Controls 2.12 4 | 5 | import Qaterial as Qaterial 6 | 7 | Qaterial.Menu 8 | { 9 | id: control 10 | 11 | signal resetPathEntries() 12 | signal addPathEntry(int index) 13 | signal editPathEntry(int index) 14 | signal deletePathEntry(int index) 15 | signal movePathUp(int index) 16 | signal movePathDown(int index) 17 | 18 | property 19 | var model: [] 20 | 21 | implicitWidth: 600 22 | bottomPadding: 0 23 | 24 | contentItem: Column 25 | { 26 | width: root.width 27 | 28 | Rectangle 29 | { 30 | color: Qaterial.Style.dialogColor 31 | width: control.width 32 | implicitHeight: title.implicitHeight 33 | z: 1 34 | 35 | Qaterial.LabelHeadline6 36 | { 37 | id: title 38 | text: "Qml Import Paths" 39 | width: parent.width 40 | elide: Text.ElideRight 41 | leftPadding: 16 42 | bottomPadding: 8 43 | } 44 | 45 | Qaterial.HorizontalLineSeparator 46 | { 47 | anchors.horizontalCenter: parent.horizontalCenter 48 | implicitWidth: control.width 49 | y: parent.height - 1 50 | } // HorizontalLineSeparator 51 | } // Header 52 | 53 | Item 54 | { 55 | width: control.width 56 | implicitHeight: Math.max(listView.implicitHeight, modifierColumn.implicitHeight) 57 | 58 | ListView 59 | { 60 | id: listView 61 | currentIndex: -1 62 | implicitHeight: Math.min(300, contentHeight) 63 | width: parent.width - modifierColumn.width 64 | interactive: height < contentHeight 65 | 66 | ScrollBar.vertical: Qaterial.ScrollBar { policy: ScrollBar.AsNeeded;width: 12;active: true } 67 | 68 | model: control.model 69 | 70 | delegate: Qaterial.ItemDelegate 71 | { 72 | id: pathDelegate 73 | width: listView.width 74 | 75 | topPadding: 4 76 | bottomPadding: 4 77 | rightPadding: 20 78 | 79 | highlighted: listView.currentIndex === index 80 | 81 | onDoubleClicked: () => control.editPathEntry(index) 82 | 83 | onClicked: function() 84 | { 85 | listView.currentIndex = listView.currentIndex === index ? -1 : index 86 | } 87 | 88 | contentItem: Qaterial.Label 89 | { 90 | id: pathLabel 91 | text: modelData 92 | elide: Text.ElideRight 93 | Binding on color 94 | { 95 | when: pathDelegate.highlighted 96 | value: Qaterial.Style.accentColor 97 | } 98 | } 99 | 100 | Qaterial.ToolTip 101 | { 102 | text: modelData 103 | visible: pathLabel.truncated && (pathDelegate.hovered || pathDelegate.pressed) 104 | position: Qaterial.Style.Position.Right 105 | } 106 | 107 | background: Rectangle 108 | { 109 | color: Qt.darker(Qaterial.Style.dialogColor, (index % 2 === 0) ? 1 : 1.2) 110 | Qaterial.Ripple 111 | { 112 | id: _ripple 113 | width: parent.width 114 | height: parent.height 115 | 116 | pressed: pathDelegate.pressed 117 | active: pathDelegate.down || pathDelegate.visualFocus || pathDelegate.hovered 118 | anchor: pathDelegate 119 | 120 | clip: visible 121 | color: Qaterial.Style.rippleColor(Qaterial.Style.RippleBackground.Background) 122 | } // Ripple 123 | } 124 | } 125 | } // ListView 126 | 127 | Qaterial.VerticalLineSeparator 128 | { 129 | x: modifierColumn.x 130 | height: parent.height 131 | } 132 | 133 | Column 134 | { 135 | id: modifierColumn 136 | anchors.right: parent.right 137 | spacing: 0 138 | 139 | Qaterial.SquareButton 140 | { 141 | icon.source: Qaterial.Icons.plus 142 | topInset: 1 143 | bottomInset: 1 144 | 145 | onClicked: function() 146 | { 147 | const index = listView.currentIndex 148 | control.addPathEntry(index) 149 | listView.currentIndex = -1 150 | } 151 | 152 | Qaterial.ToolTip 153 | { 154 | text: `Add Path Entry` 155 | visible: parent.hovered || parent.pressed 156 | position: Qaterial.Style.Position.BottomStart 157 | } 158 | } // SquareButton 159 | 160 | Qaterial.SquareButton 161 | { 162 | icon.source: Qaterial.Icons.minus 163 | topInset: 1 164 | bottomInset: 1 165 | enabled: listView.currentIndex !== -1 166 | 167 | onClicked: function() 168 | { 169 | const index = listView.currentIndex 170 | control.deletePathEntry(index) 171 | listView.currentIndex = -1 172 | } 173 | 174 | Qaterial.ToolTip 175 | { 176 | text: `Delete ${listView.model[listView.currentIndex]}` 177 | visible: parent.hovered || parent.pressed 178 | position: Qaterial.Style.Position.BottomStart 179 | } 180 | } // SquareButton 181 | 182 | Qaterial.SquareButton 183 | { 184 | icon.source: Qaterial.Icons.pencil 185 | topInset: 1 186 | bottomInset: 1 187 | enabled: listView.currentIndex !== -1 188 | 189 | onClicked: () => control.editPathEntry(listView.currentIndex) 190 | 191 | Qaterial.ToolTip 192 | { 193 | text: `Edit ${listView.model[listView.currentIndex]}` 194 | visible: parent.hovered || parent.pressed 195 | position: Qaterial.Style.Position.BottomStart 196 | } 197 | } // SquareButton 198 | 199 | Qaterial.SquareButton 200 | { 201 | icon.source: Qaterial.Icons.chevronUp 202 | topInset: 1 203 | bottomInset: 1 204 | enabled: (listView.currentIndex !== -1) && (listView.currentIndex > 0) 205 | 206 | onClicked: function() 207 | { 208 | const tempCurrentIndex = listView.currentIndex 209 | control.movePathUp(listView.currentIndex) 210 | listView.currentIndex = tempCurrentIndex - 1 211 | } 212 | 213 | Qaterial.ToolTip 214 | { 215 | text: `Move ${listView.model[listView.currentIndex]} Up` 216 | visible: parent.hovered || parent.pressed 217 | position: Qaterial.Style.Position.BottomStart 218 | } 219 | } // SquareButton 220 | 221 | Qaterial.SquareButton 222 | { 223 | icon.source: Qaterial.Icons.chevronDown 224 | topInset: 1 225 | bottomInset: 1 226 | enabled: (listView.currentIndex !== -1) && ((listView.model.length - 1) > listView.currentIndex) 227 | 228 | onClicked: function() 229 | { 230 | const tempCurrentIndex = listView.currentIndex 231 | control.movePathDown(listView.currentIndex) 232 | listView.currentIndex = tempCurrentIndex + 1 233 | } 234 | 235 | Qaterial.ToolTip 236 | { 237 | text: `Move ${listView.model[listView.currentIndex]} Down` 238 | visible: parent.hovered || parent.pressed 239 | position: Qaterial.Style.Position.BottomStart 240 | } 241 | } // SquareButton 242 | 243 | Qaterial.SquareButton 244 | { 245 | icon.source: Qaterial.Icons.restore 246 | topInset: 1 247 | bottomInset: 1 248 | 249 | onClicked: () => control.resetPathEntries() 250 | 251 | Qaterial.ToolTip 252 | { 253 | text: `Reset import path` 254 | visible: parent.hovered || parent.pressed 255 | position: Qaterial.Style.Position.BottomStart 256 | } 257 | } // SquareButton 258 | } // ButtonColumn 259 | } // Content 260 | } 261 | } 262 | -------------------------------------------------------------------------------- /qml/Qaterial/HotReload/Imports.qml: -------------------------------------------------------------------------------- 1 | import QtQml 2 | import QtQml.Models 3 | import QtQml.StateMachine 4 | 5 | import QtQuick 6 | import QtQuick.Controls 7 | import QtQuick.Window 8 | import QtQuick.Layouts 9 | import QtQuick.Extras 10 | import QtQuick.Shapes 11 | 12 | import QtQuick.Dialogs as QQ1D 13 | 14 | import Qt5Compat.GraphicalEffects 15 | 16 | import Qt.labs.animation 17 | import Qt.labs.calendar 18 | import Qt.labs.platform 19 | import Qt.labs.qmlmodels 20 | import Qt.labs.folderlistmodel 21 | import Qt.labs.location 22 | import Qt.labs.sharedimage 23 | import Qt.labs.wavefrontmesh 24 | 25 | import Qt3D.Core 26 | import Qt3D.Render 27 | import Qt3D.Input 28 | import Qt3D.Logic 29 | import Qt3D.Extras 30 | import Qt3D.Animation 31 | import QtQuick.Scene2D 32 | import QtQuick.Scene3D 33 | 34 | import QtQuick3D 35 | import QtQuick3D.Materials 36 | import QtQuick3D.Effects 37 | import QtQuick3D.Helpers 38 | 39 | Item {} 40 | -------------------------------------------------------------------------------- /qml/Qaterial/HotReload/Main.qml: -------------------------------------------------------------------------------- 1 | // Copyright Olivier Le Doeuff 2020 (C) 2 | 3 | import Qaterial.HotReload as HR 4 | 5 | HR.SplashScreenApplication {} 6 | -------------------------------------------------------------------------------- /qml/Qaterial/HotReload/MaterialPaletteMenu.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.12 2 | import QtQuick.Controls 2.12 3 | import Qaterial as Qaterial 4 | 5 | Qaterial.Menu 6 | { 7 | id: root 8 | 9 | implicitWidth: palette.implicitWidth + leftPadding + rightPadding 10 | implicitHeight: palette.implicitHeight + topPadding + bottomPadding 11 | 12 | leftPadding: 8 13 | rightPadding: 8 14 | 15 | Qaterial.MaterialColorPalette 16 | { 17 | id: palette 18 | 19 | cellWidth: 24 20 | cellHeight: 24 21 | 22 | showName: false 23 | showShade: false 24 | 25 | function camelize(str) 26 | { 27 | return str.replace(/(?:^\w|[A-Z]|\b\w)/g, function(word, index) 28 | { 29 | return index === 0 ? word.toLowerCase() : word.toUpperCase(); 30 | }) 31 | .replace(/\s+/g, ''); 32 | } 33 | 34 | onAccepted: function(color, name, shade) 35 | { 36 | const colorProperty = `Qaterial.Colors.${camelize(name)}${shade}` 37 | Qaterial.Clipboard.text = colorProperty 38 | Qaterial.SnackbarManager.show(`Color copied! \n"${colorProperty}"`) 39 | } 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /qml/Qaterial/HotReload/SplashScreenApplication.qml: -------------------------------------------------------------------------------- 1 | // Copyright Olivier Le Doeuff 2020 (C) 2 | 3 | import QtCore 4 | import QtQuick 2.0 5 | 6 | import Qaterial as Qaterial 7 | import Qaterial.HotReload as HR 8 | 9 | Qaterial.SplashScreenApplication 10 | { 11 | id: root 12 | 13 | property int appTheme: Qaterial.Style.theme 14 | 15 | splashScreen: HR.SplashScreenWindow {} 16 | window: HR.HotReloadWindow {} 17 | 18 | Settings { property alias appTheme: root.appTheme } 19 | 20 | Component.onCompleted: function() 21 | { 22 | Qaterial.Style.theme = root.appTheme 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /qml/Qaterial/HotReload/SplashScreenWindow.qml: -------------------------------------------------------------------------------- 1 | // Copyright Olivier Le Doeuff 2020 (C) 2 | 3 | import QtQuick 2.0 4 | import Qaterial as Qaterial 5 | import Qaterial.HotReload as HR 6 | 7 | Qaterial.SplashScreenWindow 8 | { 9 | id: splash 10 | 11 | image: Qaterial.Style.theme === Qaterial.Style.Theme.Dark ? HR.Images.qaterialHotreloadWhite : HR.Images.qaterialHotreloadBlack 12 | padding: Qaterial.Style.card.horizontalPadding 13 | 14 | text: "Loading ..." 15 | version: Qaterial.Version.readable 16 | 17 | property int dots: 1 18 | 19 | Timer 20 | { 21 | interval: 1000;running: true;repeat: true 22 | onTriggered: function() 23 | { 24 | ++splash.dots 25 | let text = "Loading " 26 | for(let i = 0; i < splash.dots; ++i) 27 | text += '.' 28 | 29 | splash.text = text 30 | if(splash.dots == 3) 31 | splash.dots = 0 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /qml/Qaterial/HotReload/StatusView.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.12 2 | import QtQuick.Controls 2.12 3 | import Qaterial as Qaterial 4 | 5 | Rectangle 6 | { 7 | id: root 8 | 9 | property string logString 10 | property string errorString 11 | property string file 12 | property bool error: errorString 13 | 14 | implicitWidth: 500 15 | implicitHeight: 100 16 | 17 | color: Qaterial.Style.colorTheme.background0 18 | 19 | function append(text) 20 | { 21 | _errorTextArea.append(text) 22 | } 23 | 24 | Flickable 25 | { 26 | id: _flick 27 | width: parent.width 28 | height: root.height - _errorRect.height 29 | 30 | contentWidth: width 31 | contentHeight: _errorTextArea.height 32 | 33 | clip: true 34 | 35 | ScrollBar.vertical: Qaterial.ScrollBar {} 36 | ScrollBar.horizontal: Qaterial.ScrollBar {} 37 | 38 | TextEdit 39 | { 40 | id: _errorTextArea 41 | width: parent.width 42 | text: root.logString 43 | textFormat: TextEdit.RichText 44 | readOnly: true 45 | wrapMode: Text.WordWrap 46 | selectByMouse: true 47 | padding: Qaterial.Style.card.horizontalPadding 48 | selectionColor: Qaterial.Style.accentColor 49 | 50 | //color: Qaterial.Style.textTypeToColor(Qaterial.Style.TextType.Body1) 51 | font: Qaterial.Style.textTheme.body2 52 | //font.family: Qaterial.Style.textTypeToFontFamily(Qaterial.Style.TextType.Body1) 53 | //font.styleName: Qaterial.Style.textTypeToStyleName(Qaterial.Style.TextType.Body1) 54 | //font.capitalization: Qaterial.Style.fontCapitalization(Qaterial.Style.TextType.Body1) 55 | //font.letterSpacing: Qaterial.Style.textTypeToLetterSpacing(Qaterial.Style.TextType.Body1) 56 | //font.pixelSize: Qaterial.Style.textTypeToPixelSize(Qaterial.Style.TextType.Body1) 57 | 58 | onTextChanged: function() 59 | { 60 | cursorPosition = text.length - 1 61 | if(height > _flick.height) 62 | _flick.contentY = height - _flick.height 63 | } 64 | } 65 | } 66 | 67 | Rectangle 68 | { 69 | id: _errorRect 70 | 71 | y: root.height - height 72 | width: parent.width 73 | height: 32 74 | 75 | color: root.error ? "#f44336" : "#4CAF50" 76 | 77 | Qaterial.Label 78 | { 79 | anchors.verticalCenter: parent.verticalCenter 80 | anchors.left: parent.left 81 | anchors.leftMargin: Qaterial.Style.card.horizontalPadding 82 | anchors.right: _version.left 83 | anchors.rightMargin: Qaterial.Style.card.horizontalPadding 84 | 85 | width: 200 86 | 87 | text: root.error ? `Fail to load ${root.file}` : `File ${root.file} Loaded` 88 | elide: Text.ElideRight 89 | 90 | horizontalAlignment: Text.AlignLeft 91 | verticalAlignment: Text.AlignVCenter 92 | } // Qaterial.Label 93 | 94 | Qaterial.Label 95 | { 96 | id: _version 97 | anchors.verticalCenter: parent.verticalCenter 98 | anchors.right: parent.right 99 | anchors.rightMargin: Qaterial.Style.card.horizontalPadding 100 | text: Qaterial.Version.readable 101 | font: Qaterial.Style.textTheme.caption 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /qml/Qaterial/HotReload/TypoMenu.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.12 2 | import QtQuick.Controls 2.12 3 | import Qt.labs.folderlistmodel 4 | import Qaterial as Qaterial 5 | 6 | Qaterial.Menu 7 | { 8 | id: root 9 | 10 | width: headline1.implicitWidth 11 | 12 | function copyToClipboard(labelName) 13 | { 14 | const textToCopy = `Qaterial.Label${labelName}` 15 | Qaterial.Clipboard.text = textToCopy 16 | Qaterial.SnackbarManager.show(`Text Type Copied! \n'${textToCopy}'`) 17 | root.close() 18 | } 19 | 20 | Qaterial.ItemDelegate 21 | { 22 | id: headline1 23 | implicitHeight: implicitContentHeight + topPadding + bottomPadding 24 | contentItem: Qaterial.LabelHeadline1 25 | { 26 | text: "Headline1" 27 | verticalAlignment: Text.AlignVCenter 28 | } 29 | 30 | onClicked: () => copyToClipboard("Headline1") 31 | } 32 | 33 | Qaterial.ItemDelegate 34 | { 35 | implicitHeight: implicitContentHeight + topPadding + bottomPadding 36 | contentItem: Qaterial.LabelHeadline2 37 | { 38 | text: "Headline2" 39 | verticalAlignment: Text.AlignVCenter 40 | } 41 | 42 | onClicked: () => copyToClipboard("Headline2") 43 | } 44 | 45 | Qaterial.ItemDelegate 46 | { 47 | implicitHeight: implicitContentHeight + topPadding + bottomPadding 48 | contentItem: Qaterial.LabelHeadline3 49 | { 50 | text: "Headline3" 51 | verticalAlignment: Text.AlignVCenter 52 | } 53 | 54 | onClicked: () => copyToClipboard("Headline3") 55 | } 56 | 57 | Qaterial.ItemDelegate 58 | { 59 | implicitHeight: implicitContentHeight + topPadding + bottomPadding 60 | contentItem: Qaterial.LabelHeadline4 61 | { 62 | text: "Headline4" 63 | verticalAlignment: Text.AlignVCenter 64 | } 65 | 66 | onClicked: () => copyToClipboard("Headline4") 67 | } 68 | 69 | Qaterial.ItemDelegate 70 | { 71 | implicitHeight: implicitContentHeight + topPadding + bottomPadding 72 | contentItem: Qaterial.LabelHeadline5 73 | { 74 | text: "Headline5" 75 | verticalAlignment: Text.AlignVCenter 76 | } 77 | 78 | onClicked: () => copyToClipboard("Headline5") 79 | } 80 | 81 | Qaterial.ItemDelegate 82 | { 83 | implicitHeight: implicitContentHeight + topPadding + bottomPadding 84 | contentItem: Qaterial.LabelHeadline6 85 | { 86 | text: "Headline6" 87 | verticalAlignment: Text.AlignVCenter 88 | } 89 | 90 | onClicked: () => copyToClipboard("Headline6") 91 | } 92 | 93 | Qaterial.ItemDelegate 94 | { 95 | implicitHeight: implicitContentHeight + topPadding + bottomPadding 96 | contentItem: Qaterial.LabelSubtitle1 97 | { 98 | text: "Subtitle1" 99 | verticalAlignment: Text.AlignVCenter 100 | } 101 | 102 | onClicked: () => copyToClipboard("Subtitle1") 103 | } 104 | 105 | Qaterial.ItemDelegate 106 | { 107 | implicitHeight: implicitContentHeight + topPadding + bottomPadding 108 | contentItem: Qaterial.LabelSubtitle2 109 | { 110 | text: "Subtitle2" 111 | verticalAlignment: Text.AlignVCenter 112 | } 113 | 114 | onClicked: () => copyToClipboard("Subtitle2") 115 | } 116 | 117 | Qaterial.ItemDelegate 118 | { 119 | implicitHeight: implicitContentHeight + topPadding + bottomPadding 120 | contentItem: Qaterial.LabelBody1 121 | { 122 | text: "Body1" 123 | verticalAlignment: Text.AlignVCenter 124 | } 125 | 126 | onClicked: () => copyToClipboard("Body1") 127 | } 128 | 129 | Qaterial.ItemDelegate 130 | { 131 | implicitHeight: implicitContentHeight + topPadding + bottomPadding 132 | contentItem: Qaterial.LabelBody2 133 | { 134 | text: "Body2" 135 | verticalAlignment: Text.AlignVCenter 136 | } 137 | 138 | onClicked: () => copyToClipboard("Body2") 139 | } 140 | 141 | Qaterial.ItemDelegate 142 | { 143 | implicitHeight: implicitContentHeight + topPadding + bottomPadding 144 | contentItem: Qaterial.LabelButton 145 | { 146 | text: "Button" 147 | verticalAlignment: Text.AlignVCenter 148 | } 149 | 150 | onClicked: () => copyToClipboard("Button") 151 | } 152 | 153 | Qaterial.ItemDelegate 154 | { 155 | implicitHeight: implicitContentHeight + topPadding + bottomPadding 156 | contentItem: Qaterial.LabelOverline 157 | { 158 | text: "Overline" 159 | verticalAlignment: Text.AlignVCenter 160 | } 161 | 162 | onClicked: () => copyToClipboard("Overline") 163 | } 164 | 165 | Qaterial.ItemDelegate 166 | { 167 | implicitHeight: implicitContentHeight + topPadding + bottomPadding 168 | contentItem: Qaterial.LabelCaption 169 | { 170 | text: "Caption" 171 | verticalAlignment: Text.AlignVCenter 172 | } 173 | 174 | onClicked: () => copyToClipboard("Caption") 175 | } 176 | 177 | Qaterial.ItemDelegate 178 | { 179 | implicitHeight: implicitContentHeight + topPadding + bottomPadding 180 | contentItem: Qaterial.LabelHint1 181 | { 182 | text: "Hint1" 183 | verticalAlignment: Text.AlignVCenter 184 | } 185 | 186 | onClicked: () => copyToClipboard("Hint1") 187 | } 188 | 189 | Qaterial.ItemDelegate 190 | { 191 | implicitHeight: implicitContentHeight + topPadding + bottomPadding 192 | contentItem: Qaterial.LabelHint2 193 | { 194 | text: "Hint2" 195 | verticalAlignment: Text.AlignVCenter 196 | } 197 | 198 | onClicked: () => copyToClipboard("Hint2") 199 | } 200 | } 201 | -------------------------------------------------------------------------------- /src/Qaterial/HotReload/HotReload.cpp: -------------------------------------------------------------------------------- 1 | // MIT License 2 | // 3 | // Copyright (c) 2020 Olivier Le Doeuff 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in all 13 | // copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | // SOFTWARE. 22 | 23 | #include 24 | #include 25 | 26 | namespace qaterial { 27 | 28 | bool HotReload::_resetImportPath = false; 29 | bool HotReload::_resetCurrentFile = false; 30 | static std::unordered_set hotReloaders; 31 | 32 | HotReload::HotReload(QQmlEngine* engine, QObject* parent) : QObject(parent), _engine(engine) 33 | { 34 | connect(&_watcher, &QFileSystemWatcher::fileChanged, this, &HotReload::watchedFileChanged); 35 | hotReloaders.insert(this); 36 | } 37 | 38 | HotReload::~HotReload() { hotReloaders.erase(this); } 39 | 40 | void HotReload::clearCache() const { _engine->clearComponentCache(); } 41 | 42 | void HotReload::registerSingleton() 43 | { 44 | qmlRegisterSingletonType("Qaterial", 45 | 1, 46 | 0, 47 | "HotReload", 48 | [](QQmlEngine* engine, QJSEngine* scriptEngine) -> QObject* 49 | { 50 | Q_UNUSED(scriptEngine); 51 | auto* instance = new qaterial::HotReload(engine, engine); 52 | instance->_defaultImportPaths = engine->importPathList(); 53 | return instance; 54 | }); 55 | } 56 | 57 | void HotReload::resetImportPath() { _resetImportPath = true; } 58 | 59 | void HotReload::resetCurrentFile() { _resetCurrentFile = true; } 60 | 61 | void HotReload::log(QtMsgType type, const QMessageLogContext& context, const QString& msg) 62 | { 63 | // todo : better log ui 64 | const auto timestamp = QDateTime::currentDateTime().toString("hh:mm:ss.zzz"); 65 | const auto category = [type]() -> QString 66 | { 67 | switch(type) 68 | { 69 | case QtDebugMsg: return "debug"; 70 | case QtWarningMsg: return "warning"; 71 | case QtCriticalMsg: return "error"; 72 | case QtFatalMsg: return "error"; 73 | case QtInfoMsg: return "info"; 74 | default: return "unknown"; 75 | } 76 | }(); 77 | 78 | const auto log = "[" + timestamp + "] [" + context.category + "] [" + category + "] : " + msg; 79 | 80 | for(auto* hr: hotReloaders) { Q_EMIT hr->newLog(log); } 81 | } 82 | 83 | void HotReload::watchFile(const QString& path) { _watcher.addPath(path); } 84 | 85 | void HotReload::unWatchFile(const QString& path) 86 | { 87 | if(!path.isEmpty()) 88 | _watcher.removePath(path); 89 | } 90 | 91 | } 92 | -------------------------------------------------------------------------------- /src/Qaterial/HotReload/HotReload.hpp: -------------------------------------------------------------------------------- 1 | // MIT License 2 | // 3 | // Copyright (c) 2020 Olivier Le Doeuff 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in all 13 | // copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | // SOFTWARE. 22 | 23 | #ifndef __QATERIAL_HOT_RELOAD_HPP__ 24 | #define __QATERIAL_HOT_RELOAD_HPP__ 25 | 26 | #include 27 | #include 28 | #include 29 | #include 30 | 31 | namespace qaterial { 32 | 33 | class HotReload : public QObject 34 | { 35 | Q_OBJECT 36 | 37 | public: 38 | HotReload(QQmlEngine* engine, QObject* parent); 39 | ~HotReload(); 40 | 41 | private: 42 | QQmlEngine* _engine; 43 | QFileSystemWatcher _watcher; 44 | QStringList _defaultImportPaths; 45 | 46 | Q_PROPERTY(QStringList importPaths READ importPaths WRITE setImportPaths RESET resetImportPaths NOTIFY importPathsChanged) 47 | Q_PROPERTY(bool resetImportPath READ getResetImportPath CONSTANT); 48 | Q_PROPERTY(bool resetCurrentFile READ getResetCurrentFile CONSTANT); 49 | 50 | public: 51 | QStringList importPaths() const { return _engine->importPathList(); } 52 | void setImportPaths(const QStringList& paths) 53 | { 54 | _engine->setImportPathList(paths); 55 | Q_EMIT importPathsChanged(); 56 | } 57 | void resetImportPaths() { setImportPaths(_defaultImportPaths); } 58 | 59 | Q_SIGNALS: 60 | void importPathsChanged(); 61 | 62 | public Q_SLOTS: 63 | void clearCache() const; 64 | 65 | void watchFile(const QString& path); 66 | void unWatchFile(const QString& path); 67 | Q_SIGNALS: 68 | void watchedFileChanged(); 69 | void newLog(QString s); 70 | 71 | public: 72 | static void registerSingleton(); 73 | static void resetImportPath(); 74 | static void resetCurrentFile(); 75 | static bool getResetImportPath() { return _resetImportPath; } 76 | static bool getResetCurrentFile() { return _resetCurrentFile; } 77 | static void log(QtMsgType type, const QMessageLogContext& context, const QString& msg); 78 | 79 | private: 80 | static bool _resetImportPath; 81 | static bool _resetCurrentFile; 82 | }; 83 | 84 | } 85 | 86 | #endif 87 | -------------------------------------------------------------------------------- /src/Qaterial/HotReload/Pch/Pch.hpp: -------------------------------------------------------------------------------- 1 | // MIT License 2 | // 3 | // Copyright (c) 2020 Olivier Le Doeuff 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in all 13 | // copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | // SOFTWARE. 22 | 23 | #ifndef __QATERIAL_HOTRELOAD_PCH_HPP__ 24 | #define __QATERIAL_HOTRELOAD_PCH_HPP__ 25 | 26 | #include 27 | #include 28 | 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | 36 | #include 37 | 38 | #ifdef major 39 | # undef major 40 | #endif 41 | #ifdef minor 42 | # undef minor 43 | #endif 44 | 45 | #endif 46 | -------------------------------------------------------------------------------- /src/Qaterial/HotReloadApp/Main.cpp: -------------------------------------------------------------------------------- 1 | // MIT License 2 | // 3 | // Copyright (c) 2020 Olivier Le Doeuff 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in all 13 | // copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | // SOFTWARE. 22 | 23 | #include 24 | #include 25 | #ifdef QATERIALHOTRELOAD_ENABLE_SFPM 26 | # include 27 | #endif 28 | 29 | #include 30 | #include 31 | #include 32 | #include 33 | 34 | #ifdef Q_OS_WIN 35 | # include 36 | #endif 37 | 38 | #include 39 | 40 | void qtMsgOutput(QtMsgType type, const QMessageLogContext& context, const QString& msg) 41 | { 42 | qaterial::HotReload::log(type, context, msg); 43 | 44 | { 45 | const auto timestamp = QDateTime::currentDateTime().toString("hh:mm:ss.zzz"); 46 | const auto category = [type]() -> QString 47 | { 48 | switch(type) 49 | { 50 | case QtDebugMsg: return "debug"; 51 | case QtWarningMsg: return "warning"; 52 | case QtCriticalMsg: return "error"; 53 | case QtFatalMsg: return "error"; 54 | case QtInfoMsg: return "info"; 55 | default: return "unknown"; 56 | } 57 | }(); 58 | 59 | const auto log = "[" + timestamp + "] [" + context.category + "] [" + category + "] : " + msg; 60 | #if defined(Q_OS_WIN) 61 | const auto winLog = log + "\n"; 62 | OutputDebugStringW(reinterpret_cast(winLog.utf16())); 63 | #elif defined(Q_OS_ANDROID) 64 | android_default_message_handler(type, context, msg); 65 | #endif 66 | std::cout << log.toStdString() << std::endl; 67 | } 68 | } 69 | 70 | int main(int argc, char* argv[]) 71 | { 72 | #if defined(QATERIALHOTRELOAD_IGNORE_ENV) 73 | const QString executable = argv[0]; 74 | # if defined(Q_OS_WINDOWS) 75 | const auto executablePath = executable.mid(0, executable.lastIndexOf("\\")); 76 | QCoreApplication::setLibraryPaths({executablePath}); 77 | # endif 78 | #endif 79 | qInstallMessageHandler(qtMsgOutput); 80 | 81 | QApplication app(argc, argv); 82 | QQmlApplicationEngine engine; 83 | 84 | // ──── REGISTER APPLICATION ──── 85 | 86 | QGuiApplication::setOrganizationName("Qaterial"); 87 | QGuiApplication::setApplicationName("Qaterial Hot Reload"); 88 | QGuiApplication::setOrganizationDomain("https://olivierldff.github.io/Qaterial/"); 89 | const QString version = QString::number(qaterial::versionMajor()) + QStringLiteral(".") + QString::number(qaterial::versionMinor()) + 90 | QStringLiteral(".") + QString::number(qaterial::versionPatch()) + QStringLiteral(".0x") + 91 | QString::number(qaterial::versionTag(), 16).rightJustified(8, QChar('0')); 92 | QGuiApplication::setApplicationVersion(version); 93 | QGuiApplication::setWindowIcon(QIcon(":/Qaterial/HotReload/Images/icon.svg")); 94 | 95 | QCommandLineParser parser; 96 | const QCommandLineOption resetImport(QStringList({"reset-imports"}), QCoreApplication::translate("main", "Force reset of imports")); 97 | parser.addOption(resetImport); 98 | const QCommandLineOption resetCurrentFile(QStringList({"reset-current-file"}), 99 | QCoreApplication::translate("main", "Force reset of current file")); 100 | parser.addOption(resetCurrentFile); 101 | parser.addHelpOption(); 102 | parser.process(app); 103 | 104 | // ──── LOAD AND REGISTER QML ──── 105 | 106 | #if defined(QATERIALHOTRELOAD_IGNORE_ENV) 107 | engine.setImportPathList({QLibraryInfo::location(QLibraryInfo::Qml2ImportsPath), "qrc:/", "qrc:/qt-project.org/imports"}); 108 | #else 109 | engine.addImportPath("qrc:/"); 110 | #endif 111 | 112 | // Load Qaterial 113 | qaterial::loadQmlResources(); 114 | qaterial::registerQmlTypes(); 115 | qaterial::HotReload::registerSingleton(); 116 | if(parser.isSet(resetImport)) 117 | qaterial::HotReload::resetImportPath(); 118 | if(parser.isSet(resetCurrentFile)) 119 | qaterial::HotReload::resetCurrentFile(); 120 | 121 | // ──── LOAD QML MAIN ──── 122 | 123 | engine.load(QUrl("qrc:/Qaterial/HotReload/Main.qml")); 124 | if(engine.rootObjects().isEmpty()) 125 | return -1; 126 | 127 | // ──── START EVENT LOOP ──── 128 | 129 | return QGuiApplication::exec(); 130 | } 131 | --------------------------------------------------------------------------------