Files
bbedward ccbf1d38ad widgets: bump qml-common, rename Dank* prefixed widgets to D, add
deprecation notice to plugin compatibility shims

- When qml-common became a thing, we left qs.Widgets shims in place for
  plugin compatibility
- It's annoying to need to maintain them when qml-common has any change,
  add a warning so we can safely deprecate them in the future
- One shot rename the common stuff to D to make it shorter and nicer to
  write
2026-10-05 16:47:19 -04:00

387 lines
14 KiB
QML

import QtQuick
import QtQuick.Window
import Quickshell.Services.Mpris
import qs.Common
import qs.Services
import qs.DCommon.Widgets
import qs.DCommon.Session
import "../../DCommon/Common/LayoutCodes.js" as LayoutCodes
Row {
id: root
property bool interactive: true
property bool showMediaPlayer: false
property bool showWeather: true
property bool useFahrenheit: false
readonly property color contentColor: Theme.lockScreenContentColor
readonly property color dimColor: Theme.withAlpha(contentColor, Theme.pendingOpacity)
readonly property color dividerColor: Theme.withAlpha(contentColor, Theme.stateLayerPressed)
readonly property bool weatherVisible: showWeather && WeatherService.weather.available
readonly property bool afterWeather: systemRow.visible || batteryRow.visible
readonly property bool afterMedia: weatherVisible || afterWeather
readonly property bool afterKeyboard: mediaLoader.visible || afterMedia
readonly property bool keyboardLayoutVisible: KeyboardLayoutService.layoutCount > 1
readonly property string keyboardLayoutLabel: KeyboardLayoutService.layoutKnown ? LayoutCodes.layoutCode(KeyboardLayoutService.currentLayout) : ""
readonly property color batteryColor: {
if (BatteryService.isLowBattery && !BatteryService.isCharging)
return Theme.error;
if (BatteryService.isCharging || BatteryService.isPluggedIn)
return Theme.primary;
return contentColor;
}
spacing: Theme.spacingL
function cycleKeyboardLayout() {
KeyboardLayoutService.cycle();
}
Component.onCompleted: KeyboardLayoutService.consumers++
Component.onDestruction: KeyboardLayoutService.consumers--
component Divider: Rectangle {
width: Theme.dividerWidth
height: Theme.iconSize
color: root.dividerColor
anchors.verticalCenter: parent.verticalCenter
}
Item {
id: keyboardLayout
width: keyboardLayoutRow.width
height: keyboardLayoutRow.height
anchors.verticalCenter: parent.verticalCenter
visible: root.keyboardLayoutVisible
Row {
id: keyboardLayoutRow
spacing: Theme.spacingXS
DIcon {
name: "keyboard"
size: Theme.iconSize
color: root.contentColor
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: root.keyboardLayoutLabel
font.pixelSize: Theme.fontSizeMedium
font.weight: Theme.fontWeight
color: root.contentColor
anchors.verticalCenter: parent.verticalCenter
}
}
MouseArea {
anchors.fill: parent
enabled: root.interactive
hoverEnabled: enabled
cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
onClicked: root.cycleKeyboardLayout()
}
}
Divider {
visible: keyboardLayout.visible && root.afterKeyboard
}
Loader {
id: mediaLoader
active: root.showMediaPlayer
visible: active && !!MprisController.activePlayer
anchors.verticalCenter: parent.verticalCenter
sourceComponent: Row {
spacing: Theme.spacingS
Item {
id: visualizer
width: Theme.iconSizeSmall
height: Theme.iconSize
anchors.verticalCenter: parent.verticalCenter
readonly property bool live: visible && (Window.window?.visible ?? false) && MprisController.activePlayer?.playbackState === MprisPlaybackState.Playing
Loader {
active: visualizer.live
sourceComponent: Ref {
service: CavaService
}
}
Timer {
running: !CavaService.cavaAvailable && visualizer.live
interval: 256
repeat: true
onTriggered: CavaService.values = [Math.random() * 40 + 10, Math.random() * 60 + 20, Math.random() * 50 + 15, Math.random() * 35 + 20, Math.random() * 45 + 15, Math.random() * 55 + 25]
}
Row {
anchors.centerIn: parent
spacing: Theme.spacingXXS
Repeater {
model: 6
delegate: Rectangle {
required property int index
width: Theme.outlineWidthFocused
height: {
if (!visualizer.live || CavaService.values.length <= index)
return Theme.spacingXXS;
const rawLevel = CavaService.values[index] || 0;
const scaledLevel = Math.sqrt(Math.min(Math.max(rawLevel, 0), 100) / 100) * 100;
const maxHeight = Theme.iconSize - Theme.spacingXXS;
return Theme.spacingXXS + (scaledLevel / 100) * (maxHeight - Theme.spacingXXS);
}
radius: Theme.cornerRadiusXS
color: root.contentColor
anchors.verticalCenter: parent.verticalCenter
Behavior on height {
NumberAnimation {
duration: LockMetrics.effectsDuration
easing.type: Easing.BezierSpline
easing.bezierCurve: Theme.expressiveCurves.expressiveEffects
}
}
}
}
}
}
StyledText {
text: {
const player = MprisController.activePlayer;
if (!player?.trackTitle)
return "";
return player.trackArtist ? player.trackTitle + " • " + player.trackArtist : player.trackTitle;
}
font.pixelSize: Theme.fontSizeLarge
color: root.contentColor
anchors.verticalCenter: parent.verticalCenter
elide: Text.ElideRight
width: Math.min(implicitWidth, LockMetrics.fieldWidth)
wrapMode: Text.NoWrap
maximumLineCount: 1
}
Row {
spacing: Theme.spacingXS
anchors.verticalCenter: parent.verticalCenter
Rectangle {
Accessible.role: Accessible.Button
Accessible.name: I18n.tr("Previous")
width: Theme.iconSizeSmall
height: Theme.iconSizeSmall
radius: Theme.fullRadius(width, height)
anchors.verticalCenter: parent.verticalCenter
color: prevArea.containsMouse ? root.dividerColor : "transparent"
opacity: prevArea.enabled ? 1 : Theme.onSurface_38.a
DIcon {
anchors.centerIn: parent
name: "skip_previous"
size: Theme.iconSizeSmall
color: root.contentColor
}
MouseArea {
id: prevArea
anchors.fill: parent
enabled: MprisController.activePlayer?.canGoPrevious ?? false
hoverEnabled: enabled
cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
onClicked: MprisController.previousOrRewind()
}
}
Rectangle {
Accessible.role: Accessible.Button
Accessible.name: playing ? I18n.tr("Pause") : I18n.tr("Play")
readonly property bool playing: MprisController.activePlayer?.playbackState === MprisPlaybackState.Playing
width: Theme.iconSize
height: Theme.iconSize
radius: Theme.fullRadius(width, height)
anchors.verticalCenter: parent.verticalCenter
color: playing ? root.contentColor : root.dividerColor
DIcon {
anchors.centerIn: parent
name: parent.playing ? "pause" : "play_arrow"
size: Theme.iconSizeSmall
color: parent.playing ? Theme.screenOffColor : root.contentColor
}
MouseArea {
id: playArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: MprisController.activePlayer?.togglePlaying()
}
}
Rectangle {
Accessible.role: Accessible.Button
Accessible.name: I18n.tr("Next")
width: Theme.iconSizeSmall
height: Theme.iconSizeSmall
radius: Theme.fullRadius(width, height)
anchors.verticalCenter: parent.verticalCenter
color: nextArea.containsMouse ? root.dividerColor : "transparent"
opacity: nextArea.enabled ? 1 : Theme.onSurface_38.a
DIcon {
anchors.centerIn: parent
name: "skip_next"
size: Theme.iconSizeSmall
color: root.contentColor
}
MouseArea {
id: nextArea
anchors.fill: parent
enabled: MprisController.activePlayer?.canGoNext ?? false
hoverEnabled: enabled
cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
onClicked: MprisController.next()
}
}
}
}
}
Divider {
visible: mediaLoader.visible && root.afterMedia
}
Row {
id: weatherRow
spacing: Theme.spacingXS
visible: root.weatherVisible
anchors.verticalCenter: parent.verticalCenter
DIcon {
name: WeatherService.getWeatherIcon(WeatherService.weather.wCode)
size: Theme.iconSize
color: root.contentColor
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: (root.useFahrenheit ? WeatherService.weather.tempF : WeatherService.weather.temp) + "°"
font.pixelSize: Theme.fontSizeLarge
font.weight: Theme.fontWeight
color: root.contentColor
anchors.verticalCenter: parent.verticalCenter
}
}
Divider {
visible: weatherRow.visible && root.afterWeather
}
Row {
id: systemRow
spacing: Theme.spacingM
anchors.verticalCenter: parent.verticalCenter
visible: NetworkService.networkAvailable || (BluetoothService.available && BluetoothService.enabled) || (AudioService.sink && AudioService.sink.audio)
DIcon {
name: "screen_record"
size: Theme.iconSizeSmall
color: NiriService.hasActiveCast ? root.contentColor : root.dimColor
anchors.verticalCenter: parent.verticalCenter
visible: NiriService.hasCasts
}
DIcon {
id: networkIcon
name: {
if (NetworkService.wifiToggling)
return "sync";
switch (NetworkService.networkStatus) {
case "ethernet":
return "lan";
case "cellular":
return "network_cell";
case "vpn":
return NetworkService.ethernetConnected ? "lan" : (NetworkService.cellularConnected ? "network_cell" : NetworkService.wifiSignalIcon);
default:
return NetworkService.wifiSignalIcon;
}
}
size: Theme.iconSizeSmall
color: (NetworkService.networkStatus !== "disconnected" || NetworkService.isConnecting) ? root.contentColor : root.dimColor
anchors.verticalCenter: parent.verticalCenter
visible: NetworkService.networkAvailable
DBlink {
target: networkIcon
running: NetworkService.isWifiConnecting
}
}
DIcon {
name: "vpn_lock"
size: Theme.iconSizeSmall
color: root.contentColor
anchors.verticalCenter: parent.verticalCenter
visible: NetworkService.vpnAvailable && NetworkService.vpnConnected
}
DIcon {
id: bluetoothIcon
name: "bluetooth"
size: Theme.iconSizeSmall
color: root.contentColor
anchors.verticalCenter: parent.verticalCenter
visible: BluetoothService.available && BluetoothService.enabled
DBlink {
target: bluetoothIcon
running: BluetoothService.connecting
}
}
DIcon {
name: AudioService.sinkVolumeIconName
size: Theme.iconSizeSmall
color: AudioService.sinkSilent ? root.dimColor : root.contentColor
anchors.verticalCenter: parent.verticalCenter
visible: AudioService.sink && AudioService.sink.audio
}
}
Divider {
visible: systemRow.visible && batteryRow.visible
}
Row {
id: batteryRow
spacing: Theme.spacingXS
visible: BatteryService.batteryAvailable
anchors.verticalCenter: parent.verticalCenter
DIcon {
name: BatteryService.getBatteryIcon()
size: Theme.iconSize
color: root.batteryColor
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: BatteryService.batteryLevel + "%"
font.pixelSize: Theme.fontSizeLarge
font.weight: Theme.fontWeight
color: root.contentColor
anchors.verticalCenter: parent.verticalCenter
}
}
}