├── .clang-format ├── .clang-tidy ├── .editorconfig ├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── Makefile ├── README.md ├── VERSION ├── cmake └── install-qml-module.cmake ├── flake.lock ├── flake.nix ├── nix ├── default.nix ├── overlays.nix └── shell.nix └── src ├── CMakeLists.txt └── style ├── Button.qml ├── CMakeLists.txt ├── CheckBox.qml ├── TextField.qml ├── impl ├── CMakeLists.txt ├── HyprlandStyle.qml ├── MotionBehavior.qml ├── checkdelegate.cpp ├── checkdelegate.hpp ├── hyprlandstyle.cpp └── hyprlandstyle.hpp ├── plugin.cpp └── test ├── CMakeLists.txt ├── main.cpp └── main.qml /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | BasedOnStyle: LLVM 4 | 5 | AccessModifierOffset: -2 6 | AlignAfterOpenBracket: Align 7 | AlignConsecutiveMacros: true 8 | AlignConsecutiveAssignments: true 9 | AlignEscapedNewlines: Right 10 | AlignOperands: false 11 | AlignTrailingComments: true 12 | AllowAllArgumentsOnNextLine: true 13 | AllowAllConstructorInitializersOnNextLine: true 14 | AllowAllParametersOfDeclarationOnNextLine: true 15 | AllowShortBlocksOnASingleLine: true 16 | AllowShortCaseLabelsOnASingleLine: true 17 | AllowShortFunctionsOnASingleLine: Empty 18 | AllowShortIfStatementsOnASingleLine: Never 19 | AllowShortLambdasOnASingleLine: All 20 | AllowShortLoopsOnASingleLine: false 21 | AlwaysBreakAfterDefinitionReturnType: None 22 | AlwaysBreakAfterReturnType: None 23 | AlwaysBreakBeforeMultilineStrings: false 24 | AlwaysBreakTemplateDeclarations: Yes 25 | BreakBeforeBraces: Attach 26 | BreakBeforeTernaryOperators: false 27 | BreakConstructorInitializers: AfterColon 28 | ColumnLimit: 100 29 | CompactNamespaces: false 30 | ConstructorInitializerAllOnOneLineOrOnePerLine: false 31 | ExperimentalAutoDetectBinPacking: false 32 | FixNamespaceComments: false 33 | IncludeBlocks: Preserve 34 | IndentCaseLabels: true 35 | IndentWidth: 4 36 | PointerAlignment: Left 37 | ReflowComments: false 38 | SortIncludes: false 39 | SortUsingDeclarations: false 40 | SpaceAfterCStyleCast: false 41 | SpaceAfterLogicalNot: false 42 | SpaceAfterTemplateKeyword: true 43 | SpaceBeforeCtorInitializerColon: true 44 | SpaceBeforeInheritanceColon: true 45 | SpaceBeforeParens: ControlStatements 46 | SpaceBeforeRangeBasedForLoopColon: true 47 | SpaceInEmptyParentheses: false 48 | SpacesBeforeTrailingComments: 1 49 | SpacesInAngles: false 50 | SpacesInCStyleCastParentheses: false 51 | SpacesInContainerLiterals: false 52 | SpacesInParentheses: false 53 | SpacesInSquareBrackets: false 54 | Standard: Auto 55 | TabWidth: 4 56 | UseTab: Never 57 | 58 | AllowShortEnumsOnASingleLine: false 59 | 60 | BraceWrapping: 61 | AfterEnum: false 62 | 63 | AlignConsecutiveDeclarations: AcrossEmptyLines 64 | 65 | NamespaceIndentation: All 66 | -------------------------------------------------------------------------------- /.clang-tidy: -------------------------------------------------------------------------------- 1 | WarningsAsErrors: '*' 2 | HeaderFilterRegex: '.*\.hpp' 3 | FormatStyle: file 4 | Checks: > 5 | -*, 6 | bugprone-*, 7 | -bugprone-easily-swappable-parameters, 8 | -bugprone-forward-declararion-namespace, 9 | -bugprone-forward-declararion-namespace, 10 | -bugprone-return-const-ref-from-parameter, 11 | concurrency-*, 12 | cppcoreguidelines-*, 13 | -cppcoreguidelines-owning-memory, 14 | -cppcoreguidelines-avoid-magic-numbers, 15 | -cppcoreguidelines-pro-bounds-constant-array-index, 16 | -cppcoreguidelines-avoid-const-or-ref-data-members, 17 | -cppcoreguidelines-non-private-member-variables-in-classes, 18 | -cppcoreguidelines-pro-bounds-array-to-pointer-decay, 19 | -cppcoreguidelines-avoid-do-while, 20 | -cppcoreguidelines-pro-type-reinterpret-cast, 21 | -cppcoreguidelines-pro-type-vararg, 22 | google-global-names-in-headers, 23 | google-readability-casting, 24 | google-runtime-int, 25 | google-runtime-operator, 26 | misc-*, 27 | -misc-no-recursion, 28 | -misc-non-private-member-variables-in-classes, 29 | modernize-*, 30 | -modernize-return-braced-init-list, 31 | -modernize-use-trailing-return-type, 32 | performance-*, 33 | -performance-avoid-endl, 34 | portability-std-allocator-const, 35 | readability-*, 36 | -readability-function-cognitive-complexity, 37 | -readability-function-size, 38 | -readability-identifier-length, 39 | -readability-magic-numbers, 40 | -readability-uppercase-literal-suffix, 41 | -readability-braces-around-statements, 42 | -readability-redundant-access-specifiers, 43 | -readability-else-after-return, 44 | -readability-container-data-pointer, 45 | -readability-implicit-bool-conversion, 46 | -readability-avoid-nested-conditional-operator, 47 | -readability-math-missing-parentheses, 48 | tidyfox-*, 49 | CheckOptions: 50 | performance-for-range-copy.WarnOnAllAutoCopies: true 51 | performance-inefficient-string-concatenation.StrictMode: true 52 | readability-identifier-naming.ClassCase: CamelCase 53 | readability-identifier-naming.ConstantCase: UPPER_CASE 54 | readability-identifier-naming.EnumCase: CamelCase 55 | readability-identifier-naming.EnumConstantCase: CamelCase 56 | readability-identifier-naming.FunctionCase: camelBack 57 | readability-identifier-naming.MemberCase: camelBack 58 | readability-identifier-naming.NamespaceCase: lower_case 59 | readability-identifier-naming.LocalConstantCase: camelBack 60 | readability-identifier-naming.MethodCase: camelBack 61 | readability-identifier-naming.ParameterCase: camelBack 62 | readability-identifier-naming.VariableCase: camelBack 63 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | insert_final_newline = true 7 | indent_style = space 8 | indent_size = 4 9 | 10 | [*.qml,*.nix] 11 | indent_size = 4 12 | indent_style = space 13 | 14 | [Makefile] 15 | indent_style = tab 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # build 2 | /build 3 | /result 4 | compile_commands.json 5 | 6 | # direnv 7 | /.direnv 8 | /.envrc 9 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.20) 2 | 3 | # Get version 4 | file(READ "${CMAKE_SOURCE_DIR}/VERSION" VER_RAW) 5 | string(STRIP ${VER_RAW} VER) 6 | 7 | project(hyprland-qt-support VERSION ${VER} LANGUAGES CXX) 8 | 9 | set(CMAKE_CXX_STANDARD 23) 10 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 11 | 12 | include(GNUInstallDirs) 13 | include(cmake/install-qml-module.cmake) 14 | 15 | option(BUILD_TESTER "Build style tester" OFF) 16 | 17 | find_package(Qt6 6.6 REQUIRED COMPONENTS Qml Quick QuickControls2) 18 | find_package(PkgConfig REQUIRED) 19 | 20 | pkg_check_modules(hyprlang REQUIRED IMPORTED_TARGET hyprlang>=0.6.0) 21 | 22 | qt_standard_project_setup(REQUIRES 6.6) 23 | set(QT_QML_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/qml) 24 | 25 | add_subdirectory(src) 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2024, Hypr Development 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | 1. Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | 2. Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | 3. Neither the name of the copyright holder nor the names of its 16 | contributors may be used to endorse or promote products derived from 17 | this software without specific prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: all 2 | all: build 3 | cmake --build build 4 | 5 | build: 6 | cmake -GNinja -B build -DCMAKE_BUILD_TYPE=RelWithDebInfo 7 | 8 | .PHONY: dev 9 | dev: 10 | CMAKE_EXPORT_COMPILE_COMMANDS=1 cmake -GNinja -B build -DCMAKE_BUILD_TYPE=Debug 11 | ln -sf build/compile_commands.json . 12 | 13 | .PHONY: fmt 14 | fmt: 15 | find src -type f \( -name "*.cpp" -o -name "*.hpp" \) -print0 | xargs -0 clang-format -i 16 | 17 | .PHONY: lint 18 | lint: 19 | find src -type f -name "*.cpp" -print0 | parallel -q0 --eta clang-tidy 20 | 21 | .PHONY: clean 22 | clean: 23 | rm -rf build 24 | rm -f compile_commands.json 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## hyprland-qt-support 2 | 3 | A qt6 qml style provider for hypr* apps. 4 | 5 | ## Building 6 | 7 | You can build it with this command: 8 | ```sh 9 | cmake --no-warn-unused-cli -DCMAKE_BUILD_TYPE:STRING=Release -DCMAKE_INSTALL_PREFIX:PATH=/usr -DINSTALL_QML_PREFIX=/lib/qt6/qml -S . -B ./build 10 | cmake --build ./build --config Release --target all -j`nproc 2>/dev/null || getconf NPROCESSORS_CONF` 11 | ``` 12 | 13 | Please note the `INSTALL_QML_PREFIX` is _distro-specific_ and may differ. 14 | 15 | For Arch, it's the same as in the example above, `/lib/qt6/qml`. 16 | 17 | ## Usage 18 | 19 | Launch a qt/qml app with `QT_QUICK_CONTROLS_STYLE=org.hyprland.style` 20 | -------------------------------------------------------------------------------- /VERSION: -------------------------------------------------------------------------------- 1 | 0.1.0 2 | -------------------------------------------------------------------------------- /cmake/install-qml-module.cmake: -------------------------------------------------------------------------------- 1 | set(INSTALL_QMLDIR "" CACHE STRING "QML install dir") 2 | set(INSTALL_QML_PREFIX "" CACHE STRING "QML install prefix") 3 | 4 | # There doesn't seem to be a standard cross-distro qml install path. 5 | if ("${INSTALL_QMLDIR}" STREQUAL "" AND "${INSTALL_QML_PREFIX}" STREQUAL "") 6 | message(WARNING "Neither INSTALL_QMLDIR nor INSTALL_QML_PREFIX is set. QML modules will not be installed.") 7 | else() 8 | if ("${INSTALL_QMLDIR}" STREQUAL "") 9 | set(QML_FULL_INSTALLDIR "${CMAKE_INSTALL_PREFIX}/${INSTALL_QML_PREFIX}") 10 | else() 11 | set(QML_FULL_INSTALLDIR "${INSTALL_QMLDIR}") 12 | endif() 13 | 14 | message(STATUS "QML install dir: ${QML_FULL_INSTALLDIR}") 15 | endif() 16 | 17 | # Install a given target as a QML module. This is mostly pulled from ECM, as there does not seem 18 | # to be an official way to do it. 19 | # see https://github.com/KDE/extra-cmake-modules/blob/fe0f606bf7f222e36f7560fd7a2c33ef993e23bb/modules/ECMQmlModule6.cmake#L160 20 | function(install_qml_module arg_TARGET) 21 | if (NOT DEFINED QML_FULL_INSTALLDIR) 22 | return() 23 | endif() 24 | 25 | qt_query_qml_module(${arg_TARGET} 26 | URI module_uri 27 | VERSION module_version 28 | PLUGIN_TARGET module_plugin_target 29 | TARGET_PATH module_target_path 30 | QMLDIR module_qmldir 31 | TYPEINFO module_typeinfo 32 | QML_FILES module_qml_files 33 | RESOURCES module_resources 34 | ) 35 | 36 | set(module_dir "${QML_FULL_INSTALLDIR}/${module_target_path}") 37 | 38 | if (NOT TARGET "${module_plugin_target}") 39 | message(FATAL_ERROR "install_qml_modules called for a target without a plugin") 40 | endif() 41 | 42 | # Install the target to /lib instead of the qml module dir. 43 | # If installed to the module dir, inter-module dependencies will not work 44 | # due to broken rpaths. The standard fix for this seems to be to just 45 | # put the library in /lib. 46 | install(TARGETS "${arg_TARGET}") 47 | 48 | install( 49 | TARGETS "${module_plugin_target}" 50 | LIBRARY DESTINATION "${module_dir}" 51 | RUNTIME DESTINATION "${module_dir}" 52 | ) 53 | 54 | install(FILES "${module_qmldir}" DESTINATION "${module_dir}") 55 | install(FILES "${module_typeinfo}" DESTINATION "${module_dir}") 56 | 57 | # Install QML files 58 | list(LENGTH module_qml_files num_files) 59 | if (NOT "${module_qml_files}" MATCHES "NOTFOUND" AND ${num_files} GREATER 0) 60 | qt_query_qml_module(${arg_TARGET} QML_FILES_DEPLOY_PATHS qml_files_deploy_paths) 61 | 62 | math(EXPR last_index "${num_files} - 1") 63 | foreach(i RANGE 0 ${last_index}) 64 | list(GET module_qml_files ${i} src_file) 65 | list(GET qml_files_deploy_paths ${i} deploy_path) 66 | get_filename_component(dst_name "${deploy_path}" NAME) 67 | get_filename_component(dest_dir "${deploy_path}" DIRECTORY) 68 | install(FILES "${src_file}" DESTINATION "${module_dir}/${dest_dir}" RENAME "${dst_name}") 69 | endforeach() 70 | endif() 71 | 72 | # Install resources 73 | list(LENGTH module_resources num_files) 74 | if (NOT "${module_resources}" MATCHES "NOTFOUND" AND ${num_files} GREATER 0) 75 | qt_query_qml_module(${arg_TARGET} RESOURCES_DEPLOY_PATHS resources_deploy_paths) 76 | 77 | math(EXPR last_index "${num_files} - 1") 78 | foreach(i RANGE 0 ${last_index}) 79 | list(GET module_resources ${i} src_file) 80 | list(GET resources_deploy_paths ${i} deploy_path) 81 | get_filename_component(dst_name "${deploy_path}" NAME) 82 | get_filename_component(dest_dir "${deploy_path}" DIRECTORY) 83 | install(FILES "${src_file}" DESTINATION "${module_dir}/${dest_dir}" RENAME "${dst_name}") 84 | endforeach() 85 | endif() 86 | endfunction() 87 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "hyprlang": { 4 | "inputs": { 5 | "hyprutils": "hyprutils", 6 | "nixpkgs": [ 7 | "nixpkgs" 8 | ], 9 | "systems": [ 10 | "systems" 11 | ] 12 | }, 13 | "locked": { 14 | "lastModified": 1737634606, 15 | "narHash": "sha256-W7W87Cv6wqZ9PHegI6rH1+ve3zJPiyevMFf0/HwdbCQ=", 16 | "owner": "hyprwm", 17 | "repo": "hyprlang", 18 | "rev": "f41271d35cc0f370d300413d756c2677f386af9d", 19 | "type": "github" 20 | }, 21 | "original": { 22 | "owner": "hyprwm", 23 | "repo": "hyprlang", 24 | "type": "github" 25 | } 26 | }, 27 | "hyprutils": { 28 | "inputs": { 29 | "nixpkgs": [ 30 | "hyprlang", 31 | "nixpkgs" 32 | ], 33 | "systems": [ 34 | "hyprlang", 35 | "systems" 36 | ] 37 | }, 38 | "locked": { 39 | "lastModified": 1737632363, 40 | "narHash": "sha256-X9I8POSlHxBVjD0fiX1O2j7U9Zi1+4rIkrsyHP0uHXY=", 41 | "owner": "hyprwm", 42 | "repo": "hyprutils", 43 | "rev": "006620eb29d54ea9086538891404c78563d1bae1", 44 | "type": "github" 45 | }, 46 | "original": { 47 | "owner": "hyprwm", 48 | "repo": "hyprutils", 49 | "type": "github" 50 | } 51 | }, 52 | "nixpkgs": { 53 | "locked": { 54 | "lastModified": 1737469691, 55 | "narHash": "sha256-nmKOgAU48S41dTPIXAq0AHZSehWUn6ZPrUKijHAMmIk=", 56 | "owner": "NixOS", 57 | "repo": "nixpkgs", 58 | "rev": "9e4d5190a9482a1fb9d18adf0bdb83c6e506eaab", 59 | "type": "github" 60 | }, 61 | "original": { 62 | "owner": "NixOS", 63 | "ref": "nixos-unstable", 64 | "repo": "nixpkgs", 65 | "type": "github" 66 | } 67 | }, 68 | "root": { 69 | "inputs": { 70 | "hyprlang": "hyprlang", 71 | "nixpkgs": "nixpkgs", 72 | "systems": "systems" 73 | } 74 | }, 75 | "systems": { 76 | "locked": { 77 | "lastModified": 1689347949, 78 | "narHash": "sha256-12tWmuL2zgBgZkdoB6qXZsgJEH9LR3oUgpaQq2RbI80=", 79 | "owner": "nix-systems", 80 | "repo": "default-linux", 81 | "rev": "31732fcf5e8fea42e59c2488ad31a0e651500f68", 82 | "type": "github" 83 | }, 84 | "original": { 85 | "owner": "nix-systems", 86 | "repo": "default-linux", 87 | "type": "github" 88 | } 89 | } 90 | }, 91 | "root": "root", 92 | "version": 7 93 | } 94 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "extra qt support libraries for the hyprland ecosystem"; 3 | 4 | inputs = { 5 | nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; 6 | systems.url = "github:nix-systems/default-linux"; 7 | 8 | hyprlang = { 9 | url = "github:hyprwm/hyprlang"; 10 | inputs.nixpkgs.follows = "nixpkgs"; 11 | inputs.systems.follows = "systems"; 12 | }; 13 | }; 14 | 15 | outputs = { 16 | self, 17 | nixpkgs, 18 | systems, 19 | ... 20 | } @ inputs: let 21 | inherit (nixpkgs) lib; 22 | eachSystem = lib.genAttrs (import systems); 23 | pkgsFor = eachSystem ( 24 | system: 25 | import nixpkgs { 26 | localSystem = system; 27 | overlays = [self.overlays.default]; 28 | } 29 | ); 30 | in { 31 | overlays = import ./nix/overlays.nix {inherit inputs self lib;}; 32 | 33 | packages = eachSystem (system: { 34 | default = self.packages.${system}.hyprland-qt-support; 35 | inherit (pkgsFor.${system}) hyprland-qt-support; 36 | }); 37 | 38 | devShells = eachSystem (system: { 39 | default = import ./nix/shell.nix { 40 | pkgs = pkgsFor.${system}; 41 | inherit (pkgsFor.${system}) hyprland-qt-support; 42 | }; 43 | }); 44 | }; 45 | } 46 | -------------------------------------------------------------------------------- /nix/default.nix: -------------------------------------------------------------------------------- 1 | { 2 | lib, 3 | nix-gitignore, 4 | stdenv, 5 | cmake, 6 | ninja, 7 | qt6, 8 | pkg-config, 9 | hyprlang, 10 | version ? "0", 11 | }: 12 | stdenv.mkDerivation { 13 | pname = "hyprland-qt-support"; 14 | inherit version; 15 | 16 | src = nix-gitignore.gitignoreSource [] ./..; 17 | 18 | nativeBuildInputs = [ 19 | cmake 20 | ninja 21 | pkg-config 22 | qt6.wrapQtAppsHook 23 | ]; 24 | 25 | buildInputs = [ 26 | qt6.qtbase 27 | qt6.qtdeclarative 28 | qt6.qtsvg 29 | qt6.qtwayland 30 | hyprlang 31 | ]; 32 | 33 | cmakeFlags = [ 34 | (lib.cmakeFeature "INSTALL_QML_PREFIX" qt6.qtbase.qtQmlPrefix) 35 | ]; 36 | 37 | meta = { 38 | description = "hyprland-qt-support"; 39 | homepage = "https://github.com/hyprwm/hyprland-qt-support"; 40 | license = lib.licenses.bsd3; 41 | platforms = lib.platforms.linux; 42 | }; 43 | } 44 | -------------------------------------------------------------------------------- /nix/overlays.nix: -------------------------------------------------------------------------------- 1 | { 2 | inputs, 3 | self, 4 | lib, 5 | }: let 6 | mkDate = longDate: (lib.concatStringsSep "-" [ 7 | (builtins.substring 0 4 longDate) 8 | (builtins.substring 4 2 longDate) 9 | (builtins.substring 6 2 longDate) 10 | ]); 11 | date = mkDate (self.lastModifiedDate or "19700101"); 12 | version = lib.removeSuffix "\n" (builtins.readFile ../VERSION); 13 | in { 14 | default = self.overlays.hyprland-qt-support; 15 | 16 | hyprland-qt-support = lib.composeManyExtensions [ 17 | inputs.hyprlang.overlays.default 18 | (final: prev: { 19 | hyprland-qt-support = final.callPackage ./. { 20 | stdenv = final.gcc14Stdenv; 21 | version = "${version}+date=${date}_${self.shortRev or "dirty"}"; 22 | }; 23 | }) 24 | ]; 25 | } 26 | -------------------------------------------------------------------------------- /nix/shell.nix: -------------------------------------------------------------------------------- 1 | { 2 | pkgs ? import {}, 3 | hyprland-qt-support ? pkgs.callPackage ./default.nix {}, 4 | ... 5 | }: 6 | pkgs.mkShell { 7 | inputsFrom = [hyprland-qt-support]; 8 | nativeBuildInputs = [pkgs.clang-tools pkgs.parallel]; 9 | 10 | shellHook = let 11 | inherit (pkgs.lib.strings) concatMapStringsSep; 12 | qtLibPath = f: 13 | concatMapStringsSep ":" f (with pkgs.qt6; [ 14 | qtbase 15 | qtdeclarative 16 | qtwayland 17 | ]); 18 | in '' 19 | # Add Qt-related environment variables. 20 | export QT_PLUGIN_PATH=${qtLibPath (p: "${p}/lib/qt-6/plugins")} 21 | export QML2_IMPORT_PATH=${qtLibPath (p: "${p}/lib/qt-6/qml")} 22 | ''; 23 | } 24 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_subdirectory(style) 2 | -------------------------------------------------------------------------------- /src/style/Button.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2 | import QtQuick.Templates as T 3 | import org.hyprland.style.impl 4 | 5 | // This is private and we shouldn't use it, however rewriting IconLabel would take hundreds of 6 | // lines of C++ to end up with something worse. 7 | import QtQuick.Controls.impl as ControlsPrivate 8 | 9 | T.Button { 10 | id: control 11 | 12 | implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset, implicitContentWidth + leftPadding + rightPadding) 13 | implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset, implicitContentHeight + topPadding + bottomPadding) 14 | 15 | padding: 6 16 | spacing: 6 17 | 18 | icon.width: 24 19 | icon.height: 24 20 | icon.color: control.palette.buttonText 21 | 22 | contentItem: ControlsPrivate.IconLabel { 23 | spacing: control.spacing 24 | mirrored: control.mirrored 25 | display: control.display 26 | 27 | icon: control.icon 28 | text: control.text 29 | font: control.font 30 | color: control.palette.buttonText 31 | } 32 | 33 | background: Rectangle { 34 | implicitWidth: 50 35 | implicitHeight: 30 36 | 37 | radius: { 38 | switch (HyprlandStyle.roundness) { 39 | case 0: return 0; 40 | case 1: return 4; 41 | case 2: return 8; 42 | case 3: return 16; 43 | } 44 | } 45 | 46 | border.width: HyprlandStyle.borderWidth 47 | 48 | MotionBehavior on color { ColorAnimation { duration: 60 } } 49 | color: { 50 | let highlightTint = control.down || control.checked ? 0.3 : control.highlighted ? 0.25 : 0.0; 51 | 52 | if (control.flat && highlightTint) 53 | highlightTint += 0.3; 54 | 55 | const base = HyprlandStyle.flat(control.palette.button, control.flat); 56 | return HyprlandStyle.overlay(base, control.palette.highlight, highlightTint); 57 | } 58 | 59 | MotionBehavior on border.color { ColorAnimation { duration: 60 } } 60 | border.color: { 61 | let highlightTint = control.down || control.checked ? 1.0 : (control.enabled && control.hovered) || control.highlighted ? control.flat ? 0.8 : 0.6 : 0.0; 62 | 63 | const base = HyprlandStyle.flat(HyprlandStyle.lightenOrDarken(control.palette.button, 1.4), control.flat); 64 | return HyprlandStyle.overlay(base, control.palette.highlight, highlightTint); 65 | } 66 | 67 | Rectangle { 68 | anchors.fill: parent 69 | anchors.margins: -1 70 | radius: parent.radius + 1 71 | color: "transparent" 72 | 73 | MotionBehavior on border.color { ColorAnimation { duration: 60 } } 74 | border.color: control.visualFocus ? Qt.alpha(control.palette.highlight, 0.8) : "transparent" 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/style/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_subdirectory(impl) 2 | 3 | qt_add_qml_module(hyprland-quick-style SHARED 4 | URI org.hyprland.style 5 | VERSION 0.1 6 | PLUGIN_TARGET hyprland-quick-styleplugin 7 | NO_PLUGIN_OPTIONAL 8 | NO_GENERATE_PLUGIN_SOURCE 9 | CLASS_NAME HyprlandQuickStylePlugin 10 | IMPORTS 11 | org.hyprland.style.impl 12 | QtQuick.Controls.Basic 13 | QML_FILES 14 | Button.qml 15 | CheckBox.qml 16 | TextField.qml 17 | ) 18 | 19 | target_sources(hyprland-quick-styleplugin PRIVATE plugin.cpp) 20 | target_link_libraries(hyprland-quick-styleplugin PRIVATE Qt::Gui) 21 | 22 | target_link_libraries(hyprland-quick-style PRIVATE hyprland-quick-style-impl) 23 | 24 | install_qml_module(hyprland-quick-style) 25 | 26 | if (BUILD_TESTER) 27 | add_subdirectory(test) 28 | endif() 29 | -------------------------------------------------------------------------------- /src/style/CheckBox.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2 | import QtQuick.Templates as T 3 | import org.hyprland.style.impl 4 | 5 | T.CheckBox { 6 | id: control 7 | 8 | implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset, 9 | implicitContentWidth + leftPadding + rightPadding) 10 | implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset, 11 | implicitContentHeight + topPadding + bottomPadding, 12 | implicitIndicatorHeight + topPadding + bottomPadding) 13 | 14 | padding: 6 15 | spacing: 6 16 | 17 | contentItem: Text { 18 | leftPadding: control.indicator && !control.mirrored ? control.indicator.width + control.spacing : 0 19 | rightPadding: control.indicator && control.mirrored ? control.indicator.width + control.spacing : 0 20 | 21 | text: control.text 22 | font: control.font 23 | color: control.palette.windowText 24 | elide: Text.ElideRight 25 | verticalAlignment: Text.AlignVCenter 26 | } 27 | 28 | indicator: Rectangle { 29 | implicitWidth: 16 30 | implicitHeight: 16 31 | 32 | x: control.text ? (control.mirrored ? control.width - width - control.rightPadding : control.leftPadding) : control.leftPadding + (control.availableWidth - width) / 2 33 | y: Math.floor(control.topPadding + (control.availableHeight - height) / 2) 34 | 35 | radius: { 36 | switch (HyprlandStyle.roundness) { 37 | case 0: return 0; 38 | case 1: return 4; 39 | case 2: return 6; 40 | case 3: return 8; 41 | } 42 | } 43 | 44 | border.width: HyprlandStyle.borderWidth 45 | 46 | MotionBehavior on color { ColorAnimation { duration: 60 } } 47 | color: { 48 | let highlightTint = control.checkState !== Qt.Unchecked ? 0.3 : control.highlighted ? 0.25 : 0.0; 49 | 50 | const base = Qt.darker(control.palette.button, control.down ? 1.1 : 1.0); 51 | return HyprlandStyle.overlay(base, control.palette.highlight, highlightTint); 52 | } 53 | 54 | MotionBehavior on border.color { ColorAnimation { duration: 60 } } 55 | border.color: { 56 | let highlightTint = control.down || control.checkState !== Qt.Unchecked ? 1.0 : (control.enabled && control.hovered) || control.highlighted ? 0.6 : 0.0; 57 | 58 | const base = HyprlandStyle.lightenOrDarken(control.palette.button, 1.4); 59 | return HyprlandStyle.overlay(base, control.palette.highlight, highlightTint); 60 | } 61 | 62 | CheckDelegate { 63 | anchors.fill: parent 64 | anchors.margins: 2 65 | checkState: control.checkState 66 | color: control.palette.buttonText 67 | } 68 | 69 | Rectangle { 70 | anchors.fill: parent 71 | anchors.margins: -1 72 | radius: parent.radius + 1 73 | color: "transparent" 74 | 75 | MotionBehavior on border.color { ColorAnimation { duration: 60 } } 76 | border.color: control.visualFocus ? Qt.alpha(control.palette.highlight, 0.8) : "transparent" 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/style/TextField.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2 | import QtQuick.Templates as T 3 | import org.hyprland.style.impl 4 | 5 | import QtQuick.Controls.impl 6 | 7 | T.TextField { 8 | id: control 9 | 10 | implicitWidth: implicitBackgroundWidth + leftInset + rightInset 11 | || Math.max(contentWidth, placeholder.implicitWidth) + leftPadding + rightPadding 12 | implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset, 13 | contentHeight + topPadding + bottomPadding, 14 | placeholder.implicitHeight + topPadding + bottomPadding) 15 | 16 | padding: 6 17 | 18 | color: control.palette.text 19 | selectionColor: control.palette.highlight 20 | selectedTextColor: control.palette.highlightedText 21 | placeholderTextColor: control.palette.placeholderText 22 | verticalAlignment: TextInput.AlignVCenter 23 | 24 | Text { 25 | id: placeholder 26 | x: control.leftPadding 27 | y: control.topPadding 28 | width: control.width - (control.leftPadding + control.rightPadding) 29 | height: control.height - (control.topPadding + control.bottomPadding) 30 | 31 | text: control.placeholderText 32 | font: control.font 33 | color: control.placeholderTextColor 34 | verticalAlignment: control.verticalAlignment 35 | visible: !control.length && !control.preeditText && (!control.activeFocus || control.horizontalAlignment !== Qt.AlignHCenter) 36 | elide: Text.ElideRight 37 | renderType: control.renderType 38 | } 39 | 40 | background: Rectangle { 41 | implicitWidth: 200 42 | implicitHeight: 30 43 | 44 | radius: { 45 | switch (HyprlandStyle.roundness) { 46 | case 0: return 0; 47 | case 1: return 4; 48 | case 2: return 8; 49 | case 3: return 16; 50 | } 51 | } 52 | 53 | border.width: HyprlandStyle.borderWidth 54 | 55 | color: control.palette.base 56 | 57 | MotionBehavior on border.color { ColorAnimation { duration: 60 } } 58 | border.color: { 59 | let highlightTint = control.activeFocus ? 1.0 : (control.enabled && control.hovered) || control.highlighted ? 0.6 : 0.0; 60 | 61 | const base = HyprlandStyle.lightenOrDarken(control.palette.button, 1.4); 62 | return HyprlandStyle.overlay(base, control.palette.highlight, highlightTint); 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/style/impl/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set_source_files_properties(HyprlandStyle.qml PROPERTIES QT_QML_SINGLETON_TYPE TRUE) 2 | 3 | qt_add_library(hyprland-quick-style-impl SHARED 4 | hyprlandstyle.cpp 5 | checkdelegate.cpp 6 | ) 7 | 8 | qt_add_qml_module(hyprland-quick-style-impl 9 | URI org.hyprland.style.impl 10 | VERSION 0.1 11 | QML_FILES 12 | HyprlandStyle.qml 13 | MotionBehavior.qml 14 | ) 15 | 16 | target_link_libraries(hyprland-quick-style-impl PRIVATE Qt::Quick PkgConfig::hyprlang) 17 | 18 | install_qml_module(hyprland-quick-style-impl) 19 | -------------------------------------------------------------------------------- /src/style/impl/HyprlandStyle.qml: -------------------------------------------------------------------------------- 1 | pragma Singleton 2 | import QtQuick 3 | 4 | HyprlandStyleBase { 5 | id: root 6 | 7 | function flat(color: color, flat: bool): color { 8 | return flat ? root.transparent(color) : color; 9 | } 10 | 11 | function isDark(cg: ColorGroup): bool { 12 | return cg.windowText.hsvValue > cg.window.hsvValue; 13 | } 14 | 15 | function lightenOrDarken(color: color, factor: real): color { 16 | return color.hsvValue > 0.5 ? Qt.darker(color, factor) : Qt.lighter(color, factor); 17 | } 18 | 19 | function overlay(base: color, tint: color, tintOpacity: real): color { 20 | return Qt.tint(base, Qt.alpha(tint, tintOpacity)); 21 | } 22 | 23 | function scaledColor(cg: ColorGroup, index: int): color { 24 | switch (index * (root.isDark(cg) ? 1 : -1)) { 25 | case -2: 26 | return cg.light; 27 | case -1: 28 | return cg.midlight; 29 | case 1: 30 | return cg.mid; 31 | case 2: 32 | return cg.dark; 33 | } 34 | } 35 | 36 | // ColorAnimation animates all properties of a color, instead of mixing normally, so 37 | // transparency has to have the same RGB. 38 | function transparent(color: color): color { 39 | return Qt.alpha(color, 0.0); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/style/impl/MotionBehavior.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2 | 3 | Behavior { 4 | enabled: !HyprlandStyle.reduceMotion 5 | } 6 | -------------------------------------------------------------------------------- /src/style/impl/checkdelegate.cpp: -------------------------------------------------------------------------------- 1 | #include "checkdelegate.hpp" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | namespace hyprqml::style { 9 | CheckDelegate::CheckDelegate() { 10 | QObject::connect(this, &CheckDelegate::checkStateChanged, this, &CheckDelegate::repaint); 11 | QObject::connect(this, &CheckDelegate::colorChanged, this, &CheckDelegate::repaint); 12 | } 13 | 14 | void CheckDelegate::repaint() { 15 | this->update(); 16 | } 17 | 18 | void CheckDelegate::paint(QPainter* painter) { 19 | auto size = static_cast(this->width()); // assume square and even 20 | auto middle = size / 2; 21 | 22 | switch (this->bCheckState) { 23 | case Qt::Unchecked: break; 24 | case Qt::PartiallyChecked: 25 | painter->setPen(Qt::NoPen); 26 | painter->setBrush(this->bColor.value()); 27 | painter->setRenderHint(QPainter::Antialiasing, false); 28 | painter->drawRect(1, middle - 1, size - 2, 2); 29 | break; 30 | default: 31 | QPen pen = this->bColor.value(); 32 | pen.setWidth(2); 33 | pen.setCapStyle(Qt::FlatCap); 34 | pen.setJoinStyle(Qt::MiterJoin); 35 | painter->setPen(pen); 36 | painter->setBrush(Qt::NoBrush); 37 | painter->setRenderHint(QPainter::Antialiasing, true); 38 | 39 | QPainterPath path; 40 | path.moveTo(size - 1, 2); 41 | path.lineTo(middle - 1, size - 2); 42 | path.lineTo(1, middle); 43 | painter->drawPath(path); 44 | break; 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/style/impl/checkdelegate.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | namespace hyprqml::style { 12 | class CheckDelegate : public QQuickPaintedItem { 13 | Q_OBJECT; 14 | QML_ELEMENT; 15 | Q_PROPERTY(Qt::CheckState checkState READ default WRITE default BINDABLE bindableCheckState 16 | NOTIFY checkStateChanged); 17 | 18 | Q_PROPERTY( 19 | QColor color READ default WRITE default BINDABLE bindableColor NOTIFY colorChanged); 20 | 21 | public: 22 | explicit CheckDelegate(); 23 | 24 | [[nodiscard]] QBindable bindableColor() { 25 | return &this->bColor; 26 | } 27 | [[nodiscard]] QBindable bindableCheckState() { 28 | return &this->bCheckState; 29 | } 30 | 31 | void paint(QPainter* painter) override; 32 | 33 | signals: 34 | void colorChanged(); 35 | void checkStateChanged(); 36 | 37 | private slots: 38 | void repaint(); 39 | 40 | private: 41 | Q_OBJECT_BINDABLE_PROPERTY(CheckDelegate, QColor, bColor, &CheckDelegate::colorChanged); 42 | Q_OBJECT_BINDABLE_PROPERTY(CheckDelegate, Qt::CheckState, bCheckState, 43 | &CheckDelegate::checkStateChanged); 44 | }; 45 | } 46 | -------------------------------------------------------------------------------- /src/style/impl/hyprlandstyle.cpp: -------------------------------------------------------------------------------- 1 | #include "hyprlandstyle.hpp" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | namespace { 14 | Q_LOGGING_CATEGORY(logStyle, "hyprland.style", QtWarningMsg); 15 | } 16 | 17 | namespace hyprqml::style { 18 | HyprlandStyleBase::HyprlandStyleBase() { 19 | QObject::connect(&this->configWatcher, &QFileSystemWatcher::fileChanged, this, 20 | &HyprlandStyleBase::fileChanged); 21 | QObject::connect(&this->configWatcher, &QFileSystemWatcher::directoryChanged, this, 22 | &HyprlandStyleBase::directoryChanged); 23 | 24 | auto basePaths = QList(); 25 | auto home = qEnvironmentVariable("XDG_CONFIG_HOME"); 26 | auto configDirs = qEnvironmentVariable("XDG_CONFIG_DIRS"); 27 | 28 | if (home.isEmpty()) 29 | basePaths << qEnvironmentVariable("HOME") % "/.config"; 30 | else 31 | basePaths << home; 32 | 33 | if (configDirs.isEmpty()) 34 | basePaths << "/etc/xdg"; 35 | else 36 | basePaths << configDirs.split(':'); 37 | 38 | qCDebug(logStyle) << "Hyprland style configuration paths:" << basePaths; 39 | 40 | this->configPath = basePaths.first() % "/hypr/application-style.conf"; 41 | for (const auto& basePath : basePaths) { 42 | auto path = basePath % "/hypr/application-style.conf"; 43 | 44 | if (QFileInfo(path).isFile()) { 45 | this->configPath = path; 46 | break; 47 | } 48 | } 49 | 50 | this->configWatcher.addPath(this->configPath); 51 | this->configWatcher.addPath(QFileInfo(this->configPath).dir().path()); 52 | this->loadConfig(); 53 | } 54 | 55 | void HyprlandStyleBase::loadConfig() { 56 | qCDebug(logStyle) << "Reloading configuration from" << this->configPath; 57 | 58 | Hyprlang::INT roundness = 1; 59 | Hyprlang::INT borderWidth = 1; 60 | Hyprlang::INT reduceMotion = 0; 61 | 62 | try { 63 | auto config = Hyprlang::CConfig(this->configPath.toStdString().c_str(), {}); 64 | 65 | config.addConfigValue("roundness", roundness); 66 | config.addConfigValue("border_width", borderWidth); 67 | config.addConfigValue("reduce_motion", reduceMotion); 68 | 69 | config.commence(); 70 | config.parse(); 71 | 72 | roundness = std::any_cast(config.getConfigValue("roundness")); 73 | borderWidth = std::any_cast(config.getConfigValue("border_width")); 74 | reduceMotion = std::any_cast(config.getConfigValue("reduce_motion")); 75 | } catch (...) {} // NOLINT 76 | 77 | if (roundness < 0 || roundness > 3) { 78 | qCWarning(logStyle) << "Invalid value" << roundness 79 | << "for roundness. Must be in range 0-3."; 80 | roundness = 1; 81 | } 82 | 83 | if (borderWidth < 0 || borderWidth > 3) { 84 | qCWarning(logStyle) << "Invalid value" << borderWidth 85 | << "for border_width. Must be in range 0-3."; 86 | borderWidth = 1; 87 | } 88 | 89 | this->bRoundness = static_cast(roundness); 90 | this->bBorderWidth = static_cast(borderWidth); 91 | this->bReduceMotion = reduceMotion; 92 | } 93 | 94 | void HyprlandStyleBase::fileChanged() { 95 | if (!this->configWatcher.files().contains(this->configPath)) { 96 | this->configWatcher.addPath(this->configPath); 97 | return; 98 | } 99 | 100 | this->loadConfig(); 101 | } 102 | 103 | void HyprlandStyleBase::directoryChanged() { 104 | if (!this->configWatcher.files().contains(this->configPath) && 105 | QFileInfo(this->configPath).isFile()) { 106 | this->configWatcher.addPath(this->configPath); 107 | this->loadConfig(); 108 | } 109 | 110 | // if the directory was deleted we stop watching 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/style/impl/hyprlandstyle.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | namespace hyprqml::style { 10 | class HyprlandStyleBase : public QObject { 11 | Q_OBJECT; 12 | QML_ELEMENT; 13 | // writes are mostly intended for tester 14 | Q_PROPERTY(int roundness READ default WRITE default BINDABLE bindableRoundness NOTIFY 15 | roundnessChanged); 16 | Q_PROPERTY(int borderWidth READ default WRITE default BINDABLE bindableBorderWidth NOTIFY 17 | borderWidthChanged); 18 | Q_PROPERTY(bool reduceMotion READ default WRITE default BINDABLE bindableReduceMotion NOTIFY 19 | reduceMotionChanged); 20 | 21 | public: 22 | explicit HyprlandStyleBase(); 23 | 24 | [[nodiscard]] QBindable bindableRoundness() { 25 | return &this->bRoundness; 26 | } 27 | [[nodiscard]] QBindable bindableBorderWidth() { 28 | return &this->bBorderWidth; 29 | } 30 | [[nodiscard]] QBindable bindableReduceMotion() { 31 | return &this->bReduceMotion; 32 | } 33 | 34 | signals: 35 | void roundnessChanged(); 36 | void borderWidthChanged(); 37 | void reduceMotionChanged(); 38 | 39 | private slots: 40 | void fileChanged(); 41 | void directoryChanged(); 42 | 43 | private: 44 | void loadConfig(); 45 | 46 | Q_OBJECT_BINDABLE_PROPERTY(HyprlandStyleBase, int, bRoundness, 47 | &HyprlandStyleBase::roundnessChanged); 48 | Q_OBJECT_BINDABLE_PROPERTY(HyprlandStyleBase, int, bBorderWidth, 49 | &HyprlandStyleBase::borderWidthChanged); 50 | Q_OBJECT_BINDABLE_PROPERTY(HyprlandStyleBase, bool, bReduceMotion, 51 | &HyprlandStyleBase::reduceMotionChanged); 52 | 53 | QString configPath; 54 | QFileSystemWatcher configWatcher; 55 | }; 56 | } 57 | -------------------------------------------------------------------------------- /src/style/plugin.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | void qml_register_types_org_hyprland_style(); // NOLINT 11 | 12 | class HyprlandQuickStylePlugin : public QQmlEngineExtensionPlugin { 13 | Q_OBJECT; 14 | Q_PLUGIN_METADATA(IID QQmlEngineExtensionInterface_iid); 15 | 16 | public: 17 | HyprlandQuickStylePlugin(QObject* parent = nullptr) : QQmlEngineExtensionPlugin(parent) { 18 | volatile auto registration = &qml_register_types_org_hyprland_style; 19 | Q_UNUSED(registration); 20 | } 21 | 22 | void initializeEngine(QQmlEngine* /*unused*/, const char* /*unused*/) override { 23 | // Works around a qt6ct bug that prevents controls from receiving hover state changes 24 | // https://github.com/trialuser02/qt6ct/blob/55dba8704c0a748b0ce9f2d3cc2cf200ca3db464/src/qt6ct-qtplugin/qt6ctplatformtheme.cpp#L307 25 | // Note the missing `HoverEffects`. 26 | QGuiApplication::styleHints()->setUseHoverEffects(true); 27 | } 28 | }; 29 | 30 | #include "plugin.moc" 31 | -------------------------------------------------------------------------------- /src/style/test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | qt_add_executable(style-test main.cpp) 2 | 3 | qt_add_qml_module(style-test 4 | URI org.hyprland.style.test 5 | IMPORTS 6 | org.hyprland.style 7 | QML_FILES 8 | main.qml 9 | ) 10 | 11 | target_link_libraries(style-test PRIVATE Qt::Quick Qt::QuickControls2) 12 | -------------------------------------------------------------------------------- /src/style/test/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | int main(int argc, char** argv) { 10 | auto app = QGuiApplication(argc, argv); 11 | 12 | QGuiApplication::setApplicationName("Hyprland style gallery"); 13 | 14 | if (qEnvironmentVariableIsEmpty("QT_QUICK_CONTROLS_STYLE")) 15 | QQuickStyle::setStyle("org.hyprland.style"); 16 | 17 | QQmlApplicationEngine engine; 18 | 19 | QObject::connect( 20 | &engine, &QQmlApplicationEngine::objectCreationFailed, &app, 21 | []() { QCoreApplication::exit(-1); }, Qt::QueuedConnection); 22 | 23 | engine.load("qrc:/qt/qml/org/hyprland/style/test/main.qml"); 24 | 25 | return QGuiApplication::exec(); 26 | } 27 | -------------------------------------------------------------------------------- /src/style/test/main.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2 | import QtQuick.Window 3 | import QtQuick.Layouts 4 | import QtQuick.Controls 5 | import org.hyprland.style.impl 6 | 7 | ApplicationWindow { 8 | id: window 9 | visible: true 10 | 11 | ScrollView { 12 | anchors.fill: parent 13 | ColumnLayout { 14 | RowLayout { 15 | component SettingSlider: ColumnLayout { 16 | property alias text: label.text 17 | property alias from: slider.from 18 | property alias to: slider.to 19 | property alias value: slider.value 20 | 21 | Label { 22 | id: label 23 | Layout.alignment: Qt.AlignHCenter 24 | } 25 | 26 | Slider { 27 | id: slider 28 | implicitWidth: 100 29 | } 30 | } 31 | 32 | ComboBox { 33 | model: ["Sharp", "Slightly Round", "Round", "Very Round"] 34 | 35 | Component.onCompleted: { 36 | this.currentIndex = HyprlandStyle.roundness; 37 | HyprlandStyle.roundness = Qt.binding(() => this.currentIndex); 38 | } 39 | } 40 | 41 | ComboBox { 42 | model: ["No Border", "Thin", "Thick"] 43 | 44 | Component.onCompleted: { 45 | this.currentIndex = HyprlandStyle.borderWidth; 46 | HyprlandStyle.borderWidth = Qt.binding(() => this.currentIndex); 47 | } 48 | } 49 | 50 | CheckBox { 51 | text: "Reduce motion" 52 | 53 | Component.onCompleted: { 54 | this.checkState = HyprlandStyle.reduceMotion ? Qt.Checked : Qt.Unchecked 55 | HyprlandStyle.reduceMotion = Qt.binding(() => this.checkState == Qt.Checked); 56 | } 57 | } 58 | } 59 | 60 | RowLayout { 61 | ColumnLayout { 62 | Layout.maximumWidth: 300 63 | Label { text: "Palette" } 64 | 65 | SystemPalette { id: activePalette; colorGroup: SystemPalette.Active } 66 | SystemPalette { id: inactivePalette; colorGroup: SystemPalette.Inactive } 67 | SystemPalette { id: disabledPalette; colorGroup: SystemPalette.Disabled } 68 | 69 | component PaletteColor: Rectangle { 70 | required property string name; 71 | required property var cg; 72 | implicitWidth: 50 73 | implicitHeight: 20 74 | color: cg[name] 75 | } 76 | 77 | component PaletteItem: RowLayout { 78 | id: pi 79 | 80 | property alias text: label.text 81 | property string color; 82 | 83 | Label { 84 | id: label 85 | Layout.fillWidth: true 86 | } 87 | 88 | PaletteColor { name: pi.color; cg: activePalette } 89 | PaletteColor { name: pi.color; cg: inactivePalette } 90 | PaletteColor { name: pi.color; cg: disabledPalette } 91 | } 92 | 93 | PaletteItem { text: "Light"; color: "light" } 94 | PaletteItem { text: "Midlight"; color: "midlight" } 95 | PaletteItem { text: "Button"; color: "button" } 96 | PaletteItem { text: "Window"; color: "window" } 97 | PaletteItem { text: "Mid"; color: "mid" } 98 | PaletteItem { text: "Dark"; color: "dark" } 99 | PaletteItem { text: "Base"; color: "base" } 100 | PaletteItem { text: "Text"; color: "text" } 101 | PaletteItem { text: "Button Text"; color: "buttonText" } 102 | PaletteItem { text: "Window Text"; color: "windowText" } 103 | PaletteItem { text: "Highli Text"; color: "highlightedText" } 104 | PaletteItem { text: "Placeholder"; color: "placeholderText" } 105 | PaletteItem { text: "Shadow"; color: "shadow" } 106 | PaletteItem { text: "Highlight"; color: "highlight" } 107 | PaletteItem { text: "Accent"; color: "accent" } 108 | } 109 | 110 | ColumnLayout { 111 | Layout.maximumWidth: 200 112 | Label { text: "Button" } 113 | 114 | component TestButton: Button { Layout.fillWidth: true } 115 | 116 | TestButton { text: "Normal" } 117 | TestButton { text: "Flat"; flat: true; } 118 | TestButton { text: "Highlighted"; highlighted: true } 119 | TestButton { text: "Flat Highlighted"; flat: true; highlighted: true } 120 | TestButton { text: "Checked"; checkable: true; checked: true } 121 | TestButton { text: "Flat Checked"; flat: true; checkable: true; checked: true } 122 | TestButton { text: "Down"; down: true } 123 | TestButton { text: "Flat Down"; flat: true; down: true } 124 | TestButton { text: "Disabled"; enabled: false } 125 | TestButton { text: "With Icon"; icon.name: "folder" } 126 | TestButton { text: "With Mirror Icon"; icon.name: "folder"; LayoutMirroring.enabled: true } 127 | TestButton { icon.name: "folder" } 128 | Item { Layout.fillHeight: true } 129 | } 130 | 131 | ColumnLayout { 132 | Layout.maximumWidth: 200 133 | Label { text: "CheckBox" } 134 | 135 | component TestCB: CheckBox { Layout.fillWidth: true } 136 | 137 | TestCB { text: "Normal" } 138 | TestCB { text: "Down"; down: true } 139 | TestCB { text: "Mirrored"; LayoutMirroring.enabled: true } 140 | TestCB { text: "Checked"; checked: true } 141 | TestCB { text: "Partially Checked"; tristate: true; checkState: Qt.PartiallyChecked } 142 | TestCB { text: "Disabled"; enabled: false } 143 | Item { Layout.fillHeight: true } 144 | } 145 | 146 | ColumnLayout { 147 | Layout.maximumWidth: 200 148 | Label { text: "TextField" } 149 | 150 | component TestField: TextField { Layout.fillWidth: true } 151 | 152 | TestField { text: "Normal" } 153 | TestField { text: "Disabled"; enabled: false } 154 | TestField { placeholderText: "Placeholder" } 155 | TestField { placeholderText: "Disabled Placeholder"; enabled: false } 156 | Item { Layout.fillHeight: true } 157 | } 158 | 159 | Item { Layout.fillWidth: true } 160 | } 161 | } 162 | } 163 | } 164 | --------------------------------------------------------------------------------