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