Files
QtPotato/src/qml/MpvVideo.qml
T

203 lines
6.9 KiB
QML

// SPDX-FileCopyrightText: 2020 George Florea Bănuș <georgefb899@gmail.com>
// SPDX-FileCopyrightText: 2026 Tri Vo Khac
// SPDX-License-Identifier: GPL-3.0-or-later
import QtQuick
import QtQuick.Controls
import org.kde.ki18n
import org.kde.kirigami as Kirigami
// The video, with Haruna's default mouse bindings and drag and drop.
MpvItem {
id: root
required property Actions actions
property bool fullscreen: false
signal pointerMoved(x: real, y: real)
signal pointerLeft()
// Files dropped on the top part are queued; on the bottom part they play now.
signal filesDropped(urls: list<url>, append: bool)
signal subtitleDropped(url: url)
function isSubtitleFile(url: url): bool {
return /\.(srt|ass|ssa|vtt|sub|sup)$/i.test(url.toString())
}
// Plain-text and translated subtitles; styled ASS subtitles keep their own fonts.
function applySubtitleStyle() {
setPropertyAsync("sub-font-size", QtPotatoSettings.subtitleFontSize)
setPropertyAsync("sub-outline-size", QtPotatoSettings.subtitleOutlineSize)
}
Kirigami.Theme.colorSet: Kirigami.Theme.Complementary
Kirigami.Theme.inherit: false
loadUserConfig: QtPotatoSettings.loadMpvConfig
preferredAudioLanguages: QtPotatoSettings.preferredAudioLanguages
preferredSubtitleLanguages: QtPotatoSettings.preferredSubtitleLanguages
autoSelectSubtitle: QtPotatoSettings.autoSelectSubtitle
// Set once the saved volume is applied; mpv reports its default volume before that.
property bool volumeRestored: false
// QtPotato's own settings win over mpv.conf.
onUserConfigApplied: {
applySubtitleStyle()
volume = QtPotatoSettings.volume
volumeRestored = true
}
onVolumeChanged: {
if (volumeRestored) {
QtPotatoSettings.volume = volume
}
}
readonly property int subtitleFontSize: QtPotatoSettings.subtitleFontSize
onSubtitleFontSizeChanged: applySubtitleStyle()
readonly property int subtitleOutlineSize: QtPotatoSettings.subtitleOutlineSize
onSubtitleOutlineSizeChanged: applySubtitleStyle()
Rectangle {
anchors.fill: parent
color: Kirigami.Theme.backgroundColor
visible: !root.fileLoaded
z: -1
}
Kirigami.PlaceholderMessage {
anchors.centerIn: parent
width: parent.width - Kirigami.Units.gridUnit * 4
visible: !root.fileLoaded && !dropArea.containsDrag
icon.name: "video-symbolic"
text: KI18n.i18nc("@info:placeholder", "No video open")
explanation: KI18n.i18nc("@info:placeholder", "Open a file or drop one here")
}
Timer {
id: hideCursorTimer
running: root.fullscreen && mouseArea.containsMouse
repeat: true
interval: 2000
onTriggered: mouseArea.hideCursor = true
}
MouseArea {
id: mouseArea
property bool hideCursor: false
anchors.fill: parent
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton | Qt.ForwardButton | Qt.BackButton
hoverEnabled: true
cursorShape: hideCursor && root.fullscreen ? Qt.BlankCursor : Qt.ArrowCursor
onPositionChanged: mouse => {
hideCursor = false
hideCursorTimer.restart()
root.pointerMoved(mouse.x, mouse.y)
}
onExited: root.pointerLeft()
onPressed: mouse => {
root.forceActiveFocus(Qt.MouseFocusReason)
switch (mouse.button) {
case Qt.LeftButton:
root.actions.playPause.trigger()
break
case Qt.MiddleButton:
root.actions.mute.trigger()
break
case Qt.RightButton:
root.actions.openContextMenu.trigger()
break
case Qt.ForwardButton:
root.actions.seekForwardSmall.trigger()
break
case Qt.BackButton:
root.actions.seekBackwardSmall.trigger()
break
}
}
onDoubleClicked: mouse => {
if (mouse.button === Qt.LeftButton) {
// Both presses of the double click toggled playback, so it is unchanged.
root.actions.toggleFullscreen.trigger()
} else if (mouse.button === Qt.MiddleButton) {
root.actions.configure.trigger()
}
}
onWheel: wheel => {
if (wheel.angleDelta.y > 0) {
root.actions.volumeUp.trigger()
} else if (wheel.angleDelta.y < 0) {
root.actions.volumeDown.trigger()
}
}
}
DropArea {
id: dropArea
readonly property bool onTopPart: containsDrag && drag.y <= height * 0.25
property bool onlySubtitles: false
anchors.fill: parent
keys: ["text/uri-list"]
onEntered: drag => {
onlySubtitles = drag.urls.length > 0 && drag.urls.every(url => root.isSubtitleFile(url))
}
onDropped: drop => {
if (!drop.hasUrls) {
return
}
if (onlySubtitles) {
root.subtitleDropped(drop.urls[0])
} else {
root.filesDropped(drop.urls, onTopPart)
}
}
Repeater {
model: dropArea.containsDrag && !dropArea.onlySubtitles ? 2 : 0
delegate: Rectangle {
id: dropZone
required property int index
readonly property bool isTop: index === 0
readonly property bool active: isTop === dropArea.onTopPart
x: Kirigami.Units.smallSpacing
y: isTop ? Kirigami.Units.smallSpacing : dropArea.height * 0.25 + Kirigami.Units.smallSpacing
width: dropArea.width - Kirigami.Units.smallSpacing * 2
height: (isTop ? dropArea.height * 0.25 : dropArea.height * 0.75) - Kirigami.Units.smallSpacing * 2
color: "transparent"
radius: Kirigami.Units.cornerRadius
border.color: active ? Kirigami.Theme.activeTextColor : "transparent"
border.width: active ? 3 : 0
Label {
anchors.fill: parent
text: dropZone.isTop
? KI18n.i18nc("@info:drag&drop append items", "Add to Playlist")
: KI18n.i18nc("@info:drag&drop play now", "Play Now")
elide: Text.ElideRight
color: "white"
style: Text.Outline
styleColor: "black"
opacity: dropZone.active ? 1 : 0.7
font.pointSize: Kirigami.Theme.defaultFont.pointSize * (dropZone.isTop ? 1.6 : 2.2)
font.bold: true
horizontalAlignment: Qt.AlignHCenter
verticalAlignment: Qt.AlignVCenter
}
}
}
}
}