From 0bfaf3c9dbca5027b6d4c9f1e64ad40c1737d2d0 Mon Sep 17 00:00:00 2001 From: Tri Vo Khac Date: Sat, 7 Nov 2026 18:48:04 +0700 Subject: [PATCH] fix(ui): address main window KDE HIG findings --- src/qml/Footer.qml | 55 ++++++++++---- src/qml/Header.qml | 45 ++++++++++-- src/qml/InputPopup.qml | 69 +++++++++++++----- src/qml/Main.qml | 17 +++-- src/qml/MpvVideo.qml | 6 +- src/qml/SeekBar.qml | 148 ++++++++++++++++++-------------------- src/qml/ToolTipButton.qml | 23 ++++-- src/qml/VolumeSlider.qml | 42 +++++------ 8 files changed, 248 insertions(+), 157 deletions(-) diff --git a/src/qml/Footer.qml b/src/qml/Footer.qml index c062bb1..a5d2aa2 100644 --- a/src/qml/Footer.qml +++ b/src/qml/Footer.qml @@ -13,6 +13,13 @@ import org.kde.kirigami as Kirigami Item { id: root + // Space activates the focused control before the window's playback action. + Keys.onShortcutOverride: event => { + if (event.key === Qt.Key_Space && event.modifiers === Qt.NoModifier) { + event.accepted = true + } + } + required property Actions actions required property MpvVideo mpv property bool fullscreen: false @@ -22,9 +29,17 @@ Item { readonly property bool isFloating: QtPotatoSettings.footerStyle === "floating" // Takes space below the video (instead of covering it). readonly property bool isDocked: !isFloating && !fullscreen - readonly property bool isSmallSize: width < 600 + readonly property bool isSmallSize: width < Kirigami.Units.gridUnit * 30 readonly property int buttonSize: isSmallSize ? Kirigami.Units.iconSizes.small : Kirigami.Units.iconSizes.smallMedium - readonly property bool busy: footer.hovered || hamburgerMenu.isOpen || seekBar.chaptersPopupOpen || seekBar.pressed + readonly property bool busy: footer.hovered || hamburgerMenu.isOpen || seekBar.chaptersPopupOpen || seekBar.pressed || controlHasFocus + readonly property bool controlHasFocus: { + let item = root.Window.window?.activeFocusItem + while (item) { + if (item === footer) return true + item = item.parent + } + return false + } property bool shown: true implicitHeight: isFloating @@ -92,7 +107,7 @@ Item { x: (root.width - width) / 2 width: root.isFloating - ? Math.min(root.width - Kirigami.Units.largeSpacing * (root.isSmallSize ? 2 : 4), 900) + ? Math.min(root.width - Kirigami.Units.largeSpacing * (root.isSmallSize ? 2 : 4), Kirigami.Units.gridUnit * 50) : root.width state: root.shown ? "visible" : "hidden" padding: Kirigami.Units.smallSpacing @@ -112,16 +127,17 @@ Item { ColumnLayout { anchors.fill: parent - spacing: 0 + spacing: Kirigami.Units.smallSpacing RowLayout { - spacing: root.isSmallSize ? 1 : Kirigami.Units.smallSpacing + spacing: Kirigami.Units.mediumSpacing HamburgerMenu { id: hamburgerMenu actions: root.actions mpv: root.mpv inFooter: true + focusPolicy: Qt.StrongFocus visible: root.showMenuButton icon.width: root.buttonSize icon.height: root.buttonSize @@ -133,7 +149,7 @@ Item { display: AbstractButton.IconOnly icon.width: root.buttonSize icon.height: root.buttonSize - focusPolicy: Qt.NoFocus + focusPolicy: Qt.StrongFocus ToolTip.text: root.mpv.paused ? KI18n.i18nc("@info:tooltip", "Start playback") : KI18n.i18nc("@info:tooltip", "Pause playback") ToolTip.visible: hovered @@ -146,7 +162,7 @@ Item { icon.width: root.buttonSize icon.height: root.buttonSize display: AbstractButton.IconOnly - focusPolicy: Qt.NoFocus + focusPolicy: Qt.StrongFocus ToolTip.text: KI18n.i18nc("@info:tooltip", "Play previous file") ToolTip.visible: hovered @@ -159,7 +175,7 @@ Item { icon.width: root.buttonSize icon.height: root.buttonSize display: AbstractButton.IconOnly - focusPolicy: Qt.NoFocus + focusPolicy: Qt.StrongFocus ToolTip.text: KI18n.i18nc("@info:tooltip", "Play next file") ToolTip.visible: hovered @@ -171,7 +187,6 @@ Item { mpv: root.mpv actions: root.actions Layout.fillWidth: true - Layout.preferredHeight: 20 } ToolButton { @@ -180,7 +195,7 @@ Item { icon.width: root.buttonSize icon.height: root.buttonSize display: AbstractButton.IconOnly - focusPolicy: Qt.NoFocus + focusPolicy: Qt.StrongFocus ToolTip.text: root.actions.mute.text ToolTip.visible: hovered @@ -188,9 +203,21 @@ Item { } VolumeSlider { + id: volumeSlider mpv: root.mpv - Layout.preferredWidth: root.isSmallSize ? 60 : 100 - Layout.preferredHeight: 20 + Layout.preferredWidth: Kirigami.Units.gridUnit * (root.isSmallSize ? 3 : 5) + } + + Label { + text: root.mpv.mute ? KI18n.i18nc("@info volume muted", "Muted") : KI18n.i18nc("@info volume percentage", "%1%", Math.round(volumeSlider.value)) + font: Kirigami.Theme.smallFont + Layout.minimumWidth: volumeTextMetrics.width + + TextMetrics { + id: volumeTextMetrics + font: Kirigami.Theme.smallFont + text: KI18n.i18nc("@info volume percentage", "%1%", root.mpv.volumeMax) + } } ToolButton { @@ -198,7 +225,7 @@ Item { icon.width: root.buttonSize icon.height: root.buttonSize display: AbstractButton.IconOnly - focusPolicy: Qt.NoFocus + focusPolicy: Qt.StrongFocus visible: !QtPotatoSettings.playlistToggleWithMouse ToolTip.text: text @@ -208,6 +235,8 @@ Item { } RowLayout { + spacing: Kirigami.Units.largeSpacing + LabelWithTooltip { text: root.mpv.mediaTitle font.pointSize: Kirigami.Theme.smallFont.pointSize diff --git a/src/qml/Header.qml b/src/qml/Header.qml index 38ec731..9e32d70 100644 --- a/src/qml/Header.qml +++ b/src/qml/Header.qml @@ -12,6 +12,13 @@ import org.kde.kirigami as Kirigami ToolBar { id: root + // Space activates the focused control before the window's playback action. + Keys.onShortcutOverride: event => { + if (event.key === Qt.Key_Space && event.modifiers === Qt.NoModifier) { + event.accepted = true + } + } + required property Actions actions required property MpvItem mpv required property TranslationService translator @@ -19,7 +26,7 @@ ToolBar { property bool shown: true property bool menuBarShown: true - readonly property bool isSmallSize: width < 600 + readonly property bool isSmallSize: width < Kirigami.Units.gridUnit * 34 readonly property int buttonSize: isSmallSize ? Kirigami.Units.iconSizes.small : Kirigami.Units.iconSizes.smallMedium position: ToolBar.Header @@ -27,29 +34,39 @@ ToolBar { RowLayout { width: parent.width + spacing: Kirigami.Units.mediumSpacing HamburgerMenu { actions: root.actions mpv: root.mpv visible: !root.menuBarShown + focusPolicy: Qt.StrongFocus icon.width: root.buttonSize icon.height: root.buttonSize } ToolButton { action: root.actions.openFile - focusPolicy: Qt.NoFocus + focusPolicy: Qt.StrongFocus icon.width: root.buttonSize icon.height: root.buttonSize display: root.isSmallSize ? AbstractButton.IconOnly : AbstractButton.TextBesideIcon + + ToolTip.text: text + ToolTip.visible: hovered + ToolTip.delay: Kirigami.Units.toolTipDelay } ToolButton { action: root.actions.openUrl - focusPolicy: Qt.NoFocus + focusPolicy: Qt.StrongFocus icon.width: root.buttonSize icon.height: root.buttonSize display: root.isSmallSize ? AbstractButton.IconOnly : AbstractButton.TextBesideIcon + + ToolTip.text: text + ToolTip.visible: hovered + ToolTip.delay: Kirigami.Units.toolTipDelay } ToolSeparator {} @@ -60,10 +77,15 @@ ToolBar { icon.name: "add-subtitle" icon.width: root.buttonSize icon.height: root.buttonSize - focusPolicy: Qt.NoFocus + focusPolicy: Qt.StrongFocus enabled: root.mpv.fileLoaded opacity: enabled ? 1.0 : 0.6 display: root.isSmallSize ? AbstractButton.IconOnly : AbstractButton.TextBesideIcon + + ToolTip.text: text + ToolTip.visible: hovered + ToolTip.delay: Kirigami.Units.toolTipDelay + Accessible.role: Accessible.ButtonMenu down: subtitleMenu.visible onPressed: subtitleMenu.open() @@ -89,10 +111,15 @@ ToolBar { icon.name: "audio-volume-high" icon.width: root.buttonSize icon.height: root.buttonSize - focusPolicy: Qt.NoFocus + focusPolicy: Qt.StrongFocus enabled: root.mpv.audioTracks.length > 0 opacity: enabled ? 1.0 : 0.6 display: root.isSmallSize ? AbstractButton.IconOnly : AbstractButton.TextBesideIcon + + ToolTip.text: text + ToolTip.visible: hovered + ToolTip.delay: Kirigami.Units.toolTipDelay + Accessible.role: Accessible.ButtonMenu down: audioMenu.visible onPressed: audioMenu.open() @@ -135,7 +162,7 @@ ToolBar { ToolButton { action: root.actions.cancelTranslation display: AbstractButton.IconOnly - focusPolicy: Qt.NoFocus + focusPolicy: Qt.StrongFocus ToolTip.text: text ToolTip.visible: hovered @@ -149,10 +176,14 @@ ToolBar { ToolButton { action: root.actions.configure - focusPolicy: Qt.NoFocus + focusPolicy: Qt.StrongFocus icon.width: root.buttonSize icon.height: root.buttonSize display: root.isSmallSize ? AbstractButton.IconOnly : AbstractButton.TextBesideIcon + + ToolTip.text: text + ToolTip.visible: hovered + ToolTip.delay: Kirigami.Units.toolTipDelay } } diff --git a/src/qml/InputPopup.qml b/src/qml/InputPopup.qml index abc136e..5b8afcb 100644 --- a/src/qml/InputPopup.qml +++ b/src/qml/InputPopup.qml @@ -5,46 +5,83 @@ import QtQuick import QtQuick.Controls import QtQuick.Layouts +import org.kde.ki18n import org.kde.kirigami as Kirigami -// Single-line text popup, used for Open URL. -Popup { +Kirigami.PromptDialog { id: root property string lastText: "" property string placeholderText: "" property string buttonText: "" + property string errorText: "" + readonly property bool validUrl: isValidUrl(textField.text.trim()) signal submitted(text: string) + function isValidUrl(text: string): bool { + // Network URLs need both a scheme and a host. Reject whitespace, missing + // authorities, empty labels, and ports outside the TCP/UDP range. + const match = /^([a-z][a-z0-9+.-]*):\/\/([^/?#\s]+)(?:[/?#][^\s]*)?$/i.exec(text) + if (!match) return false + const authority = match[2].substring(match[2].lastIndexOf("@") + 1) + const hostPort = /^(\[[0-9a-f:.]+\]|[^:@\[\]]+)(?::([0-9]+))?$/i.exec(authority) + if (!hostPort || (hostPort[2] !== undefined && (+hostPort[2] < 1 || +hostPort[2] > 65535))) return false + const host = hostPort[1] + return host.startsWith("[") || host.split(".").every(label => /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/i.test(label)) + } + + function submit() { + if (!validUrl) { + errorText = KI18n.i18nc("@info:usagetip", "Enter a URL with a scheme and a host, such as https://example.org/video.mkv.") + textField.forceActiveFocus() + return + } + submitted(textField.text.trim()) + close() + } + + title: KI18n.i18nc("@title:window", "Open URL") modal: true + standardButtons: Kirigami.Dialog.Cancel + customFooterActions: Kirigami.Action { + text: root.buttonText + icon.name: "document-open" + enabled: root.validUrl + onTriggered: root.submit() + } onOpened: { + errorText = "" textField.text = root.lastText - textField.forceActiveFocus(Qt.MouseFocusReason) + textField.forceActiveFocus(Qt.OtherFocusReason) textField.selectAll() } - contentItem: RowLayout { + ColumnLayout { + spacing: Kirigami.Units.smallSpacing + Layout.fillWidth: true + + Label { + text: KI18n.i18nc("@label:textbox", "URL:") + Accessible.ignored: true + } + TextField { id: textField placeholderText: root.placeholderText + Accessible.name: KI18n.i18nc("@label:textbox", "URL") inputMethodHints: Qt.ImhUrlCharactersOnly | Qt.ImhNoAutoUppercase Layout.fillWidth: true - Layout.leftMargin: Kirigami.Units.largeSpacing - onAccepted: submitButton.clicked() - Keys.onEscapePressed: root.close() + onAccepted: root.submit() + onTextEdited: root.errorText = "" } - Button { - id: submitButton - text: root.buttonText - enabled: textField.text.trim().length > 0 - Layout.rightMargin: Kirigami.Units.largeSpacing - onClicked: { - root.submitted(textField.text.trim()) - root.close() - } + Kirigami.InlineMessage { + Layout.fillWidth: true + type: Kirigami.MessageType.Error + text: root.errorText + visible: text.length > 0 } } } diff --git a/src/qml/Main.qml b/src/qml/Main.qml index 4f0fb77..4883bfa 100644 --- a/src/qml/Main.qml +++ b/src/qml/Main.qml @@ -50,10 +50,10 @@ ApplicationWindow { visible: true title: video.mediaTitle || KI18n.i18nc("@title:window", "QtPotato") - width: 1000 - height: 600 - minimumWidth: 400 - minimumHeight: 200 + width: Kirigami.Units.gridUnit * 55 + height: Kirigami.Units.gridUnit * 33 + minimumWidth: Kirigami.Units.gridUnit * 23 + minimumHeight: Kirigami.Units.gridUnit * 11 color: Kirigami.Theme.backgroundColor onVisibilityChanged: visibility => { @@ -180,7 +180,7 @@ ApplicationWindow { if (!QtPotatoSettings.playlistToggleWithMouse) { return } - const atEdge = playlistPanel.edge === Qt.RightEdge ? x > width - 50 : x < 50 + const atEdge = playlistPanel.edge === Qt.RightEdge ? x > width - Kirigami.Units.gridUnit * 3 : x < Kirigami.Units.gridUnit * 3 playlistPanel.state = atEdge ? "visible" : "hidden" } onPointerLeft: footerItem.pointerLeft() @@ -266,11 +266,10 @@ ApplicationWindow { InputPopup { id: openUrlPopup - x: Kirigami.Units.largeSpacing - y: Kirigami.Units.largeSpacing - width: Math.min(appWindow.width * 0.9, 600) + title: KI18n.i18nc("@title:window", "Open URL") + preferredWidth: Kirigami.Units.gridUnit * 30 lastText: QtPotatoSettings.lastUrl - placeholderText: "https://" + placeholderText: "https://example.org/video.mkv" buttonText: KI18n.i18nc("@action:button", "Open") onSubmitted: text => { QtPotatoSettings.lastUrl = text diff --git a/src/qml/MpvVideo.qml b/src/qml/MpvVideo.qml index d7992e8..e11ed8b 100644 --- a/src/qml/MpvVideo.qml +++ b/src/qml/MpvVideo.qml @@ -30,6 +30,9 @@ MpvItem { setPropertyAsync("sub-outline-size", QtPotatoSettings.subtitleOutlineSize) } + Kirigami.Theme.colorSet: Kirigami.Theme.Complementary + Kirigami.Theme.inherit: false + loadUserConfig: QtPotatoSettings.loadMpvConfig // Set once the saved volume is applied; mpv reports its default volume before that. @@ -55,7 +58,7 @@ MpvItem { Rectangle { anchors.fill: parent - color: "black" + color: Kirigami.Theme.backgroundColor visible: !root.fileLoaded z: -1 } @@ -95,6 +98,7 @@ MpvItem { onExited: root.pointerLeft() onPressed: mouse => { + root.forceActiveFocus(Qt.MouseFocusReason) switch (mouse.button) { case Qt.LeftButton: root.actions.playPause.trigger() diff --git a/src/qml/SeekBar.qml b/src/qml/SeekBar.qml index 559eed5..ed6fd13 100644 --- a/src/qml/SeekBar.qml +++ b/src/qml/SeekBar.qml @@ -10,7 +10,7 @@ import QtQuick.Layouts import org.kde.kirigami as Kirigami import org.kde.ki18n -// Flat progress bar of Haruna's footer: click or drag to seek, hover for the time, +// Playback position: click or drag to seek, hover for the time, // chapter marks, right click for the chapter list, wheel to seek. Slider { id: root @@ -19,7 +19,7 @@ Slider { required property Actions actions readonly property alias chaptersPopupOpen: chaptersPopup.visible - readonly property bool showChapterMarkers: root.mpv.chapters.length * 20 < width + readonly property bool showChapterMarkers: root.mpv.chapters.length * Kirigami.Units.gridUnit < width // Playing state before a drag started; playback pauses while dragging. property bool pausedBeforeDrag: false @@ -47,91 +47,81 @@ Slider { return chapter ? `${formatTime(time)} - ${chapter}` : formatTime(time) } + Accessible.name: KI18n.i18nc("@label:slider", "Playback position") + Accessible.description: timeLabel(value) + stepSize: 1 + from: 0 to: Math.max(root.mpv.duration, 0) enabled: root.mpv.fileLoaded && root.mpv.duration > 0 - focusPolicy: Qt.NoFocus - leftPadding: 0 - rightPadding: 0 + focusPolicy: Qt.StrongFocus - handle: Item { visible: false } - - background: Rectangle { - id: progressBarBackground - - color: Kirigami.Theme.alternateBackgroundColor - scale: root.mirrored ? -1 : 1 - - Rectangle { - width: root.visualPosition * parent.width - height: parent.height - color: Kirigami.Theme.highlightColor + // Preserve the Slider's own arrow-key behavior when playback shortcuts exist. + Keys.onShortcutOverride: event => { + if (event.modifiers === Qt.NoModifier && [Qt.Key_Left, Qt.Key_Right, Qt.Key_Up, Qt.Key_Down, Qt.Key_Home, Qt.Key_End].includes(event.key)) { + event.accepted = true } + } + Keys.onPressed: event => { + if (event.modifiers === Qt.NoModifier && (event.key === Qt.Key_Home || event.key === Qt.Key_End)) { + value = event.key === Qt.Key_Home ? from : to + moved() + event.accepted = true + } + } - Repeater { - model: root.showChapterMarkers ? root.mpv.chapters : [] - delegate: Rectangle { - required property var modelData - x: modelData.time / root.mpv.duration * progressBarBackground.width - width: 2 - height: progressBarBackground.height - color: Kirigami.Theme.textColor + Repeater { + model: root.showChapterMarkers ? root.mpv.chapters : [] + delegate: Rectangle { + required property var modelData + x: root.leftPadding + (root.mirrored ? 1 - modelData.time / root.to : modelData.time / root.to) * root.availableWidth + anchors.verticalCenter: parent.verticalCenter + width: Kirigami.Units.smallSpacing / 2 + height: Kirigami.Units.smallSpacing + color: Kirigami.Theme.textColor + } + } + + ToolTip { + id: timeToolTip + parent: root + property real pointerX: 0 + + x: { + const windowX = root.mapToItem(root.Window.window.contentItem, 0, 0).x + return Math.max(Kirigami.Units.largeSpacing - windowX, + Math.min(root.Window.window.width - width - Kirigami.Units.largeSpacing - windowX, pointerX - width / 2)) + } + y: -height - Kirigami.Units.largeSpacing + width: Math.min(implicitWidth, root.Window.window.width - Kirigami.Units.largeSpacing * 2) + visible: (mouseArea.containsMouse || root.pressed) && root.mpv.duration > 0 + text: root.timeLabel(root.pressed ? root.value : pointerX / Math.max(1, root.width) * root.to) + } + + MouseArea { + id: mouseArea + anchors.fill: parent + hoverEnabled: true + // Left button is the slider's own. + acceptedButtons: Qt.MiddleButton | Qt.RightButton + onPositionChanged: timeToolTip.pointerX = mouseX + onClicked: mouse => { + if (mouse.button === Qt.MiddleButton) { + const time = mouseX / width * root.to + const next = root.mpv.chapters.findIndex(chapter => chapter.time > time) + if (next >= 0) { + root.mpv.chapter = next + } + } else if (mouse.button === Qt.RightButton && root.mpv.chapters.length > 0) { + chaptersPopup.x = Math.max(0, Math.min(root.width - chaptersPopup.width, mouseX - chaptersPopup.width / 2)) + chaptersPopup.open() } } - - Rectangle { - id: timeToolTip - - property real pointerX: 0 - - x: Math.max(0, Math.min(parent.width - width, pointerX - width / 2)) - y: -height - Kirigami.Units.largeSpacing - z: 10 - width: timeLabel.implicitWidth + Kirigami.Units.smallSpacing * 2 - height: timeLabel.implicitHeight + Kirigami.Units.smallSpacing * 2 - visible: (mouseArea.containsMouse || root.pressed) && root.mpv.duration > 0 - color: Kirigami.Theme.backgroundColor - radius: Kirigami.Units.cornerRadius - border.width: 1 - border.color: Qt.darker(Kirigami.Theme.backgroundColor, 1.2) - - Label { - id: timeLabel - anchors.centerIn: parent - text: { - const time = root.pressed - ? root.value - : timeToolTip.pointerX / Math.max(1, progressBarBackground.width) * root.to - return root.timeLabel(time) - } - } - } - - MouseArea { - id: mouseArea - anchors.fill: parent - hoverEnabled: true - // Left button is the slider's own. - acceptedButtons: Qt.MiddleButton | Qt.RightButton - onPositionChanged: timeToolTip.pointerX = mouseX - onClicked: mouse => { - if (mouse.button === Qt.MiddleButton) { - const time = mouseX / width * root.to - const next = root.mpv.chapters.findIndex(chapter => chapter.time > time) - if (next >= 0) { - root.mpv.chapter = next - } - } else if (mouse.button === Qt.RightButton && root.mpv.chapters.length > 0) { - chaptersPopup.x = Math.max(0, Math.min(root.width - chaptersPopup.width, mouseX - chaptersPopup.width / 2)) - chaptersPopup.open() - } - } - onWheel: wheel => { - if (wheel.angleDelta.y > 0) { - root.actions.seekForwardMedium.trigger() - } else if (wheel.angleDelta.y < 0) { - root.actions.seekBackwardMedium.trigger() - } + onWheel: wheel => { + if (wheel.angleDelta.y > 0) { + root.actions.seekForwardMedium.trigger() + } else if (wheel.angleDelta.y < 0) { + root.actions.seekBackwardMedium.trigger() } } } diff --git a/src/qml/ToolTipButton.qml b/src/qml/ToolTipButton.qml index 28c1640..424f059 100644 --- a/src/qml/ToolTipButton.qml +++ b/src/qml/ToolTipButton.qml @@ -2,6 +2,7 @@ // SPDX-License-Identifier: GPL-3.0-or-later import QtQuick +import org.kde.ki18n import QtQuick.Controls import org.kde.kirigami as Kirigami @@ -9,10 +10,21 @@ import org.kde.kirigami as Kirigami ToolButton { id: root + // Space activates the focused control before the window's playback action. + Keys.onShortcutOverride: event => { + if (event.key === Qt.Key_Space && event.modifiers === Qt.NoModifier) { + event.accepted = true + } + } + required property string toolTipText icon.name: "documentinfo" - focusPolicy: Qt.NoFocus + text: KI18n.i18nc("@action:button", "Help") + display: AbstractButton.IconOnly + Accessible.name: text + Accessible.description: toolTipText + focusPolicy: Qt.StrongFocus checkable: true ToolTip { @@ -21,11 +33,8 @@ ToolButton { delay: root.checked ? 0 : Kirigami.Units.toolTipDelay timeout: -1 closePolicy: Popup.NoAutoClose - contentItem: Label { - text: root.toolTipText - wrapMode: Text.WordWrap - width: Math.min(implicitWidth, Kirigami.Units.gridUnit * 20) - } - width: Math.min(contentItem.implicitWidth, Kirigami.Units.gridUnit * 20) + leftPadding + rightPadding + text: root.toolTipText + x: (root.width - width) / 2 + width: Math.min(implicitWidth, Kirigami.Units.gridUnit * 20, root.Window.window.width - Kirigami.Units.largeSpacing * 2) } } diff --git a/src/qml/VolumeSlider.qml b/src/qml/VolumeSlider.qml index ca4e293..a130ae7 100644 --- a/src/qml/VolumeSlider.qml +++ b/src/qml/VolumeSlider.qml @@ -7,49 +7,41 @@ import QtQuick.Controls import org.kde.kirigami as Kirigami import org.kde.ki18n -// Flat volume bar with the value written on it, as in Haruna. +// Use the desktop style's handle and hover/focus feedback. Slider { id: root required property MpvItem mpv + Accessible.name: KI18n.i18nc("@label:slider", "Volume") + Accessible.description: root.mpv.mute ? KI18n.i18nc("@info volume muted", "Muted") : KI18n.i18nc("@info volume percentage", "%1%", Math.round(value)) from: 0 to: root.mpv.volumeMax - implicitWidth: 100 - implicitHeight: 25 + implicitWidth: Kirigami.Units.gridUnit * 5 wheelEnabled: true stepSize: QtPotatoSettings.volumeStep - focusPolicy: Qt.NoFocus - leftPadding: 0 - rightPadding: 0 + focusPolicy: Qt.StrongFocus - handle: Item { visible: false } - - background: Rectangle { - color: Kirigami.Theme.alternateBackgroundColor - scale: root.mirrored ? -1 : 1 - - Rectangle { - width: root.visualPosition * parent.width - height: parent.height - color: Kirigami.Theme.highlightColor + // Let the Slider consume navigation keys before window playback shortcuts. + Keys.onShortcutOverride: event => { + if (event.modifiers === Qt.NoModifier && [Qt.Key_Left, Qt.Key_Right, Qt.Key_Up, Qt.Key_Down, Qt.Key_Home, Qt.Key_End].includes(event.key)) { + event.accepted = true + } + } + Keys.onPressed: event => { + if (event.modifiers === Qt.NoModifier && (event.key === Qt.Key_Home || event.key === Qt.Key_End)) { + value = event.key === Qt.Key_Home ? from : to + moved() + event.accepted = true } } onMoved: root.mpv.volume = Math.round(value) - // Follow mpv, but not while the user drags (dragging replaces a plain binding). + // Follow mpv, but not while the user drags. Binding on value { value: root.mpv.volume when: !root.pressed restoreMode: Binding.RestoreNone } - - Label { - anchors.centerIn: root - text: root.mpv.mute ? KI18n.i18nc("@info volume muted", "Muted") : Math.round(root.value) - color: "#fff" - style: Text.Outline - styleColor: "#111" - } }