Audit main UI, menus, settings, and glossary against KDE HIG #4
No files matched your search
+42
-13
@@ -13,6 +13,13 @@ import org.kde.kirigami as Kirigami
|
|||||||
Item {
|
Item {
|
||||||
id: root
|
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 Actions actions
|
||||||
required property MpvVideo mpv
|
required property MpvVideo mpv
|
||||||
property bool fullscreen: false
|
property bool fullscreen: false
|
||||||
@@ -22,9 +29,17 @@ Item {
|
|||||||
readonly property bool isFloating: QtPotatoSettings.footerStyle === "floating"
|
readonly property bool isFloating: QtPotatoSettings.footerStyle === "floating"
|
||||||
// Takes space below the video (instead of covering it).
|
// Takes space below the video (instead of covering it).
|
||||||
readonly property bool isDocked: !isFloating && !fullscreen
|
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 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
|
property bool shown: true
|
||||||
|
|
||||||
implicitHeight: isFloating
|
implicitHeight: isFloating
|
||||||
@@ -92,7 +107,7 @@ Item {
|
|||||||
|
|
||||||
x: (root.width - width) / 2
|
x: (root.width - width) / 2
|
||||||
width: root.isFloating
|
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
|
: root.width
|
||||||
state: root.shown ? "visible" : "hidden"
|
state: root.shown ? "visible" : "hidden"
|
||||||
padding: Kirigami.Units.smallSpacing
|
padding: Kirigami.Units.smallSpacing
|
||||||
@@ -112,16 +127,17 @@ Item {
|
|||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
spacing: 0
|
spacing: Kirigami.Units.smallSpacing
|
||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
spacing: root.isSmallSize ? 1 : Kirigami.Units.smallSpacing
|
spacing: Kirigami.Units.mediumSpacing
|
||||||
|
|
||||||
HamburgerMenu {
|
HamburgerMenu {
|
||||||
id: hamburgerMenu
|
id: hamburgerMenu
|
||||||
actions: root.actions
|
actions: root.actions
|
||||||
mpv: root.mpv
|
mpv: root.mpv
|
||||||
inFooter: true
|
inFooter: true
|
||||||
|
focusPolicy: Qt.StrongFocus
|
||||||
visible: root.showMenuButton
|
visible: root.showMenuButton
|
||||||
icon.width: root.buttonSize
|
icon.width: root.buttonSize
|
||||||
icon.height: root.buttonSize
|
icon.height: root.buttonSize
|
||||||
@@ -133,7 +149,7 @@ Item {
|
|||||||
display: AbstractButton.IconOnly
|
display: AbstractButton.IconOnly
|
||||||
icon.width: root.buttonSize
|
icon.width: root.buttonSize
|
||||||
icon.height: 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.text: root.mpv.paused ? KI18n.i18nc("@info:tooltip", "Start playback") : KI18n.i18nc("@info:tooltip", "Pause playback")
|
||||||
ToolTip.visible: hovered
|
ToolTip.visible: hovered
|
||||||
@@ -146,7 +162,7 @@ Item {
|
|||||||
icon.width: root.buttonSize
|
icon.width: root.buttonSize
|
||||||
icon.height: root.buttonSize
|
icon.height: root.buttonSize
|
||||||
display: AbstractButton.IconOnly
|
display: AbstractButton.IconOnly
|
||||||
focusPolicy: Qt.NoFocus
|
focusPolicy: Qt.StrongFocus
|
||||||
|
|
||||||
ToolTip.text: KI18n.i18nc("@info:tooltip", "Play previous file")
|
ToolTip.text: KI18n.i18nc("@info:tooltip", "Play previous file")
|
||||||
ToolTip.visible: hovered
|
ToolTip.visible: hovered
|
||||||
@@ -159,7 +175,7 @@ Item {
|
|||||||
icon.width: root.buttonSize
|
icon.width: root.buttonSize
|
||||||
icon.height: root.buttonSize
|
icon.height: root.buttonSize
|
||||||
display: AbstractButton.IconOnly
|
display: AbstractButton.IconOnly
|
||||||
focusPolicy: Qt.NoFocus
|
focusPolicy: Qt.StrongFocus
|
||||||
|
|
||||||
ToolTip.text: KI18n.i18nc("@info:tooltip", "Play next file")
|
ToolTip.text: KI18n.i18nc("@info:tooltip", "Play next file")
|
||||||
ToolTip.visible: hovered
|
ToolTip.visible: hovered
|
||||||
@@ -171,7 +187,6 @@ Item {
|
|||||||
mpv: root.mpv
|
mpv: root.mpv
|
||||||
actions: root.actions
|
actions: root.actions
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.preferredHeight: 20
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ToolButton {
|
ToolButton {
|
||||||
@@ -180,7 +195,7 @@ Item {
|
|||||||
icon.width: root.buttonSize
|
icon.width: root.buttonSize
|
||||||
icon.height: root.buttonSize
|
icon.height: root.buttonSize
|
||||||
display: AbstractButton.IconOnly
|
display: AbstractButton.IconOnly
|
||||||
focusPolicy: Qt.NoFocus
|
focusPolicy: Qt.StrongFocus
|
||||||
|
|
||||||
ToolTip.text: root.actions.mute.text
|
ToolTip.text: root.actions.mute.text
|
||||||
ToolTip.visible: hovered
|
ToolTip.visible: hovered
|
||||||
@@ -188,9 +203,21 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
VolumeSlider {
|
VolumeSlider {
|
||||||
|
id: volumeSlider
|
||||||
mpv: root.mpv
|
mpv: root.mpv
|
||||||
Layout.preferredWidth: root.isSmallSize ? 60 : 100
|
Layout.preferredWidth: Kirigami.Units.gridUnit * (root.isSmallSize ? 3 : 5)
|
||||||
Layout.preferredHeight: 20
|
}
|
||||||
|
|
||||||
|
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 {
|
ToolButton {
|
||||||
@@ -198,7 +225,7 @@ Item {
|
|||||||
icon.width: root.buttonSize
|
icon.width: root.buttonSize
|
||||||
icon.height: root.buttonSize
|
icon.height: root.buttonSize
|
||||||
display: AbstractButton.IconOnly
|
display: AbstractButton.IconOnly
|
||||||
focusPolicy: Qt.NoFocus
|
focusPolicy: Qt.StrongFocus
|
||||||
visible: !QtPotatoSettings.playlistToggleWithMouse
|
visible: !QtPotatoSettings.playlistToggleWithMouse
|
||||||
|
|
||||||
ToolTip.text: text
|
ToolTip.text: text
|
||||||
@@ -208,6 +235,8 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
|
spacing: Kirigami.Units.largeSpacing
|
||||||
|
|
||||||
LabelWithTooltip {
|
LabelWithTooltip {
|
||||||
text: root.mpv.mediaTitle
|
text: root.mpv.mediaTitle
|
||||||
font.pointSize: Kirigami.Theme.smallFont.pointSize
|
font.pointSize: Kirigami.Theme.smallFont.pointSize
|
||||||
|
|||||||
+38
-7
@@ -12,6 +12,13 @@ import org.kde.kirigami as Kirigami
|
|||||||
ToolBar {
|
ToolBar {
|
||||||
id: root
|
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 Actions actions
|
||||||
required property MpvItem mpv
|
required property MpvItem mpv
|
||||||
required property TranslationService translator
|
required property TranslationService translator
|
||||||
@@ -19,7 +26,7 @@ ToolBar {
|
|||||||
property bool shown: true
|
property bool shown: true
|
||||||
property bool menuBarShown: 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
|
readonly property int buttonSize: isSmallSize ? Kirigami.Units.iconSizes.small : Kirigami.Units.iconSizes.smallMedium
|
||||||
|
|
||||||
position: ToolBar.Header
|
position: ToolBar.Header
|
||||||
@@ -27,29 +34,39 @@ ToolBar {
|
|||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
|
spacing: Kirigami.Units.mediumSpacing
|
||||||
|
|
||||||
HamburgerMenu {
|
HamburgerMenu {
|
||||||
actions: root.actions
|
actions: root.actions
|
||||||
mpv: root.mpv
|
mpv: root.mpv
|
||||||
visible: !root.menuBarShown
|
visible: !root.menuBarShown
|
||||||
|
focusPolicy: Qt.StrongFocus
|
||||||
icon.width: root.buttonSize
|
icon.width: root.buttonSize
|
||||||
icon.height: root.buttonSize
|
icon.height: root.buttonSize
|
||||||
}
|
}
|
||||||
|
|
||||||
ToolButton {
|
ToolButton {
|
||||||
action: root.actions.openFile
|
action: root.actions.openFile
|
||||||
focusPolicy: Qt.NoFocus
|
focusPolicy: Qt.StrongFocus
|
||||||
icon.width: root.buttonSize
|
icon.width: root.buttonSize
|
||||||
icon.height: root.buttonSize
|
icon.height: root.buttonSize
|
||||||
display: root.isSmallSize ? AbstractButton.IconOnly : AbstractButton.TextBesideIcon
|
display: root.isSmallSize ? AbstractButton.IconOnly : AbstractButton.TextBesideIcon
|
||||||
|
|
||||||
|
ToolTip.text: text
|
||||||
|
ToolTip.visible: hovered
|
||||||
|
ToolTip.delay: Kirigami.Units.toolTipDelay
|
||||||
}
|
}
|
||||||
|
|
||||||
ToolButton {
|
ToolButton {
|
||||||
action: root.actions.openUrl
|
action: root.actions.openUrl
|
||||||
focusPolicy: Qt.NoFocus
|
focusPolicy: Qt.StrongFocus
|
||||||
icon.width: root.buttonSize
|
icon.width: root.buttonSize
|
||||||
icon.height: root.buttonSize
|
icon.height: root.buttonSize
|
||||||
display: root.isSmallSize ? AbstractButton.IconOnly : AbstractButton.TextBesideIcon
|
display: root.isSmallSize ? AbstractButton.IconOnly : AbstractButton.TextBesideIcon
|
||||||
|
|
||||||
|
ToolTip.text: text
|
||||||
|
ToolTip.visible: hovered
|
||||||
|
ToolTip.delay: Kirigami.Units.toolTipDelay
|
||||||
}
|
}
|
||||||
|
|
||||||
ToolSeparator {}
|
ToolSeparator {}
|
||||||
@@ -60,10 +77,15 @@ ToolBar {
|
|||||||
icon.name: "add-subtitle"
|
icon.name: "add-subtitle"
|
||||||
icon.width: root.buttonSize
|
icon.width: root.buttonSize
|
||||||
icon.height: root.buttonSize
|
icon.height: root.buttonSize
|
||||||
focusPolicy: Qt.NoFocus
|
focusPolicy: Qt.StrongFocus
|
||||||
enabled: root.mpv.fileLoaded
|
enabled: root.mpv.fileLoaded
|
||||||
opacity: enabled ? 1.0 : 0.6
|
opacity: enabled ? 1.0 : 0.6
|
||||||
display: root.isSmallSize ? AbstractButton.IconOnly : AbstractButton.TextBesideIcon
|
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
|
down: subtitleMenu.visible
|
||||||
onPressed: subtitleMenu.open()
|
onPressed: subtitleMenu.open()
|
||||||
|
|
||||||
@@ -89,10 +111,15 @@ ToolBar {
|
|||||||
icon.name: "audio-volume-high"
|
icon.name: "audio-volume-high"
|
||||||
icon.width: root.buttonSize
|
icon.width: root.buttonSize
|
||||||
icon.height: root.buttonSize
|
icon.height: root.buttonSize
|
||||||
focusPolicy: Qt.NoFocus
|
focusPolicy: Qt.StrongFocus
|
||||||
enabled: root.mpv.audioTracks.length > 0
|
enabled: root.mpv.audioTracks.length > 0
|
||||||
opacity: enabled ? 1.0 : 0.6
|
opacity: enabled ? 1.0 : 0.6
|
||||||
display: root.isSmallSize ? AbstractButton.IconOnly : AbstractButton.TextBesideIcon
|
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
|
down: audioMenu.visible
|
||||||
onPressed: audioMenu.open()
|
onPressed: audioMenu.open()
|
||||||
|
|
||||||
@@ -135,7 +162,7 @@ ToolBar {
|
|||||||
ToolButton {
|
ToolButton {
|
||||||
action: root.actions.cancelTranslation
|
action: root.actions.cancelTranslation
|
||||||
display: AbstractButton.IconOnly
|
display: AbstractButton.IconOnly
|
||||||
focusPolicy: Qt.NoFocus
|
focusPolicy: Qt.StrongFocus
|
||||||
|
|
||||||
ToolTip.text: text
|
ToolTip.text: text
|
||||||
ToolTip.visible: hovered
|
ToolTip.visible: hovered
|
||||||
@@ -149,10 +176,14 @@ ToolBar {
|
|||||||
|
|
||||||
ToolButton {
|
ToolButton {
|
||||||
action: root.actions.configure
|
action: root.actions.configure
|
||||||
focusPolicy: Qt.NoFocus
|
focusPolicy: Qt.StrongFocus
|
||||||
icon.width: root.buttonSize
|
icon.width: root.buttonSize
|
||||||
icon.height: root.buttonSize
|
icon.height: root.buttonSize
|
||||||
display: root.isSmallSize ? AbstractButton.IconOnly : AbstractButton.TextBesideIcon
|
display: root.isSmallSize ? AbstractButton.IconOnly : AbstractButton.TextBesideIcon
|
||||||
|
|
||||||
|
ToolTip.text: text
|
||||||
|
ToolTip.visible: hovered
|
||||||
|
ToolTip.delay: Kirigami.Units.toolTipDelay
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+53
-16
@@ -5,46 +5,83 @@
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Controls
|
import QtQuick.Controls
|
||||||
import QtQuick.Layouts
|
import QtQuick.Layouts
|
||||||
|
import org.kde.ki18n
|
||||||
import org.kde.kirigami as Kirigami
|
import org.kde.kirigami as Kirigami
|
||||||
|
|
||||||
// Single-line text popup, used for Open URL.
|
Kirigami.PromptDialog {
|
||||||
Popup {
|
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
property string lastText: ""
|
property string lastText: ""
|
||||||
property string placeholderText: ""
|
property string placeholderText: ""
|
||||||
property string buttonText: ""
|
property string buttonText: ""
|
||||||
|
property string errorText: ""
|
||||||
|
readonly property bool validUrl: isValidUrl(textField.text.trim())
|
||||||
|
|
||||||
signal submitted(text: string)
|
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
|
modal: true
|
||||||
|
standardButtons: Kirigami.Dialog.Cancel
|
||||||
|
customFooterActions: Kirigami.Action {
|
||||||
|
text: root.buttonText
|
||||||
|
icon.name: "document-open"
|
||||||
|
enabled: root.validUrl
|
||||||
|
onTriggered: root.submit()
|
||||||
|
}
|
||||||
|
|
||||||
onOpened: {
|
onOpened: {
|
||||||
|
errorText = ""
|
||||||
textField.text = root.lastText
|
textField.text = root.lastText
|
||||||
textField.forceActiveFocus(Qt.MouseFocusReason)
|
textField.forceActiveFocus(Qt.OtherFocusReason)
|
||||||
textField.selectAll()
|
textField.selectAll()
|
||||||
}
|
}
|
||||||
|
|
||||||
contentItem: RowLayout {
|
ColumnLayout {
|
||||||
|
spacing: Kirigami.Units.smallSpacing
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
Label {
|
||||||
|
text: KI18n.i18nc("@label:textbox", "URL:")
|
||||||
|
Accessible.ignored: true
|
||||||
|
}
|
||||||
|
|
||||||
TextField {
|
TextField {
|
||||||
id: textField
|
id: textField
|
||||||
placeholderText: root.placeholderText
|
placeholderText: root.placeholderText
|
||||||
|
Accessible.name: KI18n.i18nc("@label:textbox", "URL")
|
||||||
inputMethodHints: Qt.ImhUrlCharactersOnly | Qt.ImhNoAutoUppercase
|
inputMethodHints: Qt.ImhUrlCharactersOnly | Qt.ImhNoAutoUppercase
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.leftMargin: Kirigami.Units.largeSpacing
|
onAccepted: root.submit()
|
||||||
onAccepted: submitButton.clicked()
|
onTextEdited: root.errorText = ""
|
||||||
Keys.onEscapePressed: root.close()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Button {
|
Kirigami.InlineMessage {
|
||||||
id: submitButton
|
Layout.fillWidth: true
|
||||||
text: root.buttonText
|
type: Kirigami.MessageType.Error
|
||||||
enabled: textField.text.trim().length > 0
|
text: root.errorText
|
||||||
Layout.rightMargin: Kirigami.Units.largeSpacing
|
visible: text.length > 0
|
||||||
onClicked: {
|
|
||||||
root.submitted(textField.text.trim())
|
|
||||||
root.close()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+8
-9
@@ -50,10 +50,10 @@ ApplicationWindow {
|
|||||||
|
|
||||||
visible: true
|
visible: true
|
||||||
title: video.mediaTitle || KI18n.i18nc("@title:window", "QtPotato")
|
title: video.mediaTitle || KI18n.i18nc("@title:window", "QtPotato")
|
||||||
width: 1000
|
width: Kirigami.Units.gridUnit * 55
|
||||||
height: 600
|
height: Kirigami.Units.gridUnit * 33
|
||||||
minimumWidth: 400
|
minimumWidth: Kirigami.Units.gridUnit * 23
|
||||||
minimumHeight: 200
|
minimumHeight: Kirigami.Units.gridUnit * 11
|
||||||
color: Kirigami.Theme.backgroundColor
|
color: Kirigami.Theme.backgroundColor
|
||||||
|
|
||||||
onVisibilityChanged: visibility => {
|
onVisibilityChanged: visibility => {
|
||||||
@@ -180,7 +180,7 @@ ApplicationWindow {
|
|||||||
if (!QtPotatoSettings.playlistToggleWithMouse) {
|
if (!QtPotatoSettings.playlistToggleWithMouse) {
|
||||||
return
|
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"
|
playlistPanel.state = atEdge ? "visible" : "hidden"
|
||||||
}
|
}
|
||||||
onPointerLeft: footerItem.pointerLeft()
|
onPointerLeft: footerItem.pointerLeft()
|
||||||
@@ -266,11 +266,10 @@ ApplicationWindow {
|
|||||||
|
|
||||||
InputPopup {
|
InputPopup {
|
||||||
id: openUrlPopup
|
id: openUrlPopup
|
||||||
x: Kirigami.Units.largeSpacing
|
title: KI18n.i18nc("@title:window", "Open URL")
|
||||||
y: Kirigami.Units.largeSpacing
|
preferredWidth: Kirigami.Units.gridUnit * 30
|
||||||
width: Math.min(appWindow.width * 0.9, 600)
|
|
||||||
lastText: QtPotatoSettings.lastUrl
|
lastText: QtPotatoSettings.lastUrl
|
||||||
placeholderText: "https://"
|
placeholderText: "https://example.org/video.mkv"
|
||||||
buttonText: KI18n.i18nc("@action:button", "Open")
|
buttonText: KI18n.i18nc("@action:button", "Open")
|
||||||
onSubmitted: text => {
|
onSubmitted: text => {
|
||||||
QtPotatoSettings.lastUrl = text
|
QtPotatoSettings.lastUrl = text
|
||||||
|
|||||||
@@ -30,6 +30,9 @@ MpvItem {
|
|||||||
setPropertyAsync("sub-outline-size", QtPotatoSettings.subtitleOutlineSize)
|
setPropertyAsync("sub-outline-size", QtPotatoSettings.subtitleOutlineSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Kirigami.Theme.colorSet: Kirigami.Theme.Complementary
|
||||||
|
Kirigami.Theme.inherit: false
|
||||||
|
|
||||||
loadUserConfig: QtPotatoSettings.loadMpvConfig
|
loadUserConfig: QtPotatoSettings.loadMpvConfig
|
||||||
|
|
||||||
// Set once the saved volume is applied; mpv reports its default volume before that.
|
// Set once the saved volume is applied; mpv reports its default volume before that.
|
||||||
@@ -55,7 +58,7 @@ MpvItem {
|
|||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
color: "black"
|
color: Kirigami.Theme.backgroundColor
|
||||||
visible: !root.fileLoaded
|
visible: !root.fileLoaded
|
||||||
z: -1
|
z: -1
|
||||||
}
|
}
|
||||||
@@ -95,6 +98,7 @@ MpvItem {
|
|||||||
onExited: root.pointerLeft()
|
onExited: root.pointerLeft()
|
||||||
|
|
||||||
onPressed: mouse => {
|
onPressed: mouse => {
|
||||||
|
root.forceActiveFocus(Qt.MouseFocusReason)
|
||||||
switch (mouse.button) {
|
switch (mouse.button) {
|
||||||
case Qt.LeftButton:
|
case Qt.LeftButton:
|
||||||
root.actions.playPause.trigger()
|
root.actions.playPause.trigger()
|
||||||
|
|||||||
+69
-79
@@ -10,7 +10,7 @@ import QtQuick.Layouts
|
|||||||
import org.kde.kirigami as Kirigami
|
import org.kde.kirigami as Kirigami
|
||||||
import org.kde.ki18n
|
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.
|
// chapter marks, right click for the chapter list, wheel to seek.
|
||||||
Slider {
|
Slider {
|
||||||
id: root
|
id: root
|
||||||
@@ -19,7 +19,7 @@ Slider {
|
|||||||
required property Actions actions
|
required property Actions actions
|
||||||
|
|
||||||
readonly property alias chaptersPopupOpen: chaptersPopup.visible
|
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.
|
// Playing state before a drag started; playback pauses while dragging.
|
||||||
property bool pausedBeforeDrag: false
|
property bool pausedBeforeDrag: false
|
||||||
|
|
||||||
@@ -47,91 +47,81 @@ Slider {
|
|||||||
return chapter ? `${formatTime(time)} - ${chapter}` : formatTime(time)
|
return chapter ? `${formatTime(time)} - ${chapter}` : formatTime(time)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Accessible.name: KI18n.i18nc("@label:slider", "Playback position")
|
||||||
|
Accessible.description: timeLabel(value)
|
||||||
|
stepSize: 1
|
||||||
|
|
||||||
from: 0
|
from: 0
|
||||||
to: Math.max(root.mpv.duration, 0)
|
to: Math.max(root.mpv.duration, 0)
|
||||||
enabled: root.mpv.fileLoaded && root.mpv.duration > 0
|
enabled: root.mpv.fileLoaded && root.mpv.duration > 0
|
||||||
focusPolicy: Qt.NoFocus
|
focusPolicy: Qt.StrongFocus
|
||||||
leftPadding: 0
|
|
||||||
rightPadding: 0
|
|
||||||
|
|
||||||
handle: Item { visible: false }
|
// Preserve the Slider's own arrow-key behavior when playback shortcuts exist.
|
||||||
|
Keys.onShortcutOverride: event => {
|
||||||
background: Rectangle {
|
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)) {
|
||||||
id: progressBarBackground
|
event.accepted = true
|
||||||
|
|
||||||
color: Kirigami.Theme.alternateBackgroundColor
|
|
||||||
scale: root.mirrored ? -1 : 1
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
width: root.visualPosition * parent.width
|
|
||||||
height: parent.height
|
|
||||||
color: Kirigami.Theme.highlightColor
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
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 {
|
Repeater {
|
||||||
model: root.showChapterMarkers ? root.mpv.chapters : []
|
model: root.showChapterMarkers ? root.mpv.chapters : []
|
||||||
delegate: Rectangle {
|
delegate: Rectangle {
|
||||||
required property var modelData
|
required property var modelData
|
||||||
x: modelData.time / root.mpv.duration * progressBarBackground.width
|
x: root.leftPadding + (root.mirrored ? 1 - modelData.time / root.to : modelData.time / root.to) * root.availableWidth
|
||||||
width: 2
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
height: progressBarBackground.height
|
width: Kirigami.Units.smallSpacing / 2
|
||||||
color: Kirigami.Theme.textColor
|
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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
onWheel: wheel => {
|
||||||
Rectangle {
|
if (wheel.angleDelta.y > 0) {
|
||||||
id: timeToolTip
|
root.actions.seekForwardMedium.trigger()
|
||||||
|
} else if (wheel.angleDelta.y < 0) {
|
||||||
property real pointerX: 0
|
root.actions.seekBackwardMedium.trigger()
|
||||||
|
|
||||||
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()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
import QtQuick
|
import QtQuick
|
||||||
|
import org.kde.ki18n
|
||||||
import QtQuick.Controls
|
import QtQuick.Controls
|
||||||
import org.kde.kirigami as Kirigami
|
import org.kde.kirigami as Kirigami
|
||||||
|
|
||||||
@@ -9,10 +10,21 @@ import org.kde.kirigami as Kirigami
|
|||||||
ToolButton {
|
ToolButton {
|
||||||
id: root
|
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
|
required property string toolTipText
|
||||||
|
|
||||||
icon.name: "documentinfo"
|
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
|
checkable: true
|
||||||
|
|
||||||
ToolTip {
|
ToolTip {
|
||||||
@@ -21,11 +33,8 @@ ToolButton {
|
|||||||
delay: root.checked ? 0 : Kirigami.Units.toolTipDelay
|
delay: root.checked ? 0 : Kirigami.Units.toolTipDelay
|
||||||
timeout: -1
|
timeout: -1
|
||||||
closePolicy: Popup.NoAutoClose
|
closePolicy: Popup.NoAutoClose
|
||||||
contentItem: Label {
|
text: root.toolTipText
|
||||||
text: root.toolTipText
|
x: (root.width - width) / 2
|
||||||
wrapMode: Text.WordWrap
|
width: Math.min(implicitWidth, Kirigami.Units.gridUnit * 20, root.Window.window.width - Kirigami.Units.largeSpacing * 2)
|
||||||
width: Math.min(implicitWidth, Kirigami.Units.gridUnit * 20)
|
|
||||||
}
|
|
||||||
width: Math.min(contentItem.implicitWidth, Kirigami.Units.gridUnit * 20) + leftPadding + rightPadding
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+17
-25
@@ -7,49 +7,41 @@ import QtQuick.Controls
|
|||||||
import org.kde.kirigami as Kirigami
|
import org.kde.kirigami as Kirigami
|
||||||
import org.kde.ki18n
|
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 {
|
Slider {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
required property MpvItem mpv
|
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
|
from: 0
|
||||||
to: root.mpv.volumeMax
|
to: root.mpv.volumeMax
|
||||||
implicitWidth: 100
|
implicitWidth: Kirigami.Units.gridUnit * 5
|
||||||
implicitHeight: 25
|
|
||||||
wheelEnabled: true
|
wheelEnabled: true
|
||||||
stepSize: QtPotatoSettings.volumeStep
|
stepSize: QtPotatoSettings.volumeStep
|
||||||
focusPolicy: Qt.NoFocus
|
focusPolicy: Qt.StrongFocus
|
||||||
leftPadding: 0
|
|
||||||
rightPadding: 0
|
|
||||||
|
|
||||||
handle: Item { visible: false }
|
// Let the Slider consume navigation keys before window playback shortcuts.
|
||||||
|
Keys.onShortcutOverride: event => {
|
||||||
background: Rectangle {
|
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)) {
|
||||||
color: Kirigami.Theme.alternateBackgroundColor
|
event.accepted = true
|
||||||
scale: root.mirrored ? -1 : 1
|
}
|
||||||
|
}
|
||||||
Rectangle {
|
Keys.onPressed: event => {
|
||||||
width: root.visualPosition * parent.width
|
if (event.modifiers === Qt.NoModifier && (event.key === Qt.Key_Home || event.key === Qt.Key_End)) {
|
||||||
height: parent.height
|
value = event.key === Qt.Key_Home ? from : to
|
||||||
color: Kirigami.Theme.highlightColor
|
moved()
|
||||||
|
event.accepted = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onMoved: root.mpv.volume = Math.round(value)
|
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 {
|
Binding on value {
|
||||||
value: root.mpv.volume
|
value: root.mpv.volume
|
||||||
when: !root.pressed
|
when: !root.pressed
|
||||||
restoreMode: Binding.RestoreNone
|
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"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user