diff --git a/Modules/Bar/Widgets/MediaMini.qml b/Modules/Bar/Widgets/MediaMini.qml index 43dca611..a4ca5f1b 100644 --- a/Modules/Bar/Widgets/MediaMini.qml +++ b/Modules/Bar/Widgets/MediaMini.qml @@ -409,16 +409,17 @@ Item { anchors.fill: parent anchors.margins: showProgressRing ? (3 * scaling) : 0.5 // Adjusted to align with progress circle better - NImageCircled { + NImageRounded { id: trackArt anchors.fill: parent anchors.margins: showProgressRing ? 0 : -1 * scaling // Add negative margin to make album art larger when no progress ring + radius: width * 0.5 visible: showAlbumArt && hasActivePlayer imagePath: MediaService.trackArtUrl fallbackIcon: MediaService.isPlaying ? "media-pause" : "media-play" fallbackIconSize: showProgressRing ? 10 : 12 // Larger fallback icon when no progress ring borderWidth: 0 - border.color: Color.transparent + borderColor: Color.transparent z: 1 // In front of the progress circle } @@ -649,9 +650,10 @@ Item { z: 1 // Above the visualizer and progress ring // Album Art - NImageCircled { + NImageRounded { anchors.fill: parent visible: showAlbumArt && hasActivePlayer + radius: width * 0.5 imagePath: MediaService.trackArtUrl fallbackIcon: MediaService.isPlaying ? "media-pause" : "media-play" fallbackIconSize: 12 diff --git a/Modules/LockScreen/LockScreen.qml b/Modules/LockScreen/LockScreen.qml index 1612017d..325395b4 100644 --- a/Modules/LockScreen/LockScreen.qml +++ b/Modules/LockScreen/LockScreen.qml @@ -301,10 +301,11 @@ Loader { } } - NImageCircled { + NImageRounded { anchors.centerIn: parent width: 66 height: 66 + radius: width * 0.5 imagePath: Settings.preprocessPath(Settings.data.general.avatarImage) fallbackIcon: "person" @@ -631,9 +632,10 @@ Loader { color: Color.transparent clip: true - NImageCircled { + NImageRounded { anchors.fill: parent anchors.margins: 2 + radius: width * 0.5 imagePath: MediaService.trackArtUrl fallbackIcon: "disc" fallbackIconSize: Style.fontSizeM diff --git a/Modules/Notification/Notification.qml b/Modules/Notification/Notification.qml index 6d39860a..d4d69db4 100644 --- a/Modules/Notification/Notification.qml +++ b/Modules/Notification/Notification.qml @@ -398,17 +398,16 @@ Variants { Layout.topMargin: Style.marginM Layout.bottomMargin: Style.marginM - ColumnLayout { - NImageCircled { - Layout.preferredWidth: Math.round(40 * Style.uiScaleRatio) - Layout.preferredHeight: Math.round(40 * Style.uiScaleRatio) - Layout.alignment: Qt.AlignVCenter - imagePath: model.originalImage || "" - borderColor: Color.transparent - borderWidth: 0 - fallbackIcon: "bell" - fallbackIconSize: 24 - } + NImageRounded { + Layout.preferredWidth: Math.round(40 * Style.uiScaleRatio) + Layout.preferredHeight: Math.round(40 * Style.uiScaleRatio) + Layout.alignment: Qt.AlignVCenter + radius: width * 0.5 + imagePath: model.originalImage || "" + borderColor: Color.transparent + borderWidth: 0 + fallbackIcon: "bell" + fallbackIconSize: 24 } ColumnLayout { diff --git a/Modules/Panels/ControlCenter/Cards/ProfileCard.qml b/Modules/Panels/ControlCenter/Cards/ProfileCard.qml index b37231ed..f827b506 100644 --- a/Modules/Panels/ControlCenter/Cards/ProfileCard.qml +++ b/Modules/Panels/ControlCenter/Cards/ProfileCard.qml @@ -25,9 +25,10 @@ NBox { anchors.margins: Style.marginM spacing: Style.marginM - NImageCircled { + NImageRounded { Layout.preferredWidth: Math.round(Style.baseWidgetSize * 1.25 * Style.uiScaleRatio) Layout.preferredHeight: Math.round(Style.baseWidgetSize * 1.25 * Style.uiScaleRatio) + radius: width * 0.5 imagePath: Settings.preprocessPath(Settings.data.general.avatarImage) fallbackIcon: "person" borderColor: Color.mPrimary diff --git a/Modules/Panels/Launcher/Launcher.qml b/Modules/Panels/Launcher/Launcher.qml index e8dfdcda..00537379 100644 --- a/Modules/Panels/Launcher/Launcher.qml +++ b/Modules/Panels/Launcher/Launcher.qml @@ -689,7 +689,7 @@ SmartPanel { id: imagePreview anchors.fill: parent visible: modelData.isImage && !modelData.emojiChar - imageRadius: Style.radiusM + radius: Style.radiusM // This property creates a dependency on the service's revision counter readonly property int _rev: ClipboardService.revision @@ -934,7 +934,7 @@ SmartPanel { id: gridImagePreview anchors.fill: parent visible: modelData.isImage && !modelData.emojiChar - imageRadius: Style.radiusM + radius: Style.radiusM readonly property int _rev: ClipboardService.revision diff --git a/Modules/Panels/NotificationHistory/NotificationHistoryPanel.qml b/Modules/Panels/NotificationHistory/NotificationHistoryPanel.qml index 8efde22c..4e25642c 100644 --- a/Modules/Panels/NotificationHistory/NotificationHistoryPanel.qml +++ b/Modules/Panels/NotificationHistory/NotificationHistoryPanel.qml @@ -197,10 +197,11 @@ SmartPanel { spacing: Style.marginM // Icon - NImageCircled { + NImageRounded { + anchors.verticalCenter: parent.verticalCenter width: Math.round(40 * Style.uiScaleRatio) height: Math.round(40 * Style.uiScaleRatio) - anchors.verticalCenter: parent.verticalCenter + radius: width * 0.5 imagePath: model.cachedImage || model.originalImage || "" borderColor: Color.transparent borderWidth: 0 diff --git a/Modules/Panels/Settings/Bar/WidgetSettings/ControlCenterSettings.qml b/Modules/Panels/Settings/Bar/WidgetSettings/ControlCenterSettings.qml index 7a0aa453..5f9eb339 100644 --- a/Modules/Panels/Settings/Bar/WidgetSettings/ControlCenterSettings.qml +++ b/Modules/Panels/Settings/Bar/WidgetSettings/ControlCenterSettings.qml @@ -59,10 +59,11 @@ ColumnLayout { description: I18n.tr("bar.widget-settings.control-center.icon.description") } - NImageCircled { + NImageRounded { Layout.preferredWidth: Style.fontSizeXL * 2 Layout.preferredHeight: Style.fontSizeXL * 2 Layout.alignment: Qt.AlignVCenter + radius: width * 0.5 imagePath: valueCustomIconPath visible: valueCustomIconPath !== "" } diff --git a/Modules/Panels/Settings/Tabs/AboutTab.qml b/Modules/Panels/Settings/Tabs/AboutTab.qml index 127ab7f5..f226afe6 100644 --- a/Modules/Panels/Settings/Tabs/AboutTab.qml +++ b/Modules/Panels/Settings/Tabs/AboutTab.qml @@ -189,10 +189,11 @@ ColumnLayout { Layout.preferredWidth: Style.baseWidgetSize * 2 * Style.uiScaleRatio Layout.preferredHeight: Style.baseWidgetSize * 2 * Style.uiScaleRatio - NImageCircled { - imagePath: modelData.avatar_url || "" + NImageRounded { anchors.fill: parent anchors.margins: Style.marginXS + radius: width * 0.5 + imagePath: modelData.avatar_url || "" fallbackIcon: "person" borderColor: contributorArea.containsMouse ? Color.mOnHover : Color.mPrimary borderWidth: Style.borderM diff --git a/Modules/Panels/Settings/Tabs/GeneralTab.qml b/Modules/Panels/Settings/Tabs/GeneralTab.qml index 481f4013..f111222b 100644 --- a/Modules/Panels/Settings/Tabs/GeneralTab.qml +++ b/Modules/Panels/Settings/Tabs/GeneralTab.qml @@ -21,9 +21,10 @@ ColumnLayout { spacing: Style.marginL // Avatar preview - NImageCircled { + NImageRounded { Layout.preferredWidth: 88 * Style.uiScaleRatio Layout.preferredHeight: width + radius: width * 0.5 imagePath: Settings.preprocessPath(Settings.data.general.avatarImage) fallbackIcon: "person" borderColor: Color.mPrimary diff --git a/Shaders/frag/circled_image.frag b/Shaders/frag/circled_image.frag deleted file mode 100644 index 308a9c5b..00000000 --- a/Shaders/frag/circled_image.frag +++ /dev/null @@ -1,30 +0,0 @@ -#version 450 - -layout(location = 0) in vec2 qt_TexCoord0; -layout(location = 0) out vec4 fragColor; - -layout(binding = 1) uniform sampler2D source; - -layout(std140, binding = 0) uniform buf { - mat4 qt_Matrix; - float qt_Opacity; - float imageOpacity; -} ubuf; - -void main() { - // Center coordinates around (0, 0) - vec2 uv = qt_TexCoord0 - 0.5; - - // Calculate distance from center - float distance = length(uv); - - // Create circular mask - anything beyond radius 0.5 is transparent - float mask = 1.0 - smoothstep(0.48, 0.52, distance); - - // Sample the texture - vec4 color = texture(source, qt_TexCoord0); - - // Apply the circular mask and opacity - float finalAlpha = color.a * mask * ubuf.imageOpacity * ubuf.qt_Opacity; - fragColor = vec4(color.rgb * finalAlpha, finalAlpha); -} \ No newline at end of file diff --git a/Shaders/frag/rounded_image.frag b/Shaders/frag/rounded_image.frag deleted file mode 100644 index 9d493b21..00000000 --- a/Shaders/frag/rounded_image.frag +++ /dev/null @@ -1,56 +0,0 @@ -#version 450 - -layout(location = 0) in vec2 qt_TexCoord0; -layout(location = 0) out vec4 fragColor; - -layout(binding = 1) uniform sampler2D source; - -layout(std140, binding = 0) uniform buf { - mat4 qt_Matrix; - float qt_Opacity; - // Custom properties with non-conflicting names - float itemWidth; - float itemHeight; - float cornerRadius; - float imageOpacity; -} ubuf; - -// Function to calculate the signed distance from a point to a rounded box -float roundedBoxSDF(vec2 centerPos, vec2 boxSize, float radius) { - vec2 d = abs(centerPos) - boxSize + radius; - return length(max(d, 0.0)) + min(max(d.x, d.y), 0.0) - radius; -} - -void main() { - // Get size from uniforms - vec2 itemSize = vec2(ubuf.itemWidth, ubuf.itemHeight); - float cornerRadius = ubuf.cornerRadius; - float itemOpacity = ubuf.imageOpacity; - - // Normalize coordinates to [-0.5, 0.5] range - vec2 uv = qt_TexCoord0 - 0.5; - - // Scale by aspect ratio to maintain uniform rounding - vec2 aspectRatio = itemSize / max(itemSize.x, itemSize.y); - uv *= aspectRatio; - - // Calculate half size in normalized space - vec2 halfSize = 0.5 * aspectRatio; - - // Normalize the corner radius - float normalizedRadius = cornerRadius / max(itemSize.x, itemSize.y); - - // Calculate distance to rounded rectangle - float distance = roundedBoxSDF(uv, halfSize, normalizedRadius); - - // Create smooth alpha mask - float smoothedAlpha = 1.0 - smoothstep(0.0, fwidth(distance), distance); - - // Sample the texture - vec4 color = texture(source, qt_TexCoord0); - - // Apply the rounded mask and opacity - // Make sure areas outside the rounded rect are completely transparent - float finalAlpha = color.a * smoothedAlpha * itemOpacity * ubuf.qt_Opacity; - fragColor = vec4(color.rgb * finalAlpha, finalAlpha); -} \ No newline at end of file diff --git a/Shaders/qsb/circled_image.frag.qsb b/Shaders/qsb/circled_image.frag.qsb deleted file mode 100644 index 4faa90f9..00000000 Binary files a/Shaders/qsb/circled_image.frag.qsb and /dev/null differ diff --git a/Shaders/qsb/rounded_image.frag.qsb b/Shaders/qsb/rounded_image.frag.qsb deleted file mode 100644 index 666f6434..00000000 Binary files a/Shaders/qsb/rounded_image.frag.qsb and /dev/null differ diff --git a/Widgets/NImageCircled.qml b/Widgets/NImageCircled.qml deleted file mode 100644 index c97e4440..00000000 --- a/Widgets/NImageCircled.qml +++ /dev/null @@ -1,76 +0,0 @@ -import QtQuick -import QtQuick.Effects -import Quickshell -import Quickshell.Widgets -import qs.Commons - -Rectangle { - id: root - - property string imagePath: "" - property color borderColor: Color.transparent - property real borderWidth: 0 - property string fallbackIcon: "" - property real fallbackIconSize: Style.fontSizeXXL - - color: Color.transparent - radius: parent.width * 0.5 - anchors.margins: Style.marginXXS - - Rectangle { - color: Color.transparent - anchors.fill: parent - - Image { - id: img - anchors.fill: parent - source: imagePath - visible: false // Hide since we're using it as shader source - mipmap: true - smooth: true - asynchronous: true - antialiasing: true - fillMode: Image.PreserveAspectCrop - } - - ShaderEffect { - anchors.fill: parent - - property var source: ShaderEffectSource { - sourceItem: img - hideSource: true - live: true - recursive: false - format: ShaderEffectSource.RGBA - } - - property real imageOpacity: root.opacity - fragmentShader: Qt.resolvedUrl(Quickshell.shellDir + "/Shaders/qsb/circled_image.frag.qsb") - supportsAtlasTextures: false - blending: true - } - - // Fallback icon - Loader { - active: fallbackIcon !== undefined && fallbackIcon !== "" && (imagePath === undefined || imagePath === "") - anchors.centerIn: parent - sourceComponent: NIcon { - anchors.centerIn: parent - icon: fallbackIcon - pointSize: fallbackIconSize - z: 0 - } - } - } - - // Border - Rectangle { - anchors.fill: parent - radius: parent.radius - color: Color.transparent - border.color: parent.borderColor - border.width: parent.borderWidth - antialiasing: true - z: 10 - } -} diff --git a/Widgets/NImageRounded.qml b/Widgets/NImageRounded.qml index 67d3a178..7d28fa2d 100644 --- a/Widgets/NImageRounded.qml +++ b/Widgets/NImageRounded.qml @@ -4,91 +4,56 @@ import Quickshell import Quickshell.Widgets import qs.Commons -Rectangle { +Item { id: root + property real radius: 0 property string imagePath: "" - property color borderColor: Color.transparent - property real borderWidth: 0 - property real imageRadius: width * 0.5 - property int imageFillMode: Image.PreserveAspectCrop property string fallbackIcon: "" property real fallbackIconSize: Style.fontSizeXXL + property real borderWidth: 0 + property color borderColor: Color.transparent - property real scaledRadius: imageRadius * Settings.data.general.radiusRatio + readonly property bool showFallback: (fallbackIcon !== undefined && fallbackIcon !== "") && (imagePath === undefined || imagePath === "") signal statusChanged(int status) - color: Color.transparent - radius: scaledRadius - anchors.margins: Style.marginXXS - - Rectangle { - color: Color.transparent + ClippingWrapperRectangle { anchors.fill: parent + color: Color.transparent + radius: root.radius + border.color: root.borderColor + border.width: root.borderWidth - Image { - id: img + Item { anchors.fill: parent - source: imagePath - visible: false - mipmap: true - smooth: true - asynchronous: true - antialiasing: true - fillMode: root.imageFillMode - horizontalAlignment: Image.AlignHCenter - verticalAlignment: Image.AlignVCenter - - onStatusChanged: root.statusChanged(status) - } - - ShaderEffect { - anchors.fill: parent - - property var source: ShaderEffectSource { - sourceItem: img - hideSource: true - live: true - recursive: false - format: ShaderEffectSource.RGBA - } - - property real itemWidth: root.width - property real itemHeight: root.height - property real cornerRadius: root.radius - property real imageOpacity: root.opacity - fragmentShader: Qt.resolvedUrl(Quickshell.shellDir + "/Shaders/qsb/rounded_image.frag.qsb") - - supportsAtlasTextures: false - blending: true - Rectangle { - id: background + Loader { + active: true anchors.fill: parent - color: Color.transparent - z: -1 + sourceComponent: showFallback ? fallback : image + } + + Component { + id: image + Image { + source: imagePath + mipmap: true + smooth: true + asynchronous: true + antialiasing: true + fillMode: Image.PreserveAspectCrop + onStatusChanged: root.statusChanged(status) + } + } + + Component { + id: fallback + NIcon { + anchors.centerIn: parent + icon: fallbackIcon + pointSize: fallbackIconSize + } } } - - Loader { - active: fallbackIcon !== undefined && fallbackIcon !== "" && (imagePath === undefined || imagePath === "") - anchors.centerIn: parent - sourceComponent: NIcon { - anchors.centerIn: parent - icon: fallbackIcon - pointSize: fallbackIconSize - z: 0 - } - } - } - - Rectangle { - anchors.fill: parent - radius: parent.radius - color: Color.transparent - border.color: parent.borderColor - border.width: parent.borderWidth - antialiasing: true - z: 10 } }