DropShadow: on the media card + some cleanup

This commit is contained in:
ItsLemmy
2025-11-18 19:51:54 -05:00
parent 8242b0d97d
commit 592e261eb8
9 changed files with 226 additions and 195 deletions

View File

@@ -148,7 +148,7 @@ Item {
}
// Apply shadow to the cached layer
NDropShadows {
NDropShadow {
anchors.fill: parent
source: backgroundsShape
}

View File

@@ -248,7 +248,7 @@ Variants {
}
}
NDropShadows {
NDropShadow {
anchors.fill: cardBackground
source: cardBackground
autoPaddingEnabled: true

View File

@@ -436,7 +436,7 @@ Variants {
}
}
NDropShadows {
NDropShadow {
anchors.fill: background
source: background
autoPaddingEnabled: true

View File

@@ -59,8 +59,8 @@ NBox {
// Dark overlay for readability
Rectangle {
anchors.fill: parent
color: Color.mSurfaceVariant
opacity: 0.85
color: Color.mSurface
opacity: 0.6
radius: Style.radiusM
}
@@ -69,7 +69,7 @@ NBox {
anchors.fill: parent
color: Color.transparent
border.color: Color.mOutline
border.width: 1
border.width: Style.borderS
radius: Style.radiusM
}
@@ -97,7 +97,7 @@ NBox {
anchors.fill: parent
values: CavaService.values
fillColor: Color.mPrimary
opacity: 0.5
opacity: 0.75
}
}
@@ -107,7 +107,7 @@ NBox {
anchors.fill: parent
values: CavaService.values
fillColor: Color.mPrimary
opacity: 0.5
opacity: 0.75
}
}
@@ -117,7 +117,7 @@ NBox {
anchors.fill: parent
values: CavaService.values
fillColor: Color.mPrimary
opacity: 0.5
opacity: 0.75
}
}
}
@@ -219,151 +219,170 @@ NBox {
anchors.fill: parent
active: root.hasActivePlayer
sourceComponent: ColumnLayout {
id: main
spacing: Style.marginS
sourceComponent: Item {
Layout.fillWidth: true
Layout.fillHeight: true
// Spacer to push content down
Item {
Layout.preferredHeight: Style.marginM
// Exceptionaly we put shadow on text to ease readability
// Dividing offset by 2 to make it a bit more subtle.
NDropShadow {
anchors.fill: main
source: main
autoPaddingEnabled: true
shadowBlur: 0.5
// shadowColor: "#00ff00"
shadowHorizontalOffset: Settings.data.general.shadowOffsetX * 0.5
shadowVerticalOffset: Settings.data.general.shadowOffsetY * 0.5
}
// Metadata at the bottom left
ColumnLayout {
Layout.fillWidth: true
Layout.alignment: Qt.AlignLeft
spacing: Style.marginXS
id: main
anchors.fill: parent
spacing: Style.marginS
NText {
visible: MediaService.trackTitle !== ""
text: MediaService.trackTitle
pointSize: Style.fontSizeL
font.weight: Style.fontWeightBold
elide: Text.ElideRight
wrapMode: Text.Wrap
maximumLineCount: 2
// Spacer to push content down
Item {
Layout.preferredHeight: Style.marginM
}
// Metadata
ColumnLayout {
id: metadata
Layout.fillWidth: true
Layout.alignment: Qt.AlignLeft
spacing: Style.marginXS
NText {
visible: MediaService.trackTitle !== ""
text: MediaService.trackTitle
pointSize: Style.fontSizeL
font.weight: Style.fontWeightBold
elide: Text.ElideRight
wrapMode: Text.Wrap
maximumLineCount: 2
Layout.fillWidth: true
}
NText {
visible: MediaService.trackArtist !== ""
text: MediaService.trackArtist
color: Color.mPrimary
pointSize: Style.fontSizeS
elide: Text.ElideRight
Layout.fillWidth: true
}
NText {
visible: MediaService.trackAlbum !== ""
text: MediaService.trackAlbum
color: Color.mOnSurfaceVariant
pointSize: Style.fontSizeM
elide: Text.ElideRight
Layout.fillWidth: true
}
}
NText {
visible: MediaService.trackArtist !== ""
text: MediaService.trackArtist
color: Color.mPrimary
pointSize: Style.fontSizeS
elide: Text.ElideRight
// Progress slider
Item {
id: progressWrapper
visible: (MediaService.currentPlayer && MediaService.trackLength > 0)
Layout.fillWidth: true
}
height: Style.baseWidgetSize * 0.5
NText {
visible: MediaService.trackAlbum !== ""
text: MediaService.trackAlbum
color: Color.mOnSurfaceVariant
pointSize: Style.fontSizeM
elide: Text.ElideRight
Layout.fillWidth: true
}
}
property real localSeekRatio: -1
property real lastSentSeekRatio: -1
property real seekEpsilon: 0.01
property real progressRatio: {
if (!MediaService.currentPlayer || MediaService.trackLength <= 0)
return 0;
const r = MediaService.currentPosition / MediaService.trackLength;
if (isNaN(r) || !isFinite(r))
return 0;
return Math.max(0, Math.min(1, r));
}
property real effectiveRatio: (MediaService.isSeeking && localSeekRatio >= 0) ? Math.max(0, Math.min(1, localSeekRatio)) : progressRatio
// Progress slider
Item {
id: progressWrapper
visible: (MediaService.currentPlayer && MediaService.trackLength > 0)
Layout.fillWidth: true
height: Style.baseWidgetSize * 0.5
property real localSeekRatio: -1
property real lastSentSeekRatio: -1
property real seekEpsilon: 0.01
property real progressRatio: {
if (!MediaService.currentPlayer || MediaService.trackLength <= 0)
return 0;
const r = MediaService.currentPosition / MediaService.trackLength;
if (isNaN(r) || !isFinite(r))
return 0;
return Math.max(0, Math.min(1, r));
}
property real effectiveRatio: (MediaService.isSeeking && localSeekRatio >= 0) ? Math.max(0, Math.min(1, localSeekRatio)) : progressRatio
Timer {
id: seekDebounce
interval: 75
repeat: false
onTriggered: {
if (MediaService.isSeeking && progressWrapper.localSeekRatio >= 0) {
const next = Math.max(0, Math.min(1, progressWrapper.localSeekRatio));
if (progressWrapper.lastSentSeekRatio < 0 || Math.abs(next - progressWrapper.lastSentSeekRatio) >= progressWrapper.seekEpsilon) {
MediaService.seekByRatio(next);
progressWrapper.lastSentSeekRatio = next;
Timer {
id: seekDebounce
interval: 75
repeat: false
onTriggered: {
if (MediaService.isSeeking && progressWrapper.localSeekRatio >= 0) {
const next = Math.max(0, Math.min(1, progressWrapper.localSeekRatio));
if (progressWrapper.lastSentSeekRatio < 0 || Math.abs(next - progressWrapper.lastSentSeekRatio) >= progressWrapper.seekEpsilon) {
MediaService.seekByRatio(next);
progressWrapper.lastSentSeekRatio = next;
}
}
}
}
}
NSlider {
id: progressSlider
anchors.fill: parent
from: 0
to: 1
stepSize: 0
snapAlways: false
enabled: MediaService.trackLength > 0 && MediaService.canSeek
heightRatio: 0.6
NSlider {
id: progressSlider
anchors.fill: parent
from: 0
to: 1
stepSize: 0
snapAlways: false
enabled: MediaService.trackLength > 0 && MediaService.canSeek
heightRatio: 0.6
onMoved: {
progressWrapper.localSeekRatio = value;
seekDebounce.restart();
}
onPressedChanged: {
if (pressed) {
MediaService.isSeeking = true;
onMoved: {
progressWrapper.localSeekRatio = value;
MediaService.seekByRatio(value);
progressWrapper.lastSentSeekRatio = value;
} else {
seekDebounce.stop();
MediaService.seekByRatio(value);
MediaService.isSeeking = false;
progressWrapper.localSeekRatio = -1;
progressWrapper.lastSentSeekRatio = -1;
seekDebounce.restart();
}
onPressedChanged: {
if (pressed) {
MediaService.isSeeking = true;
progressWrapper.localSeekRatio = value;
MediaService.seekByRatio(value);
progressWrapper.lastSentSeekRatio = value;
} else {
seekDebounce.stop();
MediaService.seekByRatio(value);
MediaService.isSeeking = false;
progressWrapper.localSeekRatio = -1;
progressWrapper.lastSentSeekRatio = -1;
}
}
}
Binding {
target: progressSlider
property: "value"
value: progressWrapper.progressRatio
when: !MediaService.isSeeking
}
}
Binding {
target: progressSlider
property: "value"
value: progressWrapper.progressRatio
when: !MediaService.isSeeking
}
}
// Spacer to push media controls down
Item {
Layout.preferredHeight: Style.marginL
}
// Media controls
RowLayout {
spacing: Style.marginS
Layout.fillWidth: true
Layout.alignment: Qt.AlignHCenter
NIconButton {
icon: "media-prev"
visible: MediaService.canGoPrevious
onClicked: MediaService.canGoPrevious ? MediaService.previous() : {}
// Spacer to push media controls down
Item {
Layout.preferredHeight: Style.marginL
}
NIconButton {
icon: MediaService.isPlaying ? "media-pause" : "media-play"
visible: (MediaService.canPlay || MediaService.canPause)
onClicked: (MediaService.canPlay || MediaService.canPause) ? MediaService.playPause() : {}
}
// Media controls
RowLayout {
spacing: Style.marginS
Layout.fillWidth: true
Layout.alignment: Qt.AlignHCenter
NIconButton {
icon: "media-next"
visible: MediaService.canGoNext
onClicked: MediaService.canGoNext ? MediaService.next() : {}
NIconButton {
icon: "media-prev"
visible: MediaService.canGoPrevious
onClicked: MediaService.canGoPrevious ? MediaService.previous() : {}
}
NIconButton {
icon: MediaService.isPlaying ? "media-pause" : "media-play"
visible: (MediaService.canPlay || MediaService.canPause)
onClicked: (MediaService.canPlay || MediaService.canPause) ? MediaService.playPause() : {}
}
NIconButton {
icon: "media-next"
visible: MediaService.canGoNext
onClicked: MediaService.canGoNext ? MediaService.next() : {}
}
}
}
}

View File

@@ -28,54 +28,61 @@ NBox {
RowLayout {
Layout.fillWidth: true
spacing: Style.marginS
Item {
Layout.preferredWidth: Style.marginS
}
NIcon {
Layout.alignment: Qt.AlignVCenter
icon: weatherReady ? LocationService.weatherSymbolFromCode(LocationService.data.weather.current_weather.weathercode) : ""
pointSize: Style.fontSizeXXXL * 1.75
color: Color.mPrimary
Layout.preferredWidth: Style.marginXXS
}
ColumnLayout {
spacing: Style.marginXXS
NText {
text: {
// Ensure the name is not too long if one had to specify the country
const chunks = Settings.data.location.name.split(",");
return chunks[0];
}
pointSize: Style.fontSizeL
font.weight: Style.fontWeightBold
visible: showLocation
RowLayout {
spacing: Style.marginL
Layout.fillWidth: true
NIcon {
Layout.alignment: Qt.AlignVCenter
icon: weatherReady ? LocationService.weatherSymbolFromCode(LocationService.data.weather.current_weather.weathercode) : ""
pointSize: Style.fontSizeXXXL * 1.75
color: Color.mPrimary
}
RowLayout {
ColumnLayout {
spacing: Style.marginXXS
NText {
visible: weatherReady
text: {
if (!weatherReady) {
return "";
}
var temp = LocationService.data.weather.current_weather.temperature;
var suffix = "C";
if (Settings.data.location.useFahrenheit) {
temp = LocationService.celsiusToFahrenheit(temp);
var suffix = "F";
}
temp = Math.round(temp);
return `${temp}°${suffix}`;
// Ensure the name is not too long if one had to specify the country
const chunks = Settings.data.location.name.split(",");
return chunks[0];
}
pointSize: showLocation ? Style.fontSizeXL : Style.fontSizeXL * 1.6
pointSize: Style.fontSizeL
font.weight: Style.fontWeightBold
visible: showLocation
}
NText {
text: weatherReady ? `(${LocationService.data.weather.timezone_abbreviation})` : ""
pointSize: Style.fontSizeXS
color: Color.mOnSurfaceVariant
visible: LocationService.data.weather && showLocation
RowLayout {
NText {
visible: weatherReady
text: {
if (!weatherReady) {
return "";
}
var temp = LocationService.data.weather.current_weather.temperature;
var suffix = "C";
if (Settings.data.location.useFahrenheit) {
temp = LocationService.celsiusToFahrenheit(temp);
var suffix = "F";
}
temp = Math.round(temp);
return `${temp}°${suffix}`;
}
pointSize: showLocation ? Style.fontSizeXL : Style.fontSizeXL * 1.6
font.weight: Style.fontWeightBold
}
NText {
text: weatherReady ? `(${LocationService.data.weather.timezone_abbreviation})` : ""
pointSize: Style.fontSizeXS
color: Color.mOnSurfaceVariant
visible: LocationService.data.weather && showLocation
}
}
}
}

View File

@@ -98,7 +98,7 @@ Popup {
border.color: Color.mOutline
border.width: Style.borderM
NDropShadows {
NDropShadow {
source: backgroundRect
}
}

View File

@@ -52,7 +52,7 @@ Item {
}
}
NDropShadows {
NDropShadow {
anchors.fill: background
source: background
autoPaddingEnabled: true

31
Widgets/NDropShadow.qml Normal file
View File

@@ -0,0 +1,31 @@
import QtQuick
import QtQuick.Effects
import qs.Commons
import qs.Services.Power
// Unified shadow system
Item {
id: root
required property var source
property bool autoPaddingEnabled: false
property real shadowHorizontalOffset: Settings.data.general.shadowOffsetX
property real shadowVerticalOffset: Settings.data.general.shadowOffsetY
property real shadowOpacity: Style.shadowOpacity
property color shadowColor: Color.black
property real shadowBlur: Style.shadowBlur
layer.enabled: Settings.data.general.enableShadows && !PowerProfileService.noctaliaPerformanceMode
layer.effect: MultiEffect {
source: root.source
shadowEnabled: true
blurMax: Style.shadowBlurMax
shadowBlur: root.shadowBlur
shadowOpacity: root.shadowOpacity
shadowColor: root.shadowColor
shadowHorizontalOffset: root.shadowHorizontalOffset
shadowVerticalOffset: root.shadowVerticalOffset
autoPaddingEnabled: root.autoPaddingEnabled
}
}

View File

@@ -1,26 +0,0 @@
import QtQuick
import QtQuick.Effects
import qs.Commons
import qs.Services.Power
// Unified shadow system
Item {
id: root
required property var source
property bool autoPaddingEnabled: false
layer.enabled: Settings.data.general.enableShadows && !PowerProfileService.noctaliaPerformanceMode
layer.effect: MultiEffect {
source: root.source
shadowEnabled: true
blurMax: Style.shadowBlurMax
shadowBlur: Style.shadowBlur
shadowOpacity: Style.shadowOpacity
shadowColor: Color.black
shadowHorizontalOffset: Settings.data.general.shadowOffsetX
shadowVerticalOffset: Settings.data.general.shadowOffsetY
autoPaddingEnabled: root.autoPaddingEnabled
}
}