wip
This commit is contained in:
parent
32edcff102
commit
0296117901
110 changed files with 9713 additions and 5 deletions
69
modules/dashboard/Background.qml
Normal file
69
modules/dashboard/Background.qml
Normal file
|
@ -0,0 +1,69 @@
|
|||
import QtQuick
|
||||
import QtQuick.Shapes
|
||||
import "root:/config"
|
||||
import "root:/services"
|
||||
|
||||
ShapePath {
|
||||
id: root
|
||||
|
||||
required property Wrapper wrapper
|
||||
readonly property real rounding: BorderConfig.rounding
|
||||
readonly property bool flatten: wrapper.height < rounding * 2
|
||||
readonly property real roundingY: flatten ? wrapper.height / 2 : rounding
|
||||
|
||||
strokeWidth: -1
|
||||
fillColor: BorderConfig.colour
|
||||
|
||||
PathArc {
|
||||
relativeX: root.rounding
|
||||
relativeY: root.roundingY
|
||||
radiusX: root.rounding
|
||||
radiusY: Math.min(root.rounding, root.wrapper.height)
|
||||
}
|
||||
|
||||
PathLine {
|
||||
relativeX: 0
|
||||
relativeY: root.wrapper.height - root.roundingY * 2
|
||||
}
|
||||
|
||||
PathArc {
|
||||
relativeX: root.rounding
|
||||
relativeY: root.roundingY
|
||||
radiusX: root.rounding
|
||||
radiusY: Math.min(root.rounding, root.wrapper.height)
|
||||
direction: PathArc.Counterclockwise
|
||||
}
|
||||
|
||||
PathLine {
|
||||
relativeX: root.wrapper.width - root.rounding * 2
|
||||
relativeY: 0
|
||||
}
|
||||
|
||||
PathArc {
|
||||
relativeX: root.rounding
|
||||
relativeY: -root.roundingY
|
||||
radiusX: root.rounding
|
||||
radiusY: Math.min(root.rounding, root.wrapper.height)
|
||||
direction: PathArc.Counterclockwise
|
||||
}
|
||||
|
||||
PathLine {
|
||||
relativeX: 0
|
||||
relativeY: -(root.wrapper.height - root.roundingY * 2)
|
||||
}
|
||||
|
||||
PathArc {
|
||||
relativeX: root.rounding
|
||||
relativeY: -root.roundingY
|
||||
radiusX: root.rounding
|
||||
radiusY: Math.min(root.rounding, root.wrapper.height)
|
||||
}
|
||||
|
||||
Behavior on fillColor {
|
||||
ColorAnimation {
|
||||
duration: Appearance.anim.durations.normal
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: Appearance.anim.curves.standard
|
||||
}
|
||||
}
|
||||
}
|
123
modules/dashboard/Content.qml
Normal file
123
modules/dashboard/Content.qml
Normal file
|
@ -0,0 +1,123 @@
|
|||
import "root:/widgets"
|
||||
import "root:/services"
|
||||
import "root:/config"
|
||||
import Quickshell
|
||||
import Quickshell.Widgets
|
||||
import QtQuick
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
required property PersistentProperties visibilities
|
||||
readonly property real nonAnimWidth: view.implicitWidth + viewWrapper.anchors.margins * 2
|
||||
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.bottom: parent.bottom
|
||||
|
||||
implicitWidth: nonAnimWidth
|
||||
implicitHeight: tabs.implicitHeight + tabs.anchors.topMargin + view.implicitHeight + viewWrapper.anchors.margins * 2
|
||||
|
||||
Tabs {
|
||||
id: tabs
|
||||
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.topMargin: Appearance.padding.normal
|
||||
anchors.margins: Appearance.padding.large
|
||||
|
||||
nonAnimWidth: root.nonAnimWidth
|
||||
currentIndex: view.currentIndex
|
||||
}
|
||||
|
||||
ClippingRectangle {
|
||||
id: viewWrapper
|
||||
|
||||
anchors.top: tabs.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.margins: Appearance.padding.large
|
||||
|
||||
radius: Appearance.rounding.normal
|
||||
color: "transparent"
|
||||
|
||||
Flickable {
|
||||
id: view
|
||||
|
||||
readonly property int currentIndex: tabs.currentIndex
|
||||
readonly property Item currentItem: row.children[currentIndex]
|
||||
|
||||
anchors.fill: parent
|
||||
|
||||
flickableDirection: Flickable.HorizontalFlick
|
||||
|
||||
implicitWidth: currentItem.implicitWidth
|
||||
implicitHeight: currentItem.implicitHeight
|
||||
|
||||
contentX: currentItem.x
|
||||
contentWidth: row.implicitWidth
|
||||
contentHeight: row.implicitHeight
|
||||
|
||||
onContentXChanged: {
|
||||
if (!moving)
|
||||
return;
|
||||
|
||||
const x = contentX - currentItem.x;
|
||||
if (x > currentItem.implicitWidth / 2)
|
||||
tabs.bar.incrementCurrentIndex();
|
||||
else if (x < -currentItem.implicitWidth / 2)
|
||||
tabs.bar.decrementCurrentIndex();
|
||||
}
|
||||
|
||||
onDragEnded: {
|
||||
const x = contentX - currentItem.x;
|
||||
if (x > currentItem.implicitWidth / 10)
|
||||
tabs.bar.incrementCurrentIndex();
|
||||
else if (x < -currentItem.implicitWidth / 10)
|
||||
tabs.bar.decrementCurrentIndex();
|
||||
else
|
||||
contentX = Qt.binding(() => currentItem.x);
|
||||
}
|
||||
|
||||
Row {
|
||||
id: row
|
||||
|
||||
Dash {
|
||||
shouldUpdate: visible && this === view.currentItem
|
||||
}
|
||||
|
||||
Media {
|
||||
shouldUpdate: visible && this === view.currentItem
|
||||
visibilities: root.visibilities
|
||||
}
|
||||
|
||||
Performance {}
|
||||
}
|
||||
|
||||
Behavior on contentX {
|
||||
NumberAnimation {
|
||||
duration: Appearance.anim.durations.normal
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: Appearance.anim.curves.standard
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on implicitWidth {
|
||||
NumberAnimation {
|
||||
duration: Appearance.anim.durations.large
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: Appearance.anim.curves.emphasized
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on implicitHeight {
|
||||
NumberAnimation {
|
||||
duration: Appearance.anim.durations.large
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: Appearance.anim.curves.emphasized
|
||||
}
|
||||
}
|
||||
}
|
86
modules/dashboard/Dash.qml
Normal file
86
modules/dashboard/Dash.qml
Normal file
|
@ -0,0 +1,86 @@
|
|||
import "root:/widgets"
|
||||
import "root:/services"
|
||||
import "root:/config"
|
||||
import "dash"
|
||||
import QtQuick.Layouts
|
||||
|
||||
GridLayout {
|
||||
id: root
|
||||
|
||||
required property bool shouldUpdate
|
||||
|
||||
rowSpacing: Appearance.spacing.normal
|
||||
columnSpacing: Appearance.spacing.normal
|
||||
|
||||
Rect {
|
||||
Layout.column: 2
|
||||
Layout.columnSpan: 3
|
||||
Layout.preferredWidth: user.implicitWidth
|
||||
Layout.preferredHeight: user.implicitHeight
|
||||
|
||||
User {
|
||||
id: user
|
||||
}
|
||||
}
|
||||
|
||||
Rect {
|
||||
Layout.row: 0
|
||||
Layout.columnSpan: 2
|
||||
Layout.preferredWidth: DashboardConfig.sizes.weatherWidth
|
||||
Layout.fillHeight: true
|
||||
|
||||
Weather {}
|
||||
}
|
||||
|
||||
Rect {
|
||||
Layout.row: 1
|
||||
Layout.preferredWidth: dateTime.implicitWidth
|
||||
Layout.fillHeight: true
|
||||
|
||||
DateTime {
|
||||
id: dateTime
|
||||
}
|
||||
}
|
||||
|
||||
Rect {
|
||||
Layout.row: 1
|
||||
Layout.column: 1
|
||||
Layout.columnSpan: 3
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: calendar.implicitHeight
|
||||
|
||||
Calendar {
|
||||
id: calendar
|
||||
}
|
||||
}
|
||||
|
||||
Rect {
|
||||
Layout.row: 1
|
||||
Layout.column: 4
|
||||
Layout.preferredWidth: resources.implicitWidth
|
||||
Layout.fillHeight: true
|
||||
|
||||
Resources {
|
||||
id: resources
|
||||
}
|
||||
}
|
||||
|
||||
Rect {
|
||||
Layout.row: 0
|
||||
Layout.column: 5
|
||||
Layout.rowSpan: 2
|
||||
Layout.preferredWidth: media.implicitWidth
|
||||
Layout.fillHeight: true
|
||||
|
||||
Media {
|
||||
id: media
|
||||
|
||||
shouldUpdate: root.shouldUpdate
|
||||
}
|
||||
}
|
||||
|
||||
component Rect: StyledRect {
|
||||
radius: Appearance.rounding.small
|
||||
color: Colours.palette.m3surfaceContainer
|
||||
}
|
||||
}
|
594
modules/dashboard/Media.qml
Normal file
594
modules/dashboard/Media.qml
Normal file
|
@ -0,0 +1,594 @@
|
|||
pragma ComponentBehavior: Bound
|
||||
|
||||
import "root:/widgets"
|
||||
import "root:/services"
|
||||
import "root:/utils"
|
||||
import "root:/config"
|
||||
import Quickshell
|
||||
import Quickshell.Widgets
|
||||
import Quickshell.Services.Mpris
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Effects
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
required property bool shouldUpdate
|
||||
required property PersistentProperties visibilities
|
||||
|
||||
property real playerProgress: {
|
||||
const active = Players.active;
|
||||
return active?.length ? active.position / active.length : 0;
|
||||
}
|
||||
|
||||
function lengthStr(length: int): string {
|
||||
if (length < 0)
|
||||
return "-1:-1";
|
||||
return `${Math.floor(length / 60)}:${Math.floor(length % 60).toString().padStart(2, "0")}`;
|
||||
}
|
||||
|
||||
implicitWidth: cover.implicitWidth + DashboardConfig.sizes.mediaVisualiserSize * 2 + details.implicitWidth + details.anchors.leftMargin + bongocat.implicitWidth + bongocat.anchors.leftMargin * 2 + Appearance.padding.large * 2
|
||||
implicitHeight: Math.max(cover.implicitHeight + DashboardConfig.sizes.mediaVisualiserSize * 2, details.implicitHeight, bongocat.implicitHeight) + Appearance.padding.large * 2
|
||||
|
||||
Behavior on playerProgress {
|
||||
NumberAnimation {
|
||||
duration: Appearance.anim.durations.large
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: Appearance.anim.curves.standard
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
running: root.shouldUpdate && (Players.active?.isPlaying ?? false)
|
||||
interval: DashboardConfig.mediaUpdateInterval
|
||||
triggeredOnStart: true
|
||||
repeat: true
|
||||
onTriggered: Players.active?.positionChanged()
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: Cava
|
||||
|
||||
function onValuesChanged(): void {
|
||||
if (root.shouldUpdate)
|
||||
visualiser.requestPaint();
|
||||
}
|
||||
}
|
||||
|
||||
Canvas {
|
||||
id: visualiser
|
||||
|
||||
readonly property real centerX: width / 2
|
||||
readonly property real centerY: height / 2
|
||||
readonly property real innerX: cover.implicitWidth / 2 + Appearance.spacing.small
|
||||
readonly property real innerY: cover.implicitHeight / 2 + Appearance.spacing.small
|
||||
property color colour: Colours.palette.m3primary
|
||||
|
||||
anchors.fill: cover
|
||||
anchors.margins: -DashboardConfig.sizes.mediaVisualiserSize
|
||||
|
||||
onColourChanged: requestPaint()
|
||||
|
||||
onPaint: {
|
||||
const ctx = getContext("2d");
|
||||
ctx.reset();
|
||||
|
||||
const values = Cava.values;
|
||||
const len = values.length;
|
||||
|
||||
ctx.strokeStyle = colour;
|
||||
ctx.lineWidth = 360 / len - Appearance.spacing.small / 4;
|
||||
ctx.lineCap = "round";
|
||||
|
||||
const size = DashboardConfig.sizes.mediaVisualiserSize;
|
||||
const cx = centerX;
|
||||
const cy = centerY;
|
||||
const rx = innerX + ctx.lineWidth / 2;
|
||||
const ry = innerY + ctx.lineWidth / 2;
|
||||
|
||||
for (let i = 0; i < len; i++) {
|
||||
const v = Math.max(1, Math.min(100, values[i]));
|
||||
|
||||
const angle = i * 2 * Math.PI / len;
|
||||
const magnitude = v / 100 * size;
|
||||
const cos = Math.cos(angle);
|
||||
const sin = Math.sin(angle);
|
||||
|
||||
ctx.moveTo(cx + rx * cos, cy + ry * sin);
|
||||
ctx.lineTo(cx + (rx + magnitude) * cos, cy + (ry + magnitude) * sin);
|
||||
}
|
||||
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
Behavior on colour {
|
||||
ColorAnimation {
|
||||
duration: Appearance.anim.durations.normal
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: Appearance.anim.curves.standard
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StyledClippingRect {
|
||||
id: cover
|
||||
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Appearance.padding.large + DashboardConfig.sizes.mediaVisualiserSize
|
||||
|
||||
implicitWidth: DashboardConfig.sizes.mediaCoverArtSize
|
||||
implicitHeight: DashboardConfig.sizes.mediaCoverArtSize
|
||||
|
||||
color: Colours.palette.m3surfaceContainerHigh
|
||||
radius: Appearance.rounding.full
|
||||
|
||||
MaterialIcon {
|
||||
anchors.centerIn: parent
|
||||
|
||||
text: "art_track"
|
||||
color: Colours.palette.m3onSurfaceVariant
|
||||
font.pointSize: (parent.width * 0.4) || 1
|
||||
}
|
||||
|
||||
Image {
|
||||
id: image
|
||||
|
||||
anchors.fill: parent
|
||||
|
||||
source: Players.active?.trackArtUrl ?? ""
|
||||
asynchronous: true
|
||||
fillMode: Image.PreserveAspectCrop
|
||||
sourceSize.width: width
|
||||
sourceSize.height: height
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
id: details
|
||||
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.left: visualiser.right
|
||||
anchors.leftMargin: Appearance.spacing.normal
|
||||
|
||||
spacing: Appearance.spacing.small
|
||||
|
||||
StyledText {
|
||||
id: title
|
||||
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
|
||||
animate: true
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
text: (Players.active?.trackTitle ?? qsTr("No media")) || qsTr("Unknown title")
|
||||
color: Colours.palette.m3primary
|
||||
font.pointSize: Appearance.font.size.normal
|
||||
|
||||
width: parent.implicitWidth
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
StyledText {
|
||||
id: album
|
||||
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
|
||||
animate: true
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
text: (Players.active?.trackAlbum ?? qsTr("No media")) || qsTr("Unknown album")
|
||||
color: Colours.palette.m3outline
|
||||
font.pointSize: Appearance.font.size.small
|
||||
|
||||
width: parent.implicitWidth
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
StyledText {
|
||||
id: artist
|
||||
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
|
||||
animate: true
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
text: (Players.active?.trackArtist ?? qsTr("No media")) || qsTr("Unknown artist")
|
||||
color: Colours.palette.m3secondary
|
||||
|
||||
width: parent.implicitWidth
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
Row {
|
||||
id: controls
|
||||
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
|
||||
spacing: Appearance.spacing.small
|
||||
|
||||
Control {
|
||||
icon: "skip_previous"
|
||||
canUse: Players.active?.canGoPrevious ?? false
|
||||
|
||||
function onClicked(): void {
|
||||
Players.active?.previous();
|
||||
}
|
||||
}
|
||||
|
||||
Control {
|
||||
icon: Players.active?.isPlaying ? "pause" : "play_arrow"
|
||||
canUse: Players.active?.canTogglePlaying ?? false
|
||||
primary: true
|
||||
|
||||
function onClicked(): void {
|
||||
Players.active?.togglePlaying();
|
||||
}
|
||||
}
|
||||
|
||||
Control {
|
||||
icon: "skip_next"
|
||||
canUse: Players.active?.canGoNext ?? false
|
||||
|
||||
function onClicked(): void {
|
||||
Players.active?.next();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Slider {
|
||||
id: slider
|
||||
|
||||
implicitWidth: controls.implicitWidth * 1.5
|
||||
implicitHeight: Appearance.padding.normal * 3
|
||||
|
||||
value: root.playerProgress
|
||||
onMoved: {
|
||||
const active = Players.active;
|
||||
if (active?.canSeek && active?.positionSupported)
|
||||
active.position = value * active.length;
|
||||
}
|
||||
|
||||
background: Item {
|
||||
StyledRect {
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.topMargin: slider.implicitHeight / 3
|
||||
anchors.bottomMargin: slider.implicitHeight / 3
|
||||
|
||||
implicitWidth: slider.handle.x - slider.implicitHeight / 6
|
||||
|
||||
color: Colours.palette.m3primary
|
||||
radius: Appearance.rounding.full
|
||||
topRightRadius: slider.implicitHeight / 15
|
||||
bottomRightRadius: slider.implicitHeight / 15
|
||||
}
|
||||
|
||||
StyledRect {
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.right: parent.right
|
||||
anchors.topMargin: slider.implicitHeight / 3
|
||||
anchors.bottomMargin: slider.implicitHeight / 3
|
||||
|
||||
implicitWidth: parent.width - slider.handle.x - slider.handle.implicitWidth - slider.implicitHeight / 6
|
||||
|
||||
color: Colours.palette.m3surfaceContainer
|
||||
radius: Appearance.rounding.full
|
||||
topLeftRadius: slider.implicitHeight / 15
|
||||
bottomLeftRadius: slider.implicitHeight / 15
|
||||
}
|
||||
}
|
||||
|
||||
handle: StyledRect {
|
||||
id: rect
|
||||
|
||||
x: slider.visualPosition * slider.availableWidth
|
||||
|
||||
implicitWidth: slider.implicitHeight / 4.5
|
||||
implicitHeight: slider.implicitHeight
|
||||
|
||||
color: Colours.palette.m3primary
|
||||
radius: Appearance.rounding.full
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onPressed: event => event.accepted = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
|
||||
implicitHeight: Math.max(position.implicitHeight, length.implicitHeight)
|
||||
|
||||
StyledText {
|
||||
id: position
|
||||
|
||||
anchors.left: parent.left
|
||||
|
||||
text: root.lengthStr(Players.active?.position ?? -1)
|
||||
color: Colours.palette.m3onSurfaceVariant
|
||||
font.pointSize: Appearance.font.size.small
|
||||
}
|
||||
|
||||
StyledText {
|
||||
id: length
|
||||
|
||||
anchors.right: parent.right
|
||||
|
||||
text: root.lengthStr(Players.active?.length ?? -1)
|
||||
color: Colours.palette.m3onSurfaceVariant
|
||||
font.pointSize: Appearance.font.size.small
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
|
||||
spacing: Appearance.spacing.small
|
||||
|
||||
Control {
|
||||
icon: "flip_to_front"
|
||||
canUse: Players.active?.canRaise ?? false
|
||||
fontSize: Appearance.font.size.larger
|
||||
padding: Appearance.padding.small
|
||||
fill: false
|
||||
color: Colours.palette.m3surfaceContainer
|
||||
|
||||
function onClicked(): void {
|
||||
Players.active?.raise();
|
||||
root.visibilities.dashboard = false;
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: playerSelector
|
||||
|
||||
property bool expanded
|
||||
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
implicitWidth: slider.implicitWidth / 2
|
||||
implicitHeight: currentPlayer.implicitHeight + Appearance.padding.small * 2
|
||||
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: expanded = !expanded
|
||||
|
||||
RectangularShadow {
|
||||
anchors.fill: playerSelectorBg
|
||||
|
||||
opacity: playerSelector.expanded ? 1 : 0
|
||||
radius: playerSelectorBg.radius
|
||||
color: Colours.palette.m3shadow
|
||||
blur: 5
|
||||
spread: 0
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Appearance.anim.durations.normal
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: Appearance.anim.curves.standard
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StyledRect {
|
||||
id: playerSelectorBg
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
|
||||
implicitHeight: playersWrapper.implicitHeight + Appearance.padding.small * 2
|
||||
|
||||
color: Colours.palette.m3secondaryContainer
|
||||
radius: Appearance.rounding.normal
|
||||
|
||||
Item {
|
||||
id: playersWrapper
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.margins: Appearance.padding.small
|
||||
|
||||
clip: true
|
||||
implicitHeight: playerSelector.expanded && Players.list.length > 1 ? players.implicitHeight : currentPlayer.implicitHeight
|
||||
|
||||
Column {
|
||||
id: players
|
||||
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.bottom: parent.bottom
|
||||
|
||||
spacing: Appearance.spacing.small
|
||||
|
||||
Repeater {
|
||||
model: Players.list.filter(p => p !== Players.active)
|
||||
|
||||
Row {
|
||||
id: player
|
||||
|
||||
required property MprisPlayer modelData
|
||||
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
spacing: Appearance.spacing.small
|
||||
|
||||
IconImage {
|
||||
id: playerIcon
|
||||
|
||||
source: Icons.getAppIcon(player.modelData.identity, "image-missing")
|
||||
implicitSize: Math.round(identity.implicitHeight * 0.9)
|
||||
}
|
||||
|
||||
StyledText {
|
||||
id: identity
|
||||
|
||||
text: identityMetrics.elidedText
|
||||
color: Colours.palette.m3onSecondaryContainer
|
||||
|
||||
TextMetrics {
|
||||
id: identityMetrics
|
||||
|
||||
text: player.modelData.identity
|
||||
font.family: identity.font.family
|
||||
font.pointSize: identity.font.pointSize
|
||||
elide: Text.ElideRight
|
||||
elideWidth: playerSelector.implicitWidth - playerIcon.implicitWidth - player.spacing - Appearance.padding.smaller * 2
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
|
||||
anchors.fill: parent
|
||||
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
Players.manualActive = player.modelData;
|
||||
playerSelector.expanded = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
implicitHeight: 1
|
||||
|
||||
StyledRect {
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.margins: -Appearance.padding.normal
|
||||
color: Colours.palette.m3secondary
|
||||
implicitHeight: 1
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
id: currentPlayer
|
||||
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
spacing: Appearance.spacing.small
|
||||
|
||||
IconImage {
|
||||
id: currentIcon
|
||||
|
||||
source: Icons.getAppIcon(Players.active?.identity ?? "", "multimedia-player")
|
||||
implicitSize: Math.round(currentIdentity.implicitHeight * 0.9)
|
||||
}
|
||||
|
||||
StyledText {
|
||||
id: currentIdentity
|
||||
|
||||
animate: true
|
||||
text: currentIdentityMetrics.elidedText
|
||||
color: Colours.palette.m3onSecondaryContainer
|
||||
|
||||
TextMetrics {
|
||||
id: currentIdentityMetrics
|
||||
|
||||
text: Players.active?.identity ?? "No players"
|
||||
font.family: currentIdentity.font.family
|
||||
font.pointSize: currentIdentity.font.pointSize
|
||||
elide: Text.ElideRight
|
||||
elideWidth: playerSelector.implicitWidth - currentIcon.implicitWidth - currentPlayer.spacing - Appearance.padding.smaller * 2
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on implicitHeight {
|
||||
NumberAnimation {
|
||||
duration: Appearance.anim.durations.normal
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: Appearance.anim.curves.emphasized
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Control {
|
||||
icon: "delete"
|
||||
canUse: Players.active?.canQuit ?? false
|
||||
fontSize: Appearance.font.size.larger
|
||||
padding: Appearance.padding.small
|
||||
fill: false
|
||||
color: Colours.palette.m3surfaceContainer
|
||||
|
||||
function onClicked(): void {
|
||||
Players.active?.quit();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: bongocat
|
||||
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.left: details.right
|
||||
anchors.leftMargin: Appearance.spacing.normal
|
||||
|
||||
implicitWidth: visualiser.width
|
||||
implicitHeight: visualiser.height
|
||||
|
||||
AnimatedImage {
|
||||
anchors.centerIn: parent
|
||||
|
||||
width: visualiser.width * 0.75
|
||||
height: visualiser.height * 0.75
|
||||
|
||||
playing: root.shouldUpdate && (Players.active?.isPlaying ?? false)
|
||||
speed: BeatDetector.bpm / 300
|
||||
source: "root:/assets/bongocat.gif"
|
||||
asynchronous: true
|
||||
fillMode: AnimatedImage.PreserveAspectFit
|
||||
}
|
||||
}
|
||||
component Control: StyledRect {
|
||||
id: control
|
||||
|
||||
required property string icon
|
||||
required property bool canUse
|
||||
property int fontSize: Appearance.font.size.extraLarge
|
||||
property int padding
|
||||
property bool fill: true
|
||||
property bool primary
|
||||
function onClicked(): void {
|
||||
}
|
||||
|
||||
implicitWidth: Math.max(icon.implicitWidth, icon.implicitHeight) + padding * 2
|
||||
implicitHeight: implicitWidth
|
||||
|
||||
radius: Appearance.rounding.full
|
||||
color: primary && canUse ? Colours.palette.m3primary : "transparent"
|
||||
|
||||
StateLayer {
|
||||
disabled: !control.canUse
|
||||
radius: parent.radius
|
||||
color: control.primary ? Colours.palette.m3onPrimary : Colours.palette.m3onSurface
|
||||
|
||||
function onClicked(): void {
|
||||
control.onClicked();
|
||||
}
|
||||
}
|
||||
|
||||
MaterialIcon {
|
||||
id: icon
|
||||
|
||||
anchors.centerIn: parent
|
||||
anchors.verticalCenterOffset: font.pointSize * 0.05
|
||||
|
||||
animate: true
|
||||
fill: control.fill ? 1 : 0
|
||||
text: control.icon
|
||||
color: control.canUse ? control.primary ? Colours.palette.m3onPrimary : Colours.palette.m3onSurface : Colours.palette.m3outline
|
||||
font.pointSize: control.fontSize
|
||||
}
|
||||
}
|
||||
}
|
230
modules/dashboard/Performance.qml
Normal file
230
modules/dashboard/Performance.qml
Normal file
|
@ -0,0 +1,230 @@
|
|||
import "root:/widgets"
|
||||
import "root:/services"
|
||||
import "root:/config"
|
||||
import QtQuick
|
||||
|
||||
Row {
|
||||
id: root
|
||||
|
||||
spacing: Appearance.spacing.large * 3
|
||||
padding: Appearance.padding.large
|
||||
leftPadding: padding * 2
|
||||
rightPadding: padding * 3
|
||||
|
||||
Resource {
|
||||
value1: Math.min(1, SystemUsage.gpuTemp / 90)
|
||||
value2: SystemUsage.gpuPerc
|
||||
|
||||
label1: `${Math.ceil(SystemUsage.gpuTemp)}°C`
|
||||
label2: `${Math.round(SystemUsage.gpuPerc * 100)}%`
|
||||
|
||||
sublabel1: qsTr("GPU temp")
|
||||
sublabel2: qsTr("Usage")
|
||||
}
|
||||
|
||||
Resource {
|
||||
primary: true
|
||||
|
||||
value1: Math.min(1, SystemUsage.cpuTemp / 90)
|
||||
value2: SystemUsage.cpuPerc
|
||||
|
||||
label1: `${Math.ceil(SystemUsage.cpuTemp)}°C`
|
||||
label2: `${Math.round(SystemUsage.cpuPerc * 100)}%`
|
||||
|
||||
sublabel1: qsTr("CPU temp")
|
||||
sublabel2: qsTr("Usage")
|
||||
}
|
||||
|
||||
Resource {
|
||||
value1: SystemUsage.memPerc
|
||||
value2: SystemUsage.storagePerc
|
||||
|
||||
label1: {
|
||||
const fmt = SystemUsage.formatKib(SystemUsage.memUsed);
|
||||
return `${+fmt.value.toFixed(1)}${fmt.unit}`;
|
||||
}
|
||||
label2: {
|
||||
const fmt = SystemUsage.formatKib(SystemUsage.storageUsed);
|
||||
return `${Math.floor(fmt.value)}${fmt.unit}`;
|
||||
}
|
||||
|
||||
sublabel1: qsTr("Memory")
|
||||
sublabel2: qsTr("Storage")
|
||||
}
|
||||
|
||||
component Resource: Item {
|
||||
id: res
|
||||
|
||||
required property real value1
|
||||
required property real value2
|
||||
required property string sublabel1
|
||||
required property string sublabel2
|
||||
required property string label1
|
||||
required property string label2
|
||||
|
||||
property bool primary
|
||||
readonly property real primaryMult: primary ? 1.2 : 1
|
||||
|
||||
readonly property real thickness: DashboardConfig.sizes.resourceProgessThickness * primaryMult
|
||||
|
||||
property color fg1: Colours.palette.m3primary
|
||||
property color fg2: Colours.palette.m3secondary
|
||||
property color bg1: Colours.palette.m3primaryContainer
|
||||
property color bg2: Colours.palette.m3secondaryContainer
|
||||
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
implicitWidth: DashboardConfig.sizes.resourceSize * primaryMult
|
||||
implicitHeight: DashboardConfig.sizes.resourceSize * primaryMult
|
||||
|
||||
onValue1Changed: canvas.requestPaint()
|
||||
onValue2Changed: canvas.requestPaint()
|
||||
onFg1Changed: canvas.requestPaint()
|
||||
onFg2Changed: canvas.requestPaint()
|
||||
onBg1Changed: canvas.requestPaint()
|
||||
onBg2Changed: canvas.requestPaint()
|
||||
|
||||
Column {
|
||||
anchors.centerIn: parent
|
||||
|
||||
StyledText {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
|
||||
text: res.label1
|
||||
font.pointSize: Appearance.font.size.extraLarge * res.primaryMult
|
||||
}
|
||||
|
||||
StyledText {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
|
||||
text: res.sublabel1
|
||||
color: Colours.palette.m3onSurfaceVariant
|
||||
font.pointSize: Appearance.font.size.smaller * res.primaryMult
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
anchors.horizontalCenter: parent.right
|
||||
anchors.top: parent.verticalCenter
|
||||
anchors.horizontalCenterOffset: -res.thickness / 2
|
||||
anchors.topMargin: res.thickness / 2 + Appearance.spacing.small
|
||||
|
||||
StyledText {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
|
||||
text: res.label2
|
||||
font.pointSize: Appearance.font.size.smaller * res.primaryMult
|
||||
}
|
||||
|
||||
StyledText {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
|
||||
text: res.sublabel2
|
||||
color: Colours.palette.m3onSurfaceVariant
|
||||
font.pointSize: Appearance.font.size.small * res.primaryMult
|
||||
}
|
||||
}
|
||||
|
||||
Canvas {
|
||||
id: canvas
|
||||
|
||||
readonly property real centerX: width / 2
|
||||
readonly property real centerY: height / 2
|
||||
|
||||
readonly property real arc1Start: degToRad(45)
|
||||
readonly property real arc1End: degToRad(220)
|
||||
readonly property real arc2Start: degToRad(230)
|
||||
readonly property real arc2End: degToRad(360)
|
||||
|
||||
function degToRad(deg: int): real {
|
||||
return deg * Math.PI / 180;
|
||||
}
|
||||
|
||||
anchors.fill: parent
|
||||
|
||||
onPaint: {
|
||||
const ctx = getContext("2d");
|
||||
ctx.reset();
|
||||
|
||||
ctx.lineWidth = res.thickness;
|
||||
ctx.lineCap = "round";
|
||||
|
||||
const radius = (Math.min(width, height) - ctx.lineWidth) / 2;
|
||||
const cx = centerX;
|
||||
const cy = centerY;
|
||||
const a1s = arc1Start;
|
||||
const a1e = arc1End;
|
||||
const a2s = arc2Start;
|
||||
const a2e = arc2End;
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.arc(cx, cy, radius, a1s, a1e, false);
|
||||
ctx.strokeStyle = res.bg1;
|
||||
ctx.stroke();
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.arc(cx, cy, radius, a1s, (a1e - a1s) * res.value1 + a1s, false);
|
||||
ctx.strokeStyle = res.fg1;
|
||||
ctx.stroke();
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.arc(cx, cy, radius, a2s, a2e, false);
|
||||
ctx.strokeStyle = res.bg2;
|
||||
ctx.stroke();
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.arc(cx, cy, radius, a2s, (a2e - a2s) * res.value2 + a2s, false);
|
||||
ctx.strokeStyle = res.fg2;
|
||||
ctx.stroke();
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on value1 {
|
||||
NumberAnimation {
|
||||
duration: Appearance.anim.durations.normal
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: Appearance.anim.curves.standard
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on value2 {
|
||||
NumberAnimation {
|
||||
duration: Appearance.anim.durations.normal
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: Appearance.anim.curves.standard
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on fg1 {
|
||||
ColorAnimation {
|
||||
duration: Appearance.anim.durations.normal
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: Appearance.anim.curves.standard
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on fg2 {
|
||||
ColorAnimation {
|
||||
duration: Appearance.anim.durations.normal
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: Appearance.anim.curves.standard
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on bg1 {
|
||||
ColorAnimation {
|
||||
duration: Appearance.anim.durations.normal
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: Appearance.anim.curves.standard
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on bg2 {
|
||||
ColorAnimation {
|
||||
duration: Appearance.anim.durations.normal
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: Appearance.anim.curves.standard
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
249
modules/dashboard/Tabs.qml
Normal file
249
modules/dashboard/Tabs.qml
Normal file
|
@ -0,0 +1,249 @@
|
|||
import "root:/widgets"
|
||||
import "root:/services"
|
||||
import "root:/config"
|
||||
import Quickshell.Widgets
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
required property real nonAnimWidth
|
||||
property alias currentIndex: bar.currentIndex
|
||||
readonly property TabBar bar: bar
|
||||
|
||||
implicitHeight: bar.implicitHeight + indicator.implicitHeight + indicator.anchors.topMargin + separator.implicitHeight
|
||||
|
||||
TabBar {
|
||||
id: bar
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
|
||||
background: null
|
||||
|
||||
Tab {
|
||||
iconName: "dashboard"
|
||||
text: qsTr("Dashboard")
|
||||
}
|
||||
|
||||
Tab {
|
||||
iconName: "queue_music"
|
||||
text: qsTr("Media")
|
||||
}
|
||||
|
||||
Tab {
|
||||
iconName: "speed"
|
||||
text: qsTr("Performance")
|
||||
}
|
||||
|
||||
Tab {
|
||||
iconName: "workspaces"
|
||||
text: qsTr("Workspaces")
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: indicator
|
||||
|
||||
anchors.top: bar.bottom
|
||||
anchors.topMargin: DashboardConfig.sizes.tabIndicatorSpacing
|
||||
|
||||
implicitWidth: bar.currentItem.implicitWidth
|
||||
implicitHeight: DashboardConfig.sizes.tabIndicatorHeight
|
||||
|
||||
x: {
|
||||
const tab = bar.currentItem;
|
||||
const width = (root.nonAnimWidth - DashboardConfig.sizes.tabIndicatorSpacing * (bar.count - 1) * 2) / bar.count
|
||||
return width * tab.TabBar.index + (width - tab.implicitWidth) / 2;
|
||||
}
|
||||
|
||||
clip: true
|
||||
|
||||
StyledRect {
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
implicitHeight: parent.implicitHeight * 2
|
||||
|
||||
color: Colours.palette.m3primary
|
||||
radius: Appearance.rounding.full
|
||||
|
||||
}
|
||||
|
||||
Behavior on x {
|
||||
Anim {}
|
||||
}
|
||||
|
||||
Behavior on implicitWidth {
|
||||
Anim {}
|
||||
}
|
||||
}
|
||||
|
||||
StyledRect {
|
||||
id: separator
|
||||
|
||||
anchors.top: indicator.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
|
||||
implicitHeight: 1
|
||||
color: Colours.palette.m3outlineVariant
|
||||
}
|
||||
|
||||
component Tab: TabButton {
|
||||
id: tab
|
||||
|
||||
required property string iconName
|
||||
readonly property bool current: TabBar.tabBar.currentItem === this
|
||||
|
||||
background: null
|
||||
|
||||
contentItem: MouseArea {
|
||||
id: mouse
|
||||
|
||||
implicitWidth: Math.max(icon.width, label.width)
|
||||
implicitHeight: icon.height + label.height
|
||||
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
|
||||
onPressed: ({x,y}) => {
|
||||
tab.TabBar.tabBar.setCurrentIndex(tab.TabBar.index);
|
||||
|
||||
const stateY = stateWrapper.y;
|
||||
rippleAnim.x = x;
|
||||
rippleAnim.y = y - stateY;
|
||||
|
||||
const dist = (ox,oy) => ox * ox + oy * oy;
|
||||
const stateEndY = stateY + stateWrapper.height;
|
||||
rippleAnim.radius = Math.sqrt(Math.max(dist(0, stateY), dist(0, stateEndY), dist(width, stateY), dist(width, stateEndY)));
|
||||
|
||||
rippleAnim.restart();
|
||||
}
|
||||
onWheel: event => {
|
||||
if (event.angleDelta.y < 0)
|
||||
tab.TabBar.tabBar.incrementCurrentIndex();
|
||||
else if (event.angleDelta.y > 0)
|
||||
tab.TabBar.tabBar.decrementCurrentIndex();
|
||||
}
|
||||
|
||||
SequentialAnimation {
|
||||
id: rippleAnim
|
||||
|
||||
property real x
|
||||
property real y
|
||||
property real radius
|
||||
|
||||
PropertyAction {
|
||||
target: ripple
|
||||
property: "x"
|
||||
value: rippleAnim.x
|
||||
}
|
||||
PropertyAction {
|
||||
target: ripple
|
||||
property: "y"
|
||||
value: rippleAnim.y
|
||||
}
|
||||
PropertyAction {
|
||||
target: ripple
|
||||
property: "opacity"
|
||||
value: 0.1
|
||||
}
|
||||
ParallelAnimation {
|
||||
Anim {
|
||||
target: ripple
|
||||
properties: "implicitWidth,implicitHeight"
|
||||
from: 0
|
||||
to: rippleAnim.radius * 2
|
||||
duration: Appearance.anim.durations.large
|
||||
easing.bezierCurve: Appearance.anim.curves.standardDecel
|
||||
}
|
||||
Anim {
|
||||
target: ripple
|
||||
property: "opacity"
|
||||
to: 0
|
||||
duration: Appearance.anim.durations.large
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: Appearance.anim.curves.standardDecel
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ClippingRectangle {
|
||||
id: stateWrapper
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
implicitHeight: parent.height + DashboardConfig.sizes.tabIndicatorSpacing * 2
|
||||
|
||||
color: "transparent"
|
||||
radius: Appearance.rounding.small
|
||||
|
||||
StyledRect {
|
||||
id: stateLayer
|
||||
|
||||
anchors.fill: parent
|
||||
|
||||
color: tab.current ? Colours.palette.m3primary : Colours.palette.m3onSurface
|
||||
opacity: mouse.pressed ? 0.1 : tab.hovered ? 0.08 : 0
|
||||
|
||||
Behavior on opacity {
|
||||
Anim {}
|
||||
}
|
||||
}
|
||||
|
||||
StyledRect {
|
||||
id: ripple
|
||||
|
||||
radius: Appearance.rounding.full
|
||||
color: tab.current ? Colours.palette.m3primary : Colours.palette.m3onSurface
|
||||
opacity: 0
|
||||
|
||||
transform: Translate {
|
||||
x: -ripple.width / 2
|
||||
y: -ripple.height / 2
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MaterialIcon {
|
||||
id: icon
|
||||
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.bottom: label.top
|
||||
|
||||
text: tab.iconName
|
||||
color: tab.current ? Colours.palette.m3primary : Colours.palette.m3onSurfaceVariant
|
||||
fill: tab.current ? 1 : 0
|
||||
font.pointSize: Appearance.font.size.large
|
||||
|
||||
Behavior on fill {
|
||||
NumberAnimation {
|
||||
duration: Appearance.anim.durations.normal
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: Appearance.anim.curves.standard
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
id: label
|
||||
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.bottom: parent.bottom
|
||||
|
||||
text: tab.text
|
||||
color: tab.current ? Colours.palette.m3primary : Colours.palette.m3onSurfaceVariant
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
component Anim: NumberAnimation {
|
||||
duration: Appearance.anim.durations.normal
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: Appearance.anim.curves.standard
|
||||
}
|
||||
}
|
55
modules/dashboard/Wrapper.qml
Normal file
55
modules/dashboard/Wrapper.qml
Normal file
|
@ -0,0 +1,55 @@
|
|||
import QtQuick
|
||||
import Quickshell
|
||||
import "root:/config"
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
required property PersistentProperties visibilities
|
||||
|
||||
visible: height > 0
|
||||
implicitHeight: 0
|
||||
implicitWidth: content.implicitWidth
|
||||
|
||||
states: State {
|
||||
name: "visible"
|
||||
when: root.visibilities.dashboard
|
||||
|
||||
PropertyChanges {
|
||||
root.implicitHeight: content.implicitHeight
|
||||
}
|
||||
}
|
||||
|
||||
transitions: [
|
||||
Transition {
|
||||
from: ""
|
||||
to: "visible"
|
||||
|
||||
NumberAnimation {
|
||||
target: root
|
||||
property: "implicitHeight"
|
||||
duration: Appearance.anim.durations.expressiveDefaultSpatial
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
|
||||
}
|
||||
},
|
||||
Transition {
|
||||
from: "visible"
|
||||
to: ""
|
||||
|
||||
NumberAnimation {
|
||||
target: root
|
||||
property: "implicitHeight"
|
||||
duration: Appearance.anim.durations.normal
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: Appearance.anim.curves.emphasized
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
Content {
|
||||
id: content
|
||||
|
||||
visibilities: root.visibilities
|
||||
}
|
||||
}
|
70
modules/dashboard/dash/Calendar.qml
Normal file
70
modules/dashboard/dash/Calendar.qml
Normal file
|
@ -0,0 +1,70 @@
|
|||
import "root:/widgets"
|
||||
import "root:/services"
|
||||
import "root:/config"
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
|
||||
Column {
|
||||
id: root
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
padding: Appearance.padding.large
|
||||
spacing: Appearance.spacing.small
|
||||
|
||||
DayOfWeekRow {
|
||||
id: days
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.margins: parent.padding
|
||||
|
||||
delegate: StyledText {
|
||||
required property var model
|
||||
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
text: model.shortName
|
||||
font.family: Appearance.font.family.sans
|
||||
font.weight: 500
|
||||
}
|
||||
}
|
||||
|
||||
MonthGrid {
|
||||
id: grid
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.margins: parent.padding
|
||||
|
||||
spacing: 3
|
||||
|
||||
delegate: Item {
|
||||
id: day
|
||||
|
||||
required property var model
|
||||
|
||||
implicitWidth: implicitHeight
|
||||
implicitHeight: text.implicitHeight + Appearance.padding.small * 2
|
||||
|
||||
StyledRect {
|
||||
anchors.centerIn: parent
|
||||
|
||||
implicitWidth: parent.implicitHeight
|
||||
implicitHeight: parent.implicitHeight
|
||||
|
||||
radius: Appearance.rounding.full
|
||||
color: model.today ? Colours.palette.m3primary : "transparent"
|
||||
|
||||
StyledText {
|
||||
id: text
|
||||
|
||||
anchors.centerIn: parent
|
||||
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
text: grid.locale.toString(day.model.date, "d")
|
||||
color: day.model.today ? Colours.palette.m3onPrimary : day.model.month === grid.month ? Colours.palette.m3onSurfaceVariant : Colours.palette.m3outline
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
71
modules/dashboard/dash/DateTime.qml
Normal file
71
modules/dashboard/dash/DateTime.qml
Normal file
|
@ -0,0 +1,71 @@
|
|||
import "root:/widgets"
|
||||
import "root:/services"
|
||||
import "root:/config"
|
||||
import QtQuick
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
implicitWidth: DashboardConfig.sizes.dateTimeWidth
|
||||
|
||||
StyledText {
|
||||
id: hours
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: (root.height - (hours.implicitHeight + sep.implicitHeight + sep.anchors.topMargin + mins.implicitHeight + mins.anchors.topMargin + date.implicitHeight + date.anchors.topMargin)) / 2
|
||||
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
text: Time.format("HH")
|
||||
color: Colours.palette.m3secondary
|
||||
font.pointSize: Appearance.font.size.extraLarge
|
||||
font.weight: 500
|
||||
}
|
||||
|
||||
StyledText {
|
||||
id: sep
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: hours.bottom
|
||||
anchors.topMargin: -font.pointSize * 0.5
|
||||
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
text: "•••"
|
||||
color: Colours.palette.m3primary
|
||||
font.pointSize: Appearance.font.size.extraLarge * 0.9
|
||||
}
|
||||
|
||||
StyledText {
|
||||
id: mins
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: sep.bottom
|
||||
anchors.topMargin: -sep.font.pointSize * 0.45
|
||||
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
text: Time.format("MM")
|
||||
color: Colours.palette.m3secondary
|
||||
font.pointSize: Appearance.font.size.extraLarge
|
||||
font.weight: 500
|
||||
}
|
||||
|
||||
StyledText {
|
||||
id: date
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: mins.bottom
|
||||
anchors.topMargin: Appearance.spacing.normal
|
||||
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
text: Time.format("ddd, d")
|
||||
color: Colours.palette.m3tertiary
|
||||
font.pointSize: Appearance.font.size.normal
|
||||
font.weight: 500
|
||||
}
|
||||
}
|
262
modules/dashboard/dash/Media.qml
Normal file
262
modules/dashboard/dash/Media.qml
Normal file
|
@ -0,0 +1,262 @@
|
|||
import "root:/widgets"
|
||||
import "root:/services"
|
||||
import "root:/config"
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import Quickshell.Widgets
|
||||
import QtQuick
|
||||
import QtQuick.Shapes
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
required property bool shouldUpdate
|
||||
|
||||
property real playerProgress: {
|
||||
const active = Players.active;
|
||||
return active?.length ? active.position / active.length : 0;
|
||||
}
|
||||
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
implicitWidth: DashboardConfig.sizes.mediaWidth
|
||||
|
||||
Behavior on playerProgress {
|
||||
NumberAnimation {
|
||||
duration: Appearance.anim.durations.large
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: Appearance.anim.curves.standard
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
running: root.shouldUpdate && (Players.active?.isPlaying ?? false)
|
||||
interval: DashboardConfig.mediaUpdateInterval
|
||||
triggeredOnStart: true
|
||||
repeat: true
|
||||
onTriggered: Players.active?.positionChanged()
|
||||
}
|
||||
|
||||
Shape {
|
||||
preferredRendererType: Shape.CurveRenderer
|
||||
|
||||
ShapePath {
|
||||
fillColor: "transparent"
|
||||
strokeColor: Colours.palette.m3surfaceContainerHigh
|
||||
strokeWidth: DashboardConfig.sizes.mediaProgressThickness
|
||||
capStyle: ShapePath.RoundCap
|
||||
|
||||
PathAngleArc {
|
||||
centerX: cover.x + cover.width / 2
|
||||
centerY: cover.y + cover.height / 2
|
||||
radiusX: (cover.width + DashboardConfig.sizes.mediaProgressThickness) / 2 + Appearance.spacing.small
|
||||
radiusY: (cover.height + DashboardConfig.sizes.mediaProgressThickness) / 2 + Appearance.spacing.small
|
||||
startAngle: -90 - DashboardConfig.sizes.mediaProgressSweep / 2
|
||||
sweepAngle: DashboardConfig.sizes.mediaProgressSweep
|
||||
}
|
||||
|
||||
Behavior on strokeColor {
|
||||
ColorAnimation {
|
||||
duration: Appearance.anim.durations.normal
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: Appearance.anim.curves.standard
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ShapePath {
|
||||
fillColor: "transparent"
|
||||
strokeColor: Colours.palette.m3primary
|
||||
strokeWidth: DashboardConfig.sizes.mediaProgressThickness
|
||||
capStyle: ShapePath.RoundCap
|
||||
|
||||
PathAngleArc {
|
||||
centerX: cover.x + cover.width / 2
|
||||
centerY: cover.y + cover.height / 2
|
||||
radiusX: (cover.width + DashboardConfig.sizes.mediaProgressThickness) / 2 + Appearance.spacing.small
|
||||
radiusY: (cover.height + DashboardConfig.sizes.mediaProgressThickness) / 2 + Appearance.spacing.small
|
||||
startAngle: -90 - DashboardConfig.sizes.mediaProgressSweep / 2
|
||||
sweepAngle: DashboardConfig.sizes.mediaProgressSweep * root.playerProgress
|
||||
}
|
||||
|
||||
Behavior on strokeColor {
|
||||
ColorAnimation {
|
||||
duration: Appearance.anim.durations.normal
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: Appearance.anim.curves.standard
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StyledClippingRect {
|
||||
id: cover
|
||||
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.margins: Appearance.padding.large + DashboardConfig.sizes.mediaProgressThickness + Appearance.spacing.small
|
||||
|
||||
implicitHeight: width
|
||||
color: Colours.palette.m3surfaceContainerHigh
|
||||
radius: Appearance.rounding.full
|
||||
|
||||
MaterialIcon {
|
||||
anchors.centerIn: parent
|
||||
|
||||
text: "art_track"
|
||||
color: Colours.palette.m3onSurfaceVariant
|
||||
font.pointSize: (parent.width * 0.4) || 1
|
||||
}
|
||||
|
||||
Image {
|
||||
id: image
|
||||
|
||||
anchors.fill: parent
|
||||
|
||||
source: Players.active?.trackArtUrl ?? ""
|
||||
asynchronous: true
|
||||
fillMode: Image.PreserveAspectCrop
|
||||
sourceSize.width: width
|
||||
sourceSize.height: height
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
id: title
|
||||
|
||||
anchors.top: cover.bottom
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.topMargin: Appearance.spacing.normal
|
||||
|
||||
animate: true
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
text: (Players.active?.trackTitle ?? qsTr("No media")) || qsTr("Unknown title")
|
||||
color: Colours.palette.m3primary
|
||||
font.pointSize: Appearance.font.size.normal
|
||||
|
||||
width: parent.implicitWidth - Appearance.padding.large * 2
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
StyledText {
|
||||
id: album
|
||||
|
||||
anchors.top: title.bottom
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.topMargin: Appearance.spacing.small
|
||||
|
||||
animate: true
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
text: (Players.active?.trackAlbum ?? qsTr("No media")) || qsTr("Unknown album")
|
||||
color: Colours.palette.m3outline
|
||||
font.pointSize: Appearance.font.size.small
|
||||
|
||||
width: parent.implicitWidth - Appearance.padding.large * 2
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
StyledText {
|
||||
id: artist
|
||||
|
||||
anchors.top: album.bottom
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.topMargin: Appearance.spacing.small
|
||||
|
||||
animate: true
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
text: (Players.active?.trackArtist ?? qsTr("No media")) || qsTr("Unknown artist")
|
||||
color: Colours.palette.m3secondary
|
||||
|
||||
width: parent.implicitWidth - Appearance.padding.large * 2
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
Row {
|
||||
id: controls
|
||||
|
||||
anchors.top: artist.bottom
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.topMargin: Appearance.spacing.smaller
|
||||
|
||||
spacing: Appearance.spacing.small
|
||||
|
||||
Control {
|
||||
icon: "skip_previous"
|
||||
canUse: Players.active?.canGoPrevious ?? false
|
||||
|
||||
function onClicked(): void {
|
||||
Players.active?.previous();
|
||||
}
|
||||
}
|
||||
|
||||
Control {
|
||||
icon: Players.active?.isPlaying ? "pause" : "play_arrow"
|
||||
canUse: Players.active?.canTogglePlaying ?? false
|
||||
|
||||
function onClicked(): void {
|
||||
Players.active?.togglePlaying();
|
||||
}
|
||||
}
|
||||
|
||||
Control {
|
||||
icon: "skip_next"
|
||||
canUse: Players.active?.canGoNext ?? false
|
||||
|
||||
function onClicked(): void {
|
||||
Players.active?.next();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AnimatedImage {
|
||||
id: bongocat
|
||||
|
||||
anchors.top: controls.bottom
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.topMargin: Appearance.spacing.small
|
||||
anchors.bottomMargin: Appearance.padding.large
|
||||
anchors.margins: Appearance.padding.large * 2
|
||||
|
||||
playing: root.shouldUpdate && (Players.active?.isPlaying ?? false)
|
||||
speed: BeatDetector.bpm / 300
|
||||
source: "root:/assets/bongocat.gif"
|
||||
asynchronous: true
|
||||
fillMode: AnimatedImage.PreserveAspectFit
|
||||
}
|
||||
|
||||
component Control: StyledRect {
|
||||
id: control
|
||||
|
||||
required property string icon
|
||||
required property bool canUse
|
||||
function onClicked(): void {
|
||||
}
|
||||
|
||||
implicitWidth: Math.max(icon.implicitHeight, icon.implicitHeight) + Appearance.padding.small
|
||||
implicitHeight: implicitWidth
|
||||
|
||||
StateLayer {
|
||||
disabled: !control.canUse
|
||||
radius: Appearance.rounding.full
|
||||
|
||||
function onClicked(): void {
|
||||
control.onClicked();
|
||||
}
|
||||
}
|
||||
|
||||
MaterialIcon {
|
||||
id: icon
|
||||
|
||||
anchors.centerIn: parent
|
||||
anchors.verticalCenterOffset: font.pointSize * 0.05
|
||||
|
||||
animate: true
|
||||
text: control.icon
|
||||
color: control.canUse ? Colours.palette.m3onSurface : Colours.palette.m3outline
|
||||
font.pointSize: Appearance.font.size.large
|
||||
}
|
||||
}
|
||||
}
|
85
modules/dashboard/dash/Resources.qml
Normal file
85
modules/dashboard/dash/Resources.qml
Normal file
|
@ -0,0 +1,85 @@
|
|||
import "root:/widgets"
|
||||
import "root:/services"
|
||||
import "root:/config"
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
|
||||
Row {
|
||||
id: root
|
||||
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
|
||||
padding: Appearance.padding.large
|
||||
spacing: Appearance.spacing.normal
|
||||
|
||||
Resource {
|
||||
icon: "memory"
|
||||
value: SystemUsage.cpuPerc
|
||||
colour: Colours.palette.m3primary
|
||||
}
|
||||
|
||||
Resource {
|
||||
icon: "memory_alt"
|
||||
value: SystemUsage.memPerc
|
||||
colour: Colours.palette.m3secondary
|
||||
}
|
||||
|
||||
Resource {
|
||||
icon: "hard_disk"
|
||||
value: SystemUsage.storagePerc
|
||||
colour: Colours.palette.m3tertiary
|
||||
}
|
||||
|
||||
component Resource: Item {
|
||||
id: res
|
||||
|
||||
required property string icon
|
||||
required property real value
|
||||
required property color colour
|
||||
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.margins: Appearance.padding.large
|
||||
implicitWidth: icon.implicitWidth
|
||||
|
||||
StyledRect {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: icon.top
|
||||
anchors.bottomMargin: Appearance.spacing.small
|
||||
|
||||
implicitWidth: DashboardConfig.sizes.resourceProgessThickness
|
||||
|
||||
color: Colours.palette.m3surfaceContainerHigh
|
||||
radius: Appearance.rounding.full
|
||||
|
||||
StyledRect {
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
implicitHeight: res.value * parent.height
|
||||
|
||||
color: res.colour
|
||||
radius: Appearance.rounding.full
|
||||
}
|
||||
}
|
||||
|
||||
MaterialIcon {
|
||||
id: icon
|
||||
|
||||
anchors.bottom: parent.bottom
|
||||
|
||||
text: res.icon
|
||||
color: res.colour
|
||||
}
|
||||
|
||||
Behavior on value {
|
||||
NumberAnimation {
|
||||
duration: Appearance.anim.durations.large
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: Appearance.anim.curves.standard
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
116
modules/dashboard/dash/User.qml
Normal file
116
modules/dashboard/dash/User.qml
Normal file
|
@ -0,0 +1,116 @@
|
|||
import "root:/widgets"
|
||||
import "root:/services"
|
||||
import "root:/config"
|
||||
import "root:/utils"
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import QtQuick
|
||||
|
||||
Row {
|
||||
id: root
|
||||
|
||||
padding: Appearance.padding.large
|
||||
spacing: Appearance.spacing.large
|
||||
|
||||
StyledClippingRect {
|
||||
implicitWidth: info.implicitHeight
|
||||
implicitHeight: info.implicitHeight
|
||||
|
||||
radius: Appearance.rounding.full
|
||||
color: Colours.palette.m3surfaceContainerHigh
|
||||
|
||||
MaterialIcon {
|
||||
anchors.centerIn: parent
|
||||
|
||||
text: "person"
|
||||
fill: 1
|
||||
font.pointSize: (info.implicitHeight / 2) || 1
|
||||
}
|
||||
|
||||
CachingImage {
|
||||
anchors.fill: parent
|
||||
path: `${Paths.home}/.face`
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
id: info
|
||||
|
||||
spacing: Appearance.spacing.normal
|
||||
|
||||
InfoLine {
|
||||
icon: Icons.osIcon
|
||||
text: Icons.osName
|
||||
colour: Colours.palette.m3primary
|
||||
}
|
||||
|
||||
InfoLine {
|
||||
icon: "select_window_2"
|
||||
text: Quickshell.env("XDG_CURRENT_DESKTOP") || Quickshell.env("XDG_SESSION_DESKTOP")
|
||||
colour: Colours.palette.m3secondary
|
||||
}
|
||||
|
||||
InfoLine {
|
||||
icon: "timer"
|
||||
text: uptimeProc.uptime
|
||||
colour: Colours.palette.m3tertiary
|
||||
|
||||
Timer {
|
||||
running: true
|
||||
repeat: true
|
||||
interval: 15000
|
||||
onTriggered: uptimeProc.running = true
|
||||
}
|
||||
|
||||
Process {
|
||||
id: uptimeProc
|
||||
|
||||
property string uptime
|
||||
|
||||
running: true
|
||||
command: ["uptime", "-p"]
|
||||
stdout: SplitParser {
|
||||
onRead: data => uptimeProc.uptime = data
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
component InfoLine: Item {
|
||||
id: line
|
||||
|
||||
required property string icon
|
||||
required property string text
|
||||
required property color colour
|
||||
|
||||
implicitWidth: icon.implicitWidth + text.width + text.anchors.leftMargin
|
||||
implicitHeight: Math.max(icon.implicitHeight, text.implicitHeight)
|
||||
|
||||
MaterialIcon {
|
||||
id: icon
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: (DashboardConfig.sizes.infoIconSize - implicitWidth) / 2
|
||||
|
||||
text: line.icon
|
||||
color: line.colour
|
||||
font.pointSize: Appearance.font.size.normal
|
||||
font.variableAxes: ({
|
||||
FILL: 1
|
||||
})
|
||||
}
|
||||
|
||||
StyledText {
|
||||
id: text
|
||||
|
||||
anchors.verticalCenter: icon.verticalCenter
|
||||
anchors.left: icon.right
|
||||
anchors.leftMargin: icon.anchors.leftMargin
|
||||
text: `: ${line.text}`
|
||||
font.pointSize: Appearance.font.size.normal
|
||||
|
||||
width: DashboardConfig.sizes.infoWidth
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
}
|
||||
}
|
63
modules/dashboard/dash/Weather.qml
Normal file
63
modules/dashboard/dash/Weather.qml
Normal file
|
@ -0,0 +1,63 @@
|
|||
import "root:/widgets"
|
||||
import "root:/services"
|
||||
import "root:/config"
|
||||
import "root:/utils"
|
||||
import QtQuick
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
anchors.centerIn: parent
|
||||
|
||||
implicitWidth: icon.implicitWidth + info.implicitWidth + info.anchors.leftMargin
|
||||
|
||||
onVisibleChanged: {
|
||||
if (visible)
|
||||
Weather.reload();
|
||||
}
|
||||
|
||||
MaterialIcon {
|
||||
id: icon
|
||||
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.left: parent.left
|
||||
|
||||
animate: true
|
||||
text: Weather.icon || "cloud_alert"
|
||||
color: Colours.palette.m3secondary
|
||||
font.pointSize: Appearance.font.size.extraLarge * 2
|
||||
font.variableAxes: ({
|
||||
opsz: Appearance.font.size.extraLarge * 1.2
|
||||
})
|
||||
}
|
||||
|
||||
Column {
|
||||
id: info
|
||||
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.left: icon.right
|
||||
anchors.leftMargin: Appearance.spacing.large
|
||||
|
||||
spacing: Appearance.spacing.small
|
||||
|
||||
StyledText {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
|
||||
animate: true
|
||||
text: `${Weather.temperature}°C`
|
||||
color: Colours.palette.m3primary
|
||||
font.pointSize: Appearance.font.size.extraLarge
|
||||
font.weight: 500
|
||||
}
|
||||
|
||||
StyledText {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
|
||||
animate: true
|
||||
text: Weather.description || qsTr("No weather")
|
||||
|
||||
elide: Text.ElideRight
|
||||
width: Math.min(implicitWidth, root.parent.width - icon.implicitWidth - info.anchors.leftMargin - Appearance.padding.large * 2)
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue