From d5e83aa9dea65cef5c028e6de4b2fc60f4c9aaa1 Mon Sep 17 00:00:00 2001 From: LemmyCook Date: Mon, 1 Sep 2025 22:27:49 -0400 Subject: [PATCH] Wallpaper: added fill color that may show up around wallpaper (depends on fillMode) + New Widget NColorPicker + New Widget NButton --- Commons/Settings.qml | 1 + Modules/Background/Background.qml | 11 +- Modules/SettingsPanel/Tabs/WallpaperTab.qml | 14 ++ Services/WallpaperService.qml | 2 +- Widgets/NButton.qml | 200 ++++++++++++++++++++ Widgets/NColorPicker.qml | 186 ++++++++++++++++++ Widgets/NTextInput.qml | 2 + 7 files changed, 411 insertions(+), 5 deletions(-) create mode 100644 Widgets/NButton.qml create mode 100644 Widgets/NColorPicker.qml diff --git a/Commons/Settings.qml b/Commons/Settings.qml index 967951f..afac08c 100644 --- a/Commons/Settings.qml +++ b/Commons/Settings.qml @@ -175,6 +175,7 @@ Singleton { property bool enableMultiMonitorDirectories: false property bool setWallpaperOnAllMonitors: true property string fillMode: "crop" + property color fillColor: "#000000" property bool randomEnabled: false property int randomIntervalSec: 300 // 5 min property int transitionDuration: 1500 // 1500 ms diff --git a/Modules/Background/Background.qml b/Modules/Background/Background.qml index 5b5112b..9249b1f 100644 --- a/Modules/Background/Background.qml +++ b/Modules/Background/Background.qml @@ -41,6 +41,9 @@ Variants { // Fillmode default is "crop" property real fillMode: 1.0 + property vector4d fillColor: Qt.vector4d(Settings.data.wallpaper.fillColor.r, + Settings.data.wallpaper.fillColor.g, + Settings.data.wallpaper.fillColor.b, 1.0) // On startup assign wallpaper immediately Component.onCompleted: { @@ -127,13 +130,13 @@ Variants { // Fill mode properties property real fillMode: root.fillMode + property vector4d fillColor: root.fillColor property real imageWidth1: source1.sourceSize.width property real imageHeight1: source1.sourceSize.height property real imageWidth2: source2.sourceSize.width property real imageHeight2: source2.sourceSize.height property real screenWidth: width property real screenHeight: height - property vector4d fillColor: Qt.vector4d(0.0, 0.0, 0.0, 1.0) // Black fragmentShader: Qt.resolvedUrl("../../Shaders/qsb/wp_fade.frag.qsb") } @@ -152,13 +155,13 @@ Variants { // Fill mode properties property real fillMode: root.fillMode + property vector4d fillColor: root.fillColor property real imageWidth1: source1.sourceSize.width property real imageHeight1: source1.sourceSize.height property real imageWidth2: source2.sourceSize.width property real imageHeight2: source2.sourceSize.height property real screenWidth: width property real screenHeight: height - property vector4d fillColor: Qt.vector4d(0.0, 0.0, 0.0, 1.0) // Black fragmentShader: Qt.resolvedUrl("../../Shaders/qsb/wp_wipe.frag.qsb") } @@ -179,13 +182,13 @@ Variants { // Fill mode properties property real fillMode: root.fillMode + property vector4d fillColor: root.fillColor property real imageWidth1: source1.sourceSize.width property real imageHeight1: source1.sourceSize.height property real imageWidth2: source2.sourceSize.width property real imageHeight2: source2.sourceSize.height property real screenWidth: width property real screenHeight: height - property vector4d fillColor: Qt.vector4d(0.0, 0.0, 0.0, 1.0) // Black fragmentShader: Qt.resolvedUrl("../../Shaders/qsb/wp_disc.frag.qsb") } @@ -206,13 +209,13 @@ Variants { // Fill mode properties property real fillMode: root.fillMode + property vector4d fillColor: root.fillColor property real imageWidth1: source1.sourceSize.width property real imageHeight1: source1.sourceSize.height property real imageWidth2: source2.sourceSize.width property real imageHeight2: source2.sourceSize.height property real screenWidth: width property real screenHeight: height - property vector4d fillColor: Qt.vector4d(0.0, 0.0, 0.0, 1.0) // Black fragmentShader: Qt.resolvedUrl("../../Shaders/qsb/wp_stripes.frag.qsb") } diff --git a/Modules/SettingsPanel/Tabs/WallpaperTab.qml b/Modules/SettingsPanel/Tabs/WallpaperTab.qml index b758cf9..4ac5093 100644 --- a/Modules/SettingsPanel/Tabs/WallpaperTab.qml +++ b/Modules/SettingsPanel/Tabs/WallpaperTab.qml @@ -105,6 +105,20 @@ ColumnLayout { onSelected: key => Settings.data.wallpaper.fillMode = key } + RowLayout { + NLabel { + label: "Fill Color" + description: "Choose a fill color that may appear behind the wallpaper." + Layout.alignment: Qt.AlignTop + } + + NColorPicker { + selectedColor: Settings.data.wallpaper.fillColor + onColorSelected: color => Settings.data.wallpaper.fillColor = color + onColorCancelled: selectedColor = Settings.data.wallpaper.fillColor + } + } + // Transition Type NComboBox { label: "Transition Type" diff --git a/Services/WallpaperService.qml b/Services/WallpaperService.qml index a8a5e19..705fb67 100644 --- a/Services/WallpaperService.qml +++ b/Services/WallpaperService.qml @@ -62,7 +62,7 @@ Singleton { // Maintains aspect ratio ListElement { key: "crop" - name: "Crop (Fill/Cover)" + name: "Crop (Fill)" uniform: 1.0 } // Scales image to fit entirely within screen diff --git a/Widgets/NButton.qml b/Widgets/NButton.qml new file mode 100644 index 0000000..b107329 --- /dev/null +++ b/Widgets/NButton.qml @@ -0,0 +1,200 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import qs.Commons + +Rectangle { + id: root + + // Public properties + property string text: "" + property string icon: "" + property color backgroundColor: Color.mPrimary + property color textColor: Color.mOnPrimary + property color hoverColor: Color.mTertiary + property color pressColor: Color.mSecondary + property bool enabled: true + property int fontSize: Style.fontSizeM * scaling + property int iconSize: Style.fontSizeL * scaling + property bool outlined: false + property real customWidth: -1 + property real customHeight: -1 + + // Signals + signal clicked + + // Internal properties + property bool hovered: false + property bool pressed: false + + // Dimensions + implicitWidth: customWidth > 0 ? customWidth : contentRow.implicitWidth + (Style.marginL * 2 * scaling) + implicitHeight: customHeight > 0 ? customHeight : Math.max(Style.baseWidgetSize * scaling, + contentRow.implicitHeight + (Style.marginM * scaling)) + + // Appearance + radius: Style.radiusS * scaling + color: { + if (!enabled) + return outlined ? Color.transparent : Qt.lighter(Color.mSurfaceVariant, 1.2) + if (pressed) + return pressColor + if (hovered) + return hoverColor + return outlined ? Color.transparent : backgroundColor + } + + border.width: outlined ? Math.max(1, Style.borderS * scaling) : 0 + border.color: { + if (!enabled) + return Color.mOutline + if (pressed || hovered) + return backgroundColor + return outlined ? backgroundColor : Color.transparent + } + + opacity: enabled ? 1.0 : 0.6 + + Behavior on color { + ColorAnimation { + duration: Style.animationFast + easing.type: Easing.OutCubic + } + } + + Behavior on border.color { + ColorAnimation { + duration: Style.animationFast + easing.type: Easing.OutCubic + } + } + + // Content + RowLayout { + id: contentRow + anchors.centerIn: parent + spacing: Style.marginS * scaling + + // Icon (optional) + NIcon { + visible: root.icon !== "" + text: root.icon + font.pointSize: root.iconSize + color: { + if (!root.enabled) + return Color.mOnSurfaceVariant + if (root.outlined) { + if (root.pressed || root.hovered) + return root.backgroundColor + return root.backgroundColor + } + return root.textColor + } + + Behavior on color { + ColorAnimation { + duration: Style.animationFast + easing.type: Easing.OutCubic + } + } + } + + // Text + NText { + visible: root.text !== "" + text: root.text + font.pointSize: root.fontSize + font.weight: Style.fontWeightBold + color: { + if (!root.enabled) + return Color.mOnSurfaceVariant + if (root.outlined) { + if (root.pressed || root.hovered) + return root.textColor + return root.backgroundColor + } + return root.textColor + } + + Behavior on color { + ColorAnimation { + duration: Style.animationFast + easing.type: Easing.OutCubic + } + } + } + } + + // Ripple effect + Rectangle { + id: ripple + anchors.centerIn: parent + width: 0 + height: width + radius: width / 2 + color: root.outlined ? root.backgroundColor : root.textColor + opacity: 0 + + ParallelAnimation { + id: rippleAnimation + + NumberAnimation { + target: ripple + property: "width" + from: 0 + to: Math.max(root.width, root.height) * 2 + duration: Style.animationFast + easing.type: Easing.OutCubic + } + + SequentialAnimation { + NumberAnimation { + target: ripple + property: "opacity" + from: 0 + to: 0.2 + duration: 100 + easing.type: Easing.OutCubic + } + + NumberAnimation { + target: ripple + property: "opacity" + from: 0.2 + to: 0 + duration: 300 + easing.type: Easing.InCubic + } + } + } + } + + // Mouse interaction + MouseArea { + id: mouseArea + anchors.fill: parent + enabled: root.enabled + hoverEnabled: true + cursorShape: root.enabled ? Qt.PointingHandCursor : Qt.ArrowCursor + + onEntered: root.hovered = true + onExited: { + root.hovered = false + root.pressed = false + } + onPressed: { + root.pressed = true + rippleAnimation.restart() + } + onReleased: { + if (containsMouse) { + root.clicked() + } + root.pressed = false + } + onCanceled: { + root.pressed = false + root.hovered = false + } + } +} diff --git a/Widgets/NColorPicker.qml b/Widgets/NColorPicker.qml new file mode 100644 index 0000000..8a26f0c --- /dev/null +++ b/Widgets/NColorPicker.qml @@ -0,0 +1,186 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import qs.Commons +import qs.Widgets + +Rectangle { + id: root + + property color selectedColor: "#000000" + property bool expanded: false + + signal colorSelected(color color) + signal colorCancelled + + implicitWidth: expanded ? 320 * scaling : 150 * scaling + implicitHeight: expanded ? 300 * scaling : 40 * scaling + + radius: Style.radiusM * scaling + color: Color.mSurface + border.color: Color.mOutline + border.width: Math.max(1, Style.borderS * scaling) + + Behavior on implicitWidth { + NumberAnimation { + duration: Style.animationFast + } + } + + Behavior on implicitHeight { + NumberAnimation { + duration: Style.animationFast + } + } + + // Collapsed view - just show current color + MouseArea { + visible: !root.expanded + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + onClicked: root.expanded = true + + RowLayout { + anchors.fill: parent + anchors.margins: Style.marginS * scaling + spacing: Style.marginS * scaling + + Rectangle { + Layout.preferredWidth: 24 * scaling + Layout.preferredHeight: 24 * scaling + radius: Layout.preferredWidth * 0.5 + color: root.selectedColor + border.color: Color.mOutline + border.width: Math.max(1, Style.borderS * scaling) + } + + NText { + text: root.selectedColor.toString().toUpperCase() + font.family: Settings.data.ui.fontFixed + Layout.fillWidth: true + } + + NIcon { + text: "palette" + color: Color.mOnSurfaceVariant + } + } + } + + // Expanded view - color selection + ColumnLayout { + visible: root.expanded + anchors.fill: parent + anchors.margins: Style.marginM * scaling + spacing: Style.marginS * scaling + + // Header + RowLayout { + Layout.fillWidth: true + + NText { + text: "Select Color" + font.weight: Style.fontWeightBold + Layout.fillWidth: true + } + + NIconButton { + icon: "close" + onClicked: root.expanded = false + } + } + + // Preset colors grid + Grid { + columns: 9 + spacing: Style.marginXS * scaling + Layout.fillWidth: true + + property var presetColors: ["#F44336", "#E91E63", "#9C27B0", "#673AB7", "#3F51B5", "#2196F3", "#03A9F4", "#00BCD4", "#009688", "#4CAF50", "#8BC34A", "#CDDC39", "#FFEB3B", "#FFC107", "#FF9800", "#FF5722", "#795548", "#9E9E9E", "#607D8B", "#000000", "#FFFFFF", "#F5F5F5", "#E0E0E0", "#9E9E9E"] + + Repeater { + model: parent.presetColors + + Rectangle { + width: Math.round(29 * scaling) + height: width + radius: Style.radiusXS * scaling + color: modelData + border.color: root.selectedColor === modelData ? Color.mPrimary : Color.mOutline + border.width: root.selectedColor === modelData ? 2 : 1 + + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + onClicked: { + root.selectedColor = modelData + // root.colorSelected(modelData) + } + } + } + } + } + + // Custom color input + RowLayout { + Layout.fillWidth: true + spacing: Style.marginS * scaling + + NTextInput { + id: hexInput + label: "Hex Color" + text: root.selectedColor.toString().toUpperCase() + fontFamily: Settings.data.ui.fontFixed + Layout.minimumWidth: 100 * scaling + onEditingFinished: { + if (/^#[0-9A-F]{6}$/i.test(text)) { + root.selectedColor = text + root.colorSelected(text) + } + } + } + + Rectangle { + Layout.preferredWidth: 32 * scaling + Layout.preferredHeight: 32 * scaling + radius: Layout.preferredWidth * 0.5 + color: root.selectedColor + border.color: Color.mOutline + border.width: 1 + Layout.alignment: Qt.AlignBottom + Layout.bottomMargin: 5 * scaling + } + } + + // Action buttons row + RowLayout { + Layout.fillWidth: true + spacing: Style.marginS * scaling + + Item { + Layout.fillWidth: true + } // Spacer + + NButton { + text: "Cancel" + outlined: true + customHeight: Style.baseWidgetSize * scaling + fontSize: Style.fontSizeS * scaling + onClicked: { + root.colorCancelled() + root.expanded = false + } + } + + NButton { + text: "Apply" + customHeight: Style.baseWidgetSize * scaling + fontSize: Style.fontSizeS * scaling + onClicked: { + root.colorSelected(root.selectedColor) + root.expanded = false + } + } + } + } +} diff --git a/Widgets/NTextInput.qml b/Widgets/NTextInput.qml index adb1f95..f05cef5 100644 --- a/Widgets/NTextInput.qml +++ b/Widgets/NTextInput.qml @@ -14,6 +14,7 @@ ColumnLayout { property int inputMaxWidth: 420 * scaling property color labelColor: Color.mOnSurface property color descriptionColor: Color.mOnSurfaceVariant + property string fontFamily: Settings.data.ui.fontDefault property alias text: input.text property alias placeholderText: input.placeholderText @@ -74,6 +75,7 @@ ColumnLayout { color: Color.mOnSurface placeholderTextColor: Color.mOnSurfaceVariant background: null + font.family: fontFamily font.pointSize: Style.fontSizeS * scaling onEditingFinished: root.editingFinished() }