improvements
This commit is contained in:
@@ -0,0 +1,72 @@
|
||||
import QtQuick
|
||||
import "root:/config"
|
||||
import "root:/components"
|
||||
import "root:/services"
|
||||
|
||||
// Secondary input band with an explicit mute action.
|
||||
Item {
|
||||
id: root
|
||||
|
||||
implicitHeight: 68
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
anchors.right: muteButton.left
|
||||
anchors.rightMargin: Theme.padM
|
||||
anchors.verticalCenter: muteButton.verticalCenter
|
||||
spacing: Theme.padS
|
||||
|
||||
Icon {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: Audio.micIcon
|
||||
color: Audio.micMuted ? Theme.accent : Theme.text
|
||||
font.pixelSize: Theme.fsLarge
|
||||
}
|
||||
|
||||
Column {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: parent.width - Theme.fsLarge - Theme.padS
|
||||
spacing: 2
|
||||
|
||||
Text {
|
||||
width: parent.width
|
||||
text: Audio.sourceName
|
||||
elide: Text.ElideRight
|
||||
color: Theme.text
|
||||
font.family: Theme.fontDisplay
|
||||
font.pixelSize: Theme.fsSmall
|
||||
font.weight: Font.DemiBold
|
||||
font.italic: true
|
||||
renderType: Text.NativeRendering
|
||||
}
|
||||
|
||||
Text {
|
||||
text: Audio.micMuted ? "MUTED" : Audio.micPercent + "%"
|
||||
color: Audio.micMuted ? Theme.accent : Theme.subtext
|
||||
font.family: Theme.fontMono
|
||||
font.pixelSize: Theme.fsMicro
|
||||
font.weight: Font.Bold
|
||||
renderType: Text.NativeRendering
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
P5Button {
|
||||
id: muteButton
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
text: Audio.micMuted ? "Unmute input" : "Mute input"
|
||||
icon: Audio.micIcon
|
||||
onClicked: Audio.toggleMicMute()
|
||||
}
|
||||
|
||||
P5Slider {
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
value: Audio.micVolume
|
||||
accent: Theme.primary
|
||||
enabled: !Audio.micMuted
|
||||
onMoved: v => Audio.setMicVolume(v)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
import QtQuick
|
||||
import "root:/config"
|
||||
import "root:/components"
|
||||
import "root:/services"
|
||||
|
||||
// Dominant current-output readout for AudioPopout.
|
||||
Item {
|
||||
implicitHeight: 112
|
||||
|
||||
Text {
|
||||
id: outputPercent
|
||||
anchors.left: parent.left
|
||||
anchors.top: parent.top
|
||||
text: Audio.volumePercent + "%"
|
||||
color: Audio.muted ? Theme.muted : Theme.text
|
||||
font.family: Theme.fontDisplay
|
||||
font.pixelSize: Theme.fsHuge
|
||||
font.weight: Theme.weightDisplay
|
||||
font.italic: true
|
||||
renderType: Text.NativeRendering
|
||||
}
|
||||
|
||||
Column {
|
||||
anchors.left: outputPercent.right
|
||||
anchors.leftMargin: Theme.padL
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: outputPercent.verticalCenter
|
||||
spacing: 3
|
||||
|
||||
Text {
|
||||
text: "CURRENT OUTPUT"
|
||||
color: Theme.accent
|
||||
font.family: Theme.fontMono
|
||||
font.pixelSize: Theme.fsMicro
|
||||
font.weight: Font.Bold
|
||||
font.letterSpacing: 2
|
||||
renderType: Text.NativeRendering
|
||||
}
|
||||
|
||||
Text {
|
||||
width: parent.width
|
||||
text: Audio.sinkName
|
||||
elide: Text.ElideRight
|
||||
color: Theme.subtext
|
||||
font.family: Theme.fontDisplay
|
||||
font.pixelSize: Theme.fsSmall
|
||||
font.weight: Font.DemiBold
|
||||
font.italic: true
|
||||
renderType: Text.NativeRendering
|
||||
}
|
||||
|
||||
Text {
|
||||
text: Audio.muted ? "MUTED" : "LIVE"
|
||||
color: Audio.muted ? Theme.accent : Theme.text
|
||||
font.family: Theme.fontMono
|
||||
font.pixelSize: Theme.fsMicro
|
||||
font.weight: Font.Bold
|
||||
renderType: Text.NativeRendering
|
||||
}
|
||||
}
|
||||
|
||||
P5Slider {
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
value: Audio.volume
|
||||
accent: Theme.accent
|
||||
enabled: !Audio.muted
|
||||
onMoved: v => Audio.setVolume(v)
|
||||
}
|
||||
}
|
||||
@@ -5,12 +5,15 @@ import "root:/config"
|
||||
import "root:/components"
|
||||
import "root:/services"
|
||||
|
||||
// Output and input levels, the sink picker, and a per-app mixer.
|
||||
// Audio is the expressive vertical slice for rail popouts: output owns the
|
||||
// composition, input is a clear secondary band, and application streams recede
|
||||
// into a compact mixer below both device controls.
|
||||
Popout {
|
||||
id: root
|
||||
|
||||
contentWidth: 360
|
||||
contentWidth: 400
|
||||
contentHeight: surface.implicitContentHeight + Theme.padM * 2
|
||||
initialFocusItem: outputMuteButton
|
||||
|
||||
PopoutSurface {
|
||||
id: surface
|
||||
@@ -39,29 +42,38 @@ Popout {
|
||||
spacing: Theme.padM
|
||||
|
||||
// ------------------------------------------------------- output
|
||||
LevelRow {
|
||||
AudioOutputHero {
|
||||
width: parent.width
|
||||
icon: Audio.icon
|
||||
label: "Output"
|
||||
readout: Audio.muted ? "MUTED" : Audio.volumePercent + "%"
|
||||
value: Audio.volume
|
||||
muted: Audio.muted
|
||||
accent: Theme.primary
|
||||
onMoved: v => Audio.setVolume(v)
|
||||
onToggled: Audio.toggleMute()
|
||||
}
|
||||
|
||||
Column {
|
||||
width: parent.width
|
||||
spacing: Theme.padS
|
||||
|
||||
P5Button {
|
||||
id: outputMuteButton
|
||||
width: parent.width
|
||||
text: Audio.muted ? "Unmute output" : "Mute output"
|
||||
icon: Audio.icon
|
||||
onClicked: Audio.toggleMute()
|
||||
}
|
||||
|
||||
P5Button {
|
||||
width: parent.width
|
||||
text: "Open audio mixer"
|
||||
icon: ""
|
||||
onClicked: Actions.openAudioSettings()
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------- input
|
||||
LevelRow {
|
||||
SectionRule {
|
||||
width: parent.width
|
||||
text: "Input"
|
||||
}
|
||||
|
||||
AudioInputBand {
|
||||
width: parent.width
|
||||
icon: Audio.micIcon
|
||||
label: "Input"
|
||||
readout: Audio.micMuted ? "MUTED" : Audio.micPercent + "%"
|
||||
value: Audio.micVolume
|
||||
muted: Audio.micMuted
|
||||
accent: Theme.blue
|
||||
onMoved: v => Audio.setMicVolume(v)
|
||||
onToggled: Audio.toggleMicMute()
|
||||
}
|
||||
|
||||
SectionRule {
|
||||
@@ -98,19 +110,14 @@ Popout {
|
||||
icon: ""
|
||||
label: Audio.streamName(modelData)
|
||||
readout: modelData.audio ? Math.round(modelData.audio.volume * 100) + "%" : ""
|
||||
value: modelData.audio?.volume ?? 0
|
||||
muted: modelData.audio?.muted ?? false
|
||||
value: modelData.audio ? modelData.audio.volume : 0
|
||||
muted: modelData.audio ? modelData.audio.muted : false
|
||||
accent: Theme.glow
|
||||
onMoved: v => { if (modelData.audio) modelData.audio.volume = v; }
|
||||
onToggled: { if (modelData.audio) modelData.audio.muted = !modelData.audio.muted; }
|
||||
}
|
||||
}
|
||||
|
||||
P5Button {
|
||||
text: "Mixer"
|
||||
icon: ""
|
||||
onClicked: Actions.openAudioSettings()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -178,7 +185,7 @@ Popout {
|
||||
anchors.right: parent.right
|
||||
value: levelRow.value
|
||||
accent: levelRow.accent
|
||||
enabledControl: !levelRow.muted
|
||||
enabled: !levelRow.muted
|
||||
onMoved: v => levelRow.moved(v)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -155,26 +155,26 @@ Popout {
|
||||
|
||||
TransportButton {
|
||||
glyph: ""
|
||||
enabledControl: Media.canGoPrevious
|
||||
enabled: Media.canGoPrevious
|
||||
onClicked: Media.previous()
|
||||
}
|
||||
|
||||
TransportButton {
|
||||
glyph: Media.playing ? "" : ""
|
||||
primary: true
|
||||
enabledControl: Media.hasPlayer
|
||||
enabled: Media.hasPlayer
|
||||
onClicked: Media.playPause()
|
||||
}
|
||||
|
||||
TransportButton {
|
||||
glyph: ""
|
||||
enabledControl: Media.canGoNext
|
||||
enabled: Media.canGoNext
|
||||
onClicked: Media.next()
|
||||
}
|
||||
|
||||
TransportButton {
|
||||
glyph: ""
|
||||
enabledControl: Media.hasPlayer
|
||||
enabled: Media.hasPlayer
|
||||
onClicked: Media.raise()
|
||||
}
|
||||
}
|
||||
@@ -196,7 +196,7 @@ Popout {
|
||||
id: elapsed
|
||||
anchors.left: parent.left
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: Media.timeString(Media.position)
|
||||
text: Media.positionSupported ? Media.timeString(Media.position) : "--:--"
|
||||
color: Theme.subtext
|
||||
font.family: Theme.fontMono
|
||||
font.pixelSize: Theme.fsMicro
|
||||
@@ -207,7 +207,7 @@ Popout {
|
||||
id: total
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: Media.timeString(Media.length)
|
||||
text: Media.lengthSupported ? Media.timeString(Media.length) : "LIVE"
|
||||
color: Theme.subtext
|
||||
font.family: Theme.fontMono
|
||||
font.pixelSize: Theme.fsMicro
|
||||
@@ -222,7 +222,7 @@ Popout {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
value: Media.progress
|
||||
accent: Theme.accent
|
||||
enabledControl: Media.canSeek
|
||||
enabled: Media.canSeek
|
||||
onMoved: v => Media.seekTo(v)
|
||||
}
|
||||
}
|
||||
@@ -238,7 +238,7 @@ Popout {
|
||||
Repeater {
|
||||
model: Media.players
|
||||
|
||||
Item {
|
||||
FocusScope {
|
||||
id: chip
|
||||
required property var modelData
|
||||
|
||||
@@ -246,21 +246,44 @@ Popout {
|
||||
|
||||
implicitWidth: chipLabel.implicitWidth + Theme.padM
|
||||
implicitHeight: 22
|
||||
focusPolicy: chip.enabled ? Qt.TabFocus : Qt.NoFocus
|
||||
|
||||
function activate(): void {
|
||||
if (chip.enabled)
|
||||
Media.select(chip.modelData);
|
||||
}
|
||||
|
||||
function activateFromKey(event): void {
|
||||
if (!event.isAutoRepeat)
|
||||
chip.activate();
|
||||
}
|
||||
|
||||
Skew {
|
||||
anchors.fill: parent
|
||||
color: chip.current
|
||||
? Theme.alpha(Theme.primary, 0.22)
|
||||
: (chipHover.hovered ? Theme.alpha(Theme.primary, 0.1) : "transparent")
|
||||
borderWidth: 1
|
||||
borderColor: chip.current ? Theme.primary : Theme.alpha(Theme.outline, 0.7)
|
||||
color: !chip.enabled
|
||||
? Theme.treatmentDisabledFill
|
||||
: (chip.activeFocus
|
||||
? Theme.treatmentFocusFill
|
||||
: (chip.current
|
||||
? Theme.alpha(Theme.primary, 0.22)
|
||||
: (chipHover.hovered ? Theme.alpha(Theme.primary, 0.1) : "transparent")))
|
||||
borderWidth: chip.activeFocus ? Theme.stroke : 1
|
||||
borderColor: !chip.enabled
|
||||
? Theme.treatmentDisabledMark
|
||||
: (chip.activeFocus
|
||||
? Theme.treatmentFocusMark
|
||||
: (chip.current ? Theme.primary : Theme.alpha(Theme.outline, 0.7)))
|
||||
}
|
||||
|
||||
Text {
|
||||
id: chipLabel
|
||||
anchors.centerIn: parent
|
||||
text: chip.modelData.identity
|
||||
color: chip.current ? Theme.text : Theme.muted
|
||||
color: !chip.enabled
|
||||
? Theme.treatmentDisabledText
|
||||
: (chip.activeFocus
|
||||
? Theme.treatmentFocusText
|
||||
: (chip.current ? Theme.text : Theme.muted))
|
||||
font.family: Theme.fontMono
|
||||
font.pixelSize: Theme.fsMicro
|
||||
renderType: Text.NativeRendering
|
||||
@@ -268,39 +291,57 @@ Popout {
|
||||
|
||||
HoverHandler {
|
||||
id: chipHover
|
||||
enabled: chip.enabled
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
}
|
||||
|
||||
TapHandler {
|
||||
onTapped: Media.select(chip.modelData)
|
||||
enabled: chip.enabled
|
||||
onTapped: chip.activate()
|
||||
}
|
||||
|
||||
Keys.onReturnPressed: event => chip.activateFromKey(event)
|
||||
Keys.onEnterPressed: event => chip.activateFromKey(event)
|
||||
Keys.onSpacePressed: event => chip.activateFromKey(event)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
component TransportButton: Item {
|
||||
component TransportButton: FocusScope {
|
||||
id: tb
|
||||
|
||||
property string glyph: ""
|
||||
property bool primary: false
|
||||
property bool enabledControl: true
|
||||
|
||||
signal clicked()
|
||||
|
||||
implicitWidth: primary ? 38 : 30
|
||||
implicitHeight: primary ? 38 : 30
|
||||
focusPolicy: tb.enabled ? Qt.TabFocus : Qt.NoFocus
|
||||
|
||||
function activate(): void {
|
||||
if (tb.enabled)
|
||||
tb.clicked();
|
||||
}
|
||||
|
||||
function activateFromKey(event): void {
|
||||
if (!event.isAutoRepeat)
|
||||
tb.activate();
|
||||
}
|
||||
|
||||
Skew {
|
||||
anchors.fill: parent
|
||||
color: {
|
||||
if (!tb.enabledControl) return "transparent";
|
||||
if (!tb.enabled) return "transparent";
|
||||
if (tb.activeFocus) return Theme.treatmentFocusFill;
|
||||
if (tbHover.hovered) return tb.primary ? Theme.accent : Theme.alpha(Theme.primary, 0.22);
|
||||
return tb.primary ? Theme.alpha(Theme.primary, 0.18) : "transparent";
|
||||
}
|
||||
borderWidth: tb.primary ? 2 : 1
|
||||
borderColor: tb.enabledControl
|
||||
? (tbHover.hovered ? Theme.text : Theme.alpha(Theme.primary, 0.6))
|
||||
borderWidth: tb.activeFocus ? Theme.stroke * 2 : (tb.primary ? 2 : 1)
|
||||
borderColor: tb.enabled
|
||||
? (tb.activeFocus
|
||||
? Theme.treatmentFocusMark
|
||||
: (tbHover.hovered ? Theme.text : Theme.alpha(Theme.primary, 0.6)))
|
||||
: Theme.alpha(Theme.outline, 0.4)
|
||||
|
||||
Behavior on color {
|
||||
@@ -313,26 +354,31 @@ Popout {
|
||||
text: tb.glyph
|
||||
font.pixelSize: tb.primary ? Theme.fsTitle : Theme.fsLarge
|
||||
color: {
|
||||
if (!tb.enabledControl) return Theme.alpha(Theme.muted, 0.5);
|
||||
if (!tb.enabled) return Theme.alpha(Theme.muted, 0.5);
|
||||
if (tb.activeFocus) return Theme.treatmentFocusText;
|
||||
if (tbHover.hovered && tb.primary) return Theme.text;
|
||||
return Theme.primary;
|
||||
}
|
||||
}
|
||||
|
||||
scale: tbHover.hovered && tb.enabledControl ? 1.12 : 1.0
|
||||
scale: tbHover.hovered && tb.enabled ? 1.12 : 1.0
|
||||
Behavior on scale {
|
||||
NumberAnimation { duration: Theme.durBase; easing.type: Easing.OutBack }
|
||||
}
|
||||
|
||||
HoverHandler {
|
||||
id: tbHover
|
||||
enabled: tb.enabledControl
|
||||
enabled: tb.enabled
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
}
|
||||
|
||||
TapHandler {
|
||||
enabled: tb.enabledControl
|
||||
onTapped: tb.clicked()
|
||||
enabled: tb.enabled
|
||||
onTapped: tb.activate()
|
||||
}
|
||||
|
||||
Keys.onReturnPressed: event => tb.activateFromKey(event)
|
||||
Keys.onEnterPressed: event => tb.activateFromKey(event)
|
||||
Keys.onSpacePressed: event => tb.activateFromKey(event)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,12 +6,13 @@ import "root:/components"
|
||||
// own, so P5Panel's default content property stays usable by callers.
|
||||
P5Panel {
|
||||
property color tone: Theme.primary
|
||||
property bool textured: false
|
||||
|
||||
fill: Theme.mantle
|
||||
fillOpacity: 0.97
|
||||
border: Theme.alpha(tone, 0.75)
|
||||
borderWidth: Theme.stroke
|
||||
halftone: true
|
||||
halftone: textured
|
||||
halftoneColor: tone
|
||||
halftoneOpacity: 0.06
|
||||
sheen: true
|
||||
|
||||
@@ -74,6 +74,16 @@ PopupWindow {
|
||||
close();
|
||||
}
|
||||
|
||||
function dismissImmediately() {
|
||||
openAnim.stop();
|
||||
closeAnim.stop();
|
||||
shown = false;
|
||||
holder.opacity = 0;
|
||||
holder.scale = 0.94;
|
||||
holder.x = root.bleed;
|
||||
PopupCoordinator.release(root);
|
||||
}
|
||||
|
||||
// The grab can only take hold once the popup is actually mapped. Asked for
|
||||
// in the same tick as `shown`, Hyprland has no surface to hand it and
|
||||
// clears it immediately — which closed the menu the instant it opened.
|
||||
|
||||
Reference in New Issue
Block a user