Bring in line
This commit is contained in:
+115
-122
@@ -287,6 +287,22 @@ Panel {
|
|||||||
return inputMuted ? "" : ""
|
return inputMuted ? "" : ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Playful mood-name for a given output volume. Mirrors the brightness
|
||||||
|
// panel's brightnessName ladder; bands are wide enough that small
|
||||||
|
// tweaks don't rename the room you're in.
|
||||||
|
function outputVolumeName(volume, muted) {
|
||||||
|
if (muted) return "Muted"
|
||||||
|
var p = Math.round(volume * 100)
|
||||||
|
if (p === 0) return "Silenced"
|
||||||
|
if (p >= 100) return "Concert hall"
|
||||||
|
if (p >= 85) return "Party mode"
|
||||||
|
if (p >= 70) return "Cranked up"
|
||||||
|
if (p >= 50) return "Steady groove"
|
||||||
|
if (p >= 30) return "Easy listening"
|
||||||
|
if (p >= 15) return "Murmur"
|
||||||
|
return "Whisper"
|
||||||
|
}
|
||||||
|
|
||||||
function setOutputVolume(v) {
|
function setOutputVolume(v) {
|
||||||
if (!sink || !sink.audio) return
|
if (!sink || !sink.audio) return
|
||||||
sink.audio.volume = Math.max(0, Math.min(1, v))
|
sink.audio.volume = Math.max(0, Math.min(1, v))
|
||||||
@@ -588,105 +604,105 @@ Panel {
|
|||||||
width: scrollArea.availableWidth
|
width: scrollArea.availableWidth
|
||||||
spacing: Style.space(14)
|
spacing: Style.space(14)
|
||||||
|
|
||||||
|
// ---------- Hero: speaker icon · mood · % ----------
|
||||||
|
Item {
|
||||||
|
id: heroItem
|
||||||
|
width: parent.width
|
||||||
|
implicitHeight: Math.max(heroIcon.implicitHeight, heroPercent.implicitHeight)
|
||||||
|
|
||||||
|
Text {
|
||||||
|
id: heroIcon
|
||||||
|
text: root.outputIcon()
|
||||||
|
color: root.bar.foreground
|
||||||
|
font.family: root.bar.fontFamily
|
||||||
|
font.pixelSize: Style.font.display
|
||||||
|
opacity: root.outputMuted ? 0.5 : 1.0
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: root.toggleOutputMute()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
id: heroLabel
|
||||||
|
text: root.outputVolumeName(
|
||||||
|
outputSlider.dragging ? outputSlider.liveValue : root.outputVolume,
|
||||||
|
root.outputMuted
|
||||||
|
).toUpperCase()
|
||||||
|
color: Qt.darker(root.bar.foreground, 1.4)
|
||||||
|
font.family: root.bar.fontFamily
|
||||||
|
font.pixelSize: Style.font.caption
|
||||||
|
font.bold: true
|
||||||
|
font.letterSpacing: 1.2
|
||||||
|
anchors.left: heroIcon.right
|
||||||
|
anchors.leftMargin: Style.space(14)
|
||||||
|
anchors.right: heroPercent.left
|
||||||
|
anchors.rightMargin: Style.space(10)
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
elide: Text.ElideRight
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
id: heroPercent
|
||||||
|
text: Math.round((outputSlider.dragging ? outputSlider.liveValue : root.outputVolume) * 100) + "%"
|
||||||
|
color: root.bar.foreground
|
||||||
|
font.family: root.bar.fontFamily
|
||||||
|
font.pixelSize: Style.font.displayLarge
|
||||||
|
font.bold: true
|
||||||
|
opacity: root.outputMuted ? 0.5 : 1.0
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ---- Output ----
|
// ---- Output ----
|
||||||
Column {
|
Column {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
spacing: Style.space(6)
|
spacing: Style.space(6)
|
||||||
|
|
||||||
Row {
|
Text {
|
||||||
|
text: root.sink ? "Output · " + root.nodeLabel(root.sink) : "Output"
|
||||||
|
color: Qt.darker(root.bar.foreground, 1.6)
|
||||||
|
font.family: root.bar.fontFamily
|
||||||
|
font.pixelSize: Style.font.bodySmall
|
||||||
|
elide: Text.ElideRight
|
||||||
width: parent.width
|
width: parent.width
|
||||||
spacing: Style.space(8)
|
|
||||||
|
|
||||||
PanelSectionHeader {
|
|
||||||
text: "Output"
|
|
||||||
foreground: root.bar.foreground
|
|
||||||
fontFamily: root.bar.fontFamily
|
|
||||||
fontSize: Style.font.bodySmall
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
Text {
|
|
||||||
text: root.sink ? "· " + root.nodeLabel(root.sink) : ""
|
|
||||||
color: Qt.darker(root.bar.foreground, 1.8)
|
|
||||||
font.family: root.bar.fontFamily
|
|
||||||
font.pixelSize: Style.font.bodySmall
|
|
||||||
elide: Text.ElideRight
|
|
||||||
width: parent.width - Style.space(70)
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Output slider row — itself a cursor target (selectedIndex === -1
|
// Output slider row — keyboard cursor target (selectedIndex === -1
|
||||||
// when focusSection === "output"). h/l adjust the value via
|
// when focusSection === "output"). Icon/percent live in the hero
|
||||||
// root.adjustVolume; m / Enter toggle mute.
|
// above, so the slider takes the full row width.
|
||||||
CursorSurface {
|
CursorSurface {
|
||||||
id: outputSliderRow
|
id: outputSliderRow
|
||||||
width: parent.width
|
width: parent.width
|
||||||
height: outputSliderInner.implicitHeight + Style.spacing.controlGap
|
height: outputSlider.implicitHeight + Style.spacing.controlGap
|
||||||
hasCursor: root.cursorActive && root.focusSection === "output" && root.selectedIndex === -1
|
hasCursor: root.cursorActive && root.focusSection === "output" && root.selectedIndex === -1
|
||||||
onHasCursorChanged: if (hasCursor) root.ensureCursorVisible(outputSliderRow)
|
onHasCursorChanged: if (hasCursor) root.ensureCursorVisible(outputSliderRow)
|
||||||
foreground: root.bar.foreground
|
foreground: root.bar.foreground
|
||||||
outline: true
|
outline: true
|
||||||
|
|
||||||
Row {
|
PanelSlider {
|
||||||
id: outputSliderInner
|
id: outputSlider
|
||||||
|
bar: root.bar
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.leftMargin: Style.space(6)
|
anchors.leftMargin: Style.space(6)
|
||||||
anchors.rightMargin: Style.space(6)
|
anchors.rightMargin: Style.space(6)
|
||||||
spacing: Style.space(8)
|
minimum: 0
|
||||||
|
maximum: 1
|
||||||
|
step: 0.05
|
||||||
|
value: root.outputVolume
|
||||||
|
opacity: root.outputMuted ? 0.5 : 1.0
|
||||||
|
enabled: !!root.sink
|
||||||
|
|
||||||
Text {
|
onMoved: function(v) { root.setOutputVolume(v) }
|
||||||
id: outputIconText
|
|
||||||
text: root.outputIcon()
|
|
||||||
color: root.bar.foreground
|
|
||||||
font.family: root.bar.fontFamily
|
|
||||||
font.pixelSize: Style.font.heading
|
|
||||||
width: Style.space(22)
|
|
||||||
horizontalAlignment: Text.AlignHCenter
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
opacity: root.outputMuted ? 0.5 : 1.0
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
anchors.fill: parent
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: root.toggleOutputMute()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
PanelSlider {
|
|
||||||
id: outputSlider
|
|
||||||
bar: root.bar
|
|
||||||
width: parent.width - outputIconText.width - outputPercent.width - Style.space(16)
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
minimum: 0
|
|
||||||
maximum: 1
|
|
||||||
step: 0.05
|
|
||||||
value: root.outputVolume
|
|
||||||
opacity: root.outputMuted ? 0.5 : 1.0
|
|
||||||
enabled: !!root.sink
|
|
||||||
|
|
||||||
onMoved: function(v) { root.setOutputVolume(v) }
|
|
||||||
}
|
|
||||||
|
|
||||||
Text {
|
|
||||||
id: outputPercent
|
|
||||||
text: Math.round((outputSlider.dragging ? outputSlider.liveValue : root.outputVolume) * 100) + "%"
|
|
||||||
color: root.bar.foreground
|
|
||||||
font.family: root.bar.fontFamily
|
|
||||||
font.pixelSize: Style.font.bodySmall
|
|
||||||
width: Style.space(36)
|
|
||||||
horizontalAlignment: Text.AlignRight
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
opacity: root.outputMuted ? 0.5 : 1.0
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
MouseArea {
|
HoverHandler {
|
||||||
anchors.fill: parent
|
onHoveredChanged: if (hovered) {
|
||||||
hoverEnabled: true
|
|
||||||
acceptedButtons: Qt.NoButton
|
|
||||||
propagateComposedEvents: true
|
|
||||||
onContainsMouseChanged: if (containsMouse) {
|
|
||||||
root.cursorActive = true
|
root.cursorActive = true
|
||||||
root.focusSection = "output"
|
root.focusSection = "output"
|
||||||
root.selectedIndex = -1
|
root.selectedIndex = -1
|
||||||
@@ -707,33 +723,24 @@ Panel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PanelSeparator {
|
||||||
|
visible: root.audioSources.length > 0 || !!root.source
|
||||||
|
foreground: root.bar.foreground
|
||||||
|
}
|
||||||
|
|
||||||
// ---- Input ----
|
// ---- Input ----
|
||||||
Column {
|
Column {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
spacing: Style.space(6)
|
spacing: Style.space(6)
|
||||||
visible: root.audioSources.length > 0 || !!root.source
|
visible: root.audioSources.length > 0 || !!root.source
|
||||||
|
|
||||||
Row {
|
Text {
|
||||||
|
text: root.source ? "Input · " + root.nodeLabel(root.source) : "Input"
|
||||||
|
color: Qt.darker(root.bar.foreground, 1.6)
|
||||||
|
font.family: root.bar.fontFamily
|
||||||
|
font.pixelSize: Style.font.bodySmall
|
||||||
|
elide: Text.ElideRight
|
||||||
width: parent.width
|
width: parent.width
|
||||||
spacing: Style.space(8)
|
|
||||||
|
|
||||||
PanelSectionHeader {
|
|
||||||
text: "Input"
|
|
||||||
foreground: root.bar.foreground
|
|
||||||
fontFamily: root.bar.fontFamily
|
|
||||||
fontSize: Style.font.bodySmall
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
Text {
|
|
||||||
text: root.source ? "· " + root.nodeLabel(root.source) : ""
|
|
||||||
color: Qt.darker(root.bar.foreground, 1.8)
|
|
||||||
font.family: root.bar.fontFamily
|
|
||||||
font.pixelSize: Style.font.bodySmall
|
|
||||||
elide: Text.ElideRight
|
|
||||||
width: parent.width - Style.space(56)
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
CursorSurface {
|
CursorSurface {
|
||||||
@@ -825,17 +832,21 @@ Panel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PanelSeparator {
|
||||||
|
visible: root.audioStreams.length > 0
|
||||||
|
foreground: root.bar.foreground
|
||||||
|
}
|
||||||
|
|
||||||
// ---- Per-app streams ----
|
// ---- Per-app streams ----
|
||||||
Column {
|
Column {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
spacing: Style.space(6)
|
spacing: Style.space(10)
|
||||||
visible: root.audioStreams.length > 0
|
visible: root.audioStreams.length > 0
|
||||||
|
|
||||||
PanelSectionHeader {
|
PanelSectionHeader {
|
||||||
text: "Playing"
|
text: "NOW PLAYING"
|
||||||
foreground: root.bar.foreground
|
foreground: root.bar.foreground
|
||||||
fontFamily: root.bar.fontFamily
|
fontFamily: root.bar.fontFamily
|
||||||
fontSize: Style.font.bodySmall
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Repeater {
|
Repeater {
|
||||||
@@ -898,18 +909,9 @@ Panel {
|
|||||||
color: root.bar.foreground
|
color: root.bar.foreground
|
||||||
font.family: root.bar.fontFamily
|
font.family: root.bar.fontFamily
|
||||||
font.pixelSize: Style.font.body
|
font.pixelSize: Style.font.body
|
||||||
|
font.bold: sinkRow.isActive
|
||||||
elide: Text.ElideRight
|
elide: Text.ElideRight
|
||||||
width: parent.width - Style.space(22) - Style.space(14) - Style.space(16)
|
width: parent.width - Style.space(22) - Style.space(8)
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
Text {
|
|
||||||
text: sinkRow.isActive ? "" : ""
|
|
||||||
color: root.bar.foreground
|
|
||||||
font.family: root.bar.fontFamily
|
|
||||||
font.pixelSize: Style.font.subtitle
|
|
||||||
width: Style.space(14)
|
|
||||||
horizontalAlignment: Text.AlignRight
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -966,18 +968,9 @@ Panel {
|
|||||||
color: root.bar.foreground
|
color: root.bar.foreground
|
||||||
font.family: root.bar.fontFamily
|
font.family: root.bar.fontFamily
|
||||||
font.pixelSize: Style.font.body
|
font.pixelSize: Style.font.body
|
||||||
|
font.bold: sourceRow.isActive
|
||||||
elide: Text.ElideRight
|
elide: Text.ElideRight
|
||||||
width: parent.width - Style.space(22) - Style.space(14) - Style.space(16)
|
width: parent.width - Style.space(22) - Style.space(8)
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
Text {
|
|
||||||
text: sourceRow.isActive ? "" : ""
|
|
||||||
color: root.bar.foreground
|
|
||||||
font.family: root.bar.fontFamily
|
|
||||||
font.pixelSize: Style.font.subtitle
|
|
||||||
width: Style.space(14)
|
|
||||||
horizontalAlignment: Text.AlignRight
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user