dashboard/media: improve ui

This commit is contained in:
2 * r + 2 * t 2025-09-23 23:16:17 +10:00
parent 603caa945a
commit 06f366e27e
4 changed files with 82 additions and 269 deletions

View file

@ -18,6 +18,7 @@ StyledRect {
property real padding: type === IconButton.Text ? Appearance.padding.small / 2 : Appearance.padding.smaller property real padding: type === IconButton.Text ? Appearance.padding.small / 2 : Appearance.padding.smaller
property alias font: label.font property alias font: label.font
property int type: IconButton.Filled property int type: IconButton.Filled
property bool disabled
property alias stateLayer: stateLayer property alias stateLayer: stateLayer
property alias label: label property alias label: label
@ -36,13 +37,15 @@ StyledRect {
return Colours.palette.m3onPrimary; return Colours.palette.m3onPrimary;
return type === IconButton.Tonal ? Colours.palette.m3onSecondaryContainer : Colours.palette.m3onSurfaceVariant; return type === IconButton.Tonal ? Colours.palette.m3onSecondaryContainer : Colours.palette.m3onSurfaceVariant;
} }
property color disabledColour: Qt.alpha(Colours.palette.m3onSurface, 0.1)
property color disabledOnColour: Qt.alpha(Colours.palette.m3onSurface, 0.38)
signal clicked signal clicked
onCheckedChanged: internalChecked = checked onCheckedChanged: internalChecked = checked
radius: internalChecked ? Appearance.rounding.small : implicitHeight / 2 radius: internalChecked ? Appearance.rounding.small : implicitHeight / 2
color: type === IconButton.Text ? "transparent" : internalChecked ? activeColour : inactiveColour color: type === IconButton.Text ? "transparent" : disabled ? disabledColour : internalChecked ? activeColour : inactiveColour
implicitWidth: implicitHeight implicitWidth: implicitHeight
implicitHeight: label.implicitHeight + padding * 2 implicitHeight: label.implicitHeight + padding * 2
@ -51,6 +54,7 @@ StyledRect {
id: stateLayer id: stateLayer
color: root.internalChecked ? root.activeOnColour : root.inactiveOnColour color: root.internalChecked ? root.activeOnColour : root.inactiveOnColour
disabled: root.disabled
function onClicked(): void { function onClicked(): void {
if (root.toggle) if (root.toggle)
@ -63,7 +67,7 @@ StyledRect {
id: label id: label
anchors.centerIn: parent anchors.centerIn: parent
color: root.internalChecked ? root.activeOnColour : root.inactiveOnColour color: root.disabled ? root.disabledOnColour : root.internalChecked ? root.activeOnColour : root.inactiveOnColour
fill: !root.toggle || root.internalChecked ? 1 : 0 fill: !root.toggle || root.internalChecked ? 1 : 0
Behavior on fill { Behavior on fill {

View file

@ -19,7 +19,7 @@ Elevation {
radius: Appearance.rounding.small / 2 radius: Appearance.rounding.small / 2
level: 2 level: 2
implicitWidth: column.implicitWidth implicitWidth: Math.max(200, column.implicitWidth)
implicitHeight: root.expanded ? column.implicitHeight : 0 implicitHeight: root.expanded ? column.implicitHeight : 0
opacity: root.expanded ? 1 : 0 opacity: root.expanded ? 1 : 0
@ -31,6 +31,8 @@ Elevation {
ColumnLayout { ColumnLayout {
id: column id: column
anchors.left: parent.left
anchors.right: parent.right
spacing: 0 spacing: 0
Repeater { Repeater {

View file

@ -16,15 +16,21 @@ Row {
property real verticalPadding: Appearance.padding.smaller property real verticalPadding: Appearance.padding.smaller
property int type: SplitButton.Filled property int type: SplitButton.Filled
property bool disabled property bool disabled
property string fallbackIcon
property string fallbackText
property alias menuItems: menu.items property alias menuItems: menu.items
property alias active: menu.active property alias active: menu.active
property alias expanded: menu.expanded property alias expanded: menu.expanded
property alias menu: menu property alias menu: menu
property alias iconLabel: iconLabel
property alias label: label
property alias stateLayer: stateLayer
property color colour: type == SplitButton.Filled ? Colours.palette.m3primary : Colours.palette.m3secondaryContainer property color colour: type == SplitButton.Filled ? Colours.palette.m3primary : Colours.palette.m3secondaryContainer
property color textColour: type == SplitButton.Filled ? Colours.palette.m3onPrimary : Colours.palette.m3onSecondaryContainer property color textColour: type == SplitButton.Filled ? Colours.palette.m3onPrimary : Colours.palette.m3onSecondaryContainer
readonly property color disabledColour: Qt.alpha(Colours.palette.m3onSurface, 0.1) property color disabledColour: Qt.alpha(Colours.palette.m3onSurface, 0.1)
readonly property color disabledTextColour: Qt.alpha(Colours.palette.m3onSurface, 0.38) property color disabledTextColour: Qt.alpha(Colours.palette.m3onSurface, 0.38)
spacing: Math.floor(Appearance.spacing.small / 2) spacing: Math.floor(Appearance.spacing.small / 2)
@ -62,7 +68,7 @@ Row {
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
animate: true animate: true
text: root.active?.activeIcon ?? "" text: root.active?.activeIcon ?? root.fallbackIcon
color: root.disabled ? root.disabledTextColour : root.textColour color: root.disabled ? root.disabledTextColour : root.textColour
fill: 1 fill: 1
} }
@ -73,7 +79,7 @@ Row {
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
Layout.preferredWidth: implicitWidth Layout.preferredWidth: implicitWidth
animate: true animate: true
text: root.active?.activeText ?? "" text: root.active?.activeText ?? root.fallbackText
color: root.disabled ? root.disabledTextColour : root.textColour color: root.disabled ? root.disabledTextColour : root.textColour
clip: true clip: true

View file

@ -170,6 +170,7 @@ Item {
text: (Players.active?.trackTitle ?? qsTr("No media")) || qsTr("Unknown title") text: (Players.active?.trackTitle ?? qsTr("No media")) || qsTr("Unknown title")
color: Players.active ? Colours.palette.m3primary : Colours.palette.m3onSurface color: Players.active ? Colours.palette.m3primary : Colours.palette.m3onSurface
font.pointSize: Appearance.font.size.normal font.pointSize: Appearance.font.size.normal
elide: Text.ElideRight
} }
StyledText { StyledText {
@ -184,6 +185,7 @@ Item {
text: Players.active?.trackAlbum || qsTr("Unknown album") text: Players.active?.trackAlbum || qsTr("Unknown album")
color: Colours.palette.m3outline color: Colours.palette.m3outline
font.pointSize: Appearance.font.size.small font.pointSize: Appearance.font.size.small
elide: Text.ElideRight
} }
StyledText { StyledText {
@ -210,73 +212,30 @@ Item {
spacing: Appearance.spacing.small spacing: Appearance.spacing.small
PlayerControl { PlayerControl {
type: IconButton.Text
icon: "skip_previous" icon: "skip_previous"
canUse: Players.active?.canGoPrevious ?? false font.pointSize: Math.round(Appearance.font.size.large * 1.5)
disabled: !Players.active?.canGoPrevious
function onClicked(): void { onClicked: Players.active?.previous()
Players.active?.previous();
}
}
StyledRect {
id: playBtn
property int fontSize: Appearance.font.size.extraLarge
property int padding
property bool fill: true
property bool primary
function onClicked(): void {
}
implicitWidth: Math.max(playIcon.implicitWidth, playIcon.implicitHeight) + padding * 2
implicitHeight: implicitWidth
radius: Players.active?.isPlaying ? Appearance.rounding.small : implicitHeight / 2 * Math.min(1, Appearance.rounding.scale)
color: {
if (!Players.active?.canTogglePlaying)
return Qt.alpha(Colours.palette.m3onSurface, 0.1);
return Players.active?.isPlaying ? Colours.palette.m3primary : Colours.palette.m3primaryContainer;
}
StateLayer {
disabled: !Players.active?.canTogglePlaying
color: Players.active?.isPlaying ? Colours.palette.m3onPrimary : Colours.palette.m3onPrimaryContainer
function onClicked(): void {
Players.active?.togglePlaying();
}
}
MaterialIcon {
id: playIcon
anchors.centerIn: parent
anchors.horizontalCenterOffset: -font.pointSize * 0.02
anchors.verticalCenterOffset: font.pointSize * 0.02
animate: true
fill: 1
text: Players.active?.isPlaying ? "pause" : "play_arrow"
color: {
if (!Players.active?.canTogglePlaying)
return Qt.alpha(Colours.palette.m3onSurface, 0.38);
return Players.active?.isPlaying ? Colours.palette.m3onPrimary : Colours.palette.m3onPrimaryContainer;
}
font.pointSize: Appearance.font.size.extraLarge
}
Behavior on radius {
Anim {}
}
} }
PlayerControl { PlayerControl {
icon: "skip_next" icon: Players.active?.isPlaying ? "pause" : "play_arrow"
canUse: Players.active?.canGoNext ?? false label.animate: true
toggle: true
function onClicked(): void { padding: Appearance.padding.small / 2
Players.active?.next(); checked: Players.active?.isPlaying
font.pointSize: Math.round(Appearance.font.size.large * 1.5)
disabled: !Players.active?.canTogglePlaying
onClicked: Players.active?.togglePlaying()
} }
PlayerControl {
type: IconButton.Text
icon: "skip_next"
font.pointSize: Math.round(Appearance.font.size.large * 1.5)
disabled: !Players.active?.canGoNext
onClicked: Players.active?.next()
} }
} }
@ -284,7 +243,7 @@ Item {
id: slider id: slider
enabled: !!Players.active enabled: !!Players.active
implicitWidth: controls.implicitWidth * 1.5 implicitWidth: 280
implicitHeight: Appearance.padding.normal * 3 implicitHeight: Appearance.padding.normal * 3
onMoved: { onMoved: {
@ -348,166 +307,60 @@ Item {
spacing: Appearance.spacing.small spacing: Appearance.spacing.small
PlayerControl { PlayerControl {
icon: "flip_to_front" type: IconButton.Text
canUse: Players.active?.canRaise ?? false icon: "move_up"
fontSize: Appearance.font.size.larger inactiveOnColour: Colours.palette.m3secondary
padding: Appearance.padding.small padding: Appearance.padding.small
fill: false font.pointSize: Appearance.font.size.large
color: Colours.tPalette.m3surfaceContainer disabled: !Players.active?.canRaise
onClicked: {
function onClicked(): void {
Players.active?.raise(); Players.active?.raise();
root.visibilities.dashboard = false; root.visibilities.dashboard = false;
} }
} }
StyledRect { SplitButton {
id: playerSelector id: playerSelector
property bool expanded disabled: !Players.list.length
active: menuItems.find(m => m.modelData === Players.active) ?? menuItems[0]
menu.onItemSelected: item => Players.manualActive = item.modelData
Layout.alignment: Qt.AlignVCenter menuItems: playerList.instances
fallbackIcon: "music_off"
fallbackText: qsTr("No players")
implicitWidth: slider.implicitWidth * 0.6 label.Layout.maximumWidth: slider.implicitWidth * 0.28
implicitHeight: currentPlayer.implicitHeight + Appearance.padding.smaller * 2 label.elide: Text.ElideRight
radius: Appearance.rounding.normal
color: Colours.tPalette.m3surfaceContainer
z: 1
StateLayer { stateLayer.disabled: true
disabled: Players.list.length <= 1 menu.anchors.top: undefined
menu.anchors.bottom: menu.parent.top
menu.anchors.bottomMargin: Appearance.spacing.small
function onClicked(): void { Variants {
playerSelector.expanded = !playerSelector.expanded;
}
}
RowLayout {
id: currentPlayer
anchors.centerIn: parent
spacing: Appearance.spacing.small
PlayerIcon {
player: Players.active
}
StyledText {
Layout.fillWidth: true
Layout.maximumWidth: playerSelector.implicitWidth - implicitHeight - parent.spacing - Appearance.padding.normal * 2
text: Players.active ? Players.getIdentity(Players.active) : qsTr("No players")
color: Players.active ? Colours.palette.m3onSurface : Colours.palette.m3onSurfaceVariant
elide: Text.ElideRight
}
}
Elevation {
anchors.fill: playerSelectorBg
radius: playerSelectorBg.radius
opacity: playerSelector.expanded ? 1 : 0
level: 2
Behavior on opacity {
Anim {
duration: Appearance.anim.durations.expressiveDefaultSpatial
}
}
}
StyledClippingRect {
id: playerSelectorBg
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: parent.bottom
implicitWidth: playerSelector.expanded ? playerList.implicitWidth : playerSelector.implicitWidth
implicitHeight: playerSelector.expanded ? playerList.implicitHeight : playerSelector.implicitHeight
color: Colours.palette.m3secondaryContainer
radius: Appearance.rounding.normal
opacity: playerSelector.expanded ? 1 : 0
ColumnLayout {
id: playerList id: playerList
anchors.horizontalCenter: parent.horizontalCenter model: Players.list
anchors.bottom: parent.bottom
spacing: 0
Repeater {
model: [...Players.list].sort((a, b) => (a === Players.active) - (b === Players.active))
Item {
id: player
MenuItem {
required property MprisPlayer modelData required property MprisPlayer modelData
Layout.fillWidth: true icon: modelData === Players.active ? "check" : ""
Layout.minimumWidth: playerSelector.implicitWidth text: Players.getIdentity(modelData)
implicitWidth: playerInner.implicitWidth + Appearance.padding.normal * 2 activeIcon: "animated_images"
implicitHeight: playerInner.implicitHeight + Appearance.padding.smaller * 2
StateLayer {
disabled: !playerSelector.expanded
function onClicked(): void {
playerSelector.expanded = false;
Players.manualActive = player.modelData;
}
}
RowLayout {
id: playerInner
anchors.centerIn: parent
spacing: Appearance.spacing.small
PlayerIcon {
player: player.modelData
}
StyledText {
text: Players.getIdentity(player.modelData)
color: Colours.palette.m3onSecondaryContainer
}
}
}
}
}
Behavior on opacity {
Anim {
duration: Appearance.anim.durations.expressiveDefaultSpatial
}
}
Behavior on implicitWidth {
Anim {
duration: Appearance.anim.durations.expressiveDefaultSpatial
easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
}
}
Behavior on implicitHeight {
Anim {
duration: Appearance.anim.durations.expressiveDefaultSpatial
easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
}
} }
} }
} }
PlayerControl { PlayerControl {
type: IconButton.Text
icon: "delete" icon: "delete"
canUse: Players.active?.canQuit ?? false inactiveOnColour: Colours.palette.m3error
fontSize: Appearance.font.size.larger
padding: Appearance.padding.small padding: Appearance.padding.small
fill: false font.pointSize: Appearance.font.size.large
color: Colours.tPalette.m3surfaceContainer disabled: !Players.active?.canQuit
onClicked: Players.active?.quit()
function onClicked(): void {
Players.active?.quit();
}
} }
} }
} }
@ -536,69 +389,17 @@ Item {
} }
} }
component PlayerIcon: Loader { component PlayerControl: IconButton {
id: loader Layout.preferredWidth: implicitWidth + (stateLayer.pressed ? Appearance.padding.large : internalChecked ? Appearance.padding.smaller : 0)
radius: stateLayer.pressed ? Appearance.rounding.small / 2 : internalChecked ? Appearance.rounding.small : implicitHeight / 2
radiusAnim.duration: Appearance.anim.durations.expressiveFastSpatial
radiusAnim.easing.bezierCurve: Appearance.anim.curves.expressiveFastSpatial
required property MprisPlayer player Behavior on Layout.preferredWidth {
readonly property string icon: Icons.getAppIcon(player?.identity) Anim {
duration: Appearance.anim.durations.expressiveFastSpatial
Layout.fillHeight: true easing.bezierCurve: Appearance.anim.curves.expressiveFastSpatial
sourceComponent: !player || icon === "image://icon/" ? fallbackIcon : playerImage
Component {
id: playerImage
IconImage {
implicitWidth: height
source: loader.icon
}
}
Component {
id: fallbackIcon
MaterialIcon {
text: loader.player ? "animated_images" : "music_off"
} }
} }
} }
component PlayerControl: 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
function onClicked(): void {
}
implicitWidth: Math.max(icon.implicitWidth, icon.implicitHeight) + padding * 2
implicitHeight: implicitWidth
radius: Appearance.rounding.full
StateLayer {
disabled: !control.canUse
color: Colours.palette.m3onSurface
function onClicked(): void {
control.onClicked();
}
}
MaterialIcon {
id: icon
anchors.centerIn: parent
anchors.horizontalCenterOffset: -font.pointSize * 0.02
anchors.verticalCenterOffset: font.pointSize * 0.02
animate: true
fill: control.fill ? 1 : 0
text: control.icon
color: control.canUse ? Colours.palette.m3onSurface : Colours.palette.m3outline
font.pointSize: control.fontSize
}
}
} }