feat: rebuild the interface after Haruna

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
triandClaude Opus 5.5 committed 2026-10-28 09:26:47 +07:00
1 parent 54ad4eecf3
commit 614a31db1b
45 files changed
+3818 -798

No files matched your search

+302 -31
View File
@@ -1,44 +1,315 @@
import QtQuick
import org.kde.kirigami as Kirigami
import org.kde.kirigamiaddons.formcard as FormCard
// SPDX-FileCopyrightText: 2020 George Florea Bănuș <georgefb899@gmail.com>
// SPDX-FileCopyrightText: 2026 Tri Vo Khac
// SPDX-License-Identifier: GPL-3.0-or-later
Kirigami.ApplicationWindow {
id: root
import QtQuick
import QtQuick.Controls
import QtQuick.Dialogs
import org.kde.ki18n
import org.kde.kirigami as Kirigami
import org.kde.config as KConfig
// Main window, laid out like Haruna: menu bar, toolbar, video with the playlist at
// its edge, and the playback controls below.
ApplicationWindow {
id: appWindow
// Set from C++ (main.cpp) when a file is passed on the command line.
required property url initialUrl
width: Kirigami.Units.gridUnit * 50
height: Kirigami.Units.gridUnit * 32
minimumWidth: Kirigami.Units.gridUnit * 20
minimumHeight: Kirigami.Units.gridUnit * 14
property int previousVisibility: Window.Windowed
readonly property bool isFullScreen: visibility === Window.FullScreen
title: player.mediaTitle.length > 0 ? player.mediaTitle : "QtPotato"
pageStack.initialPage: PlayerPage {
id: player
initialUrl: root.initialUrl
fullScreen: root.visibility === Window.FullScreen
onAboutRequested: root.pageStack.layers.push(aboutPage)
onSettingsRequested: root.pageStack.layers.push(settingsPage)
onFullScreenToggled: root.visibility = root.visibility === Window.FullScreen ? Window.Windowed : Window.FullScreen
function openFile(url: url) {
playlistModel.openFile(url, QtPotatoSettings.playlistLoadSiblings)
}
pageStack.globalToolBar.style: Kirigami.ApplicationHeaderStyle.ToolBar
pageStack.columnView.columnResizeMode: Kirigami.ColumnView.SingleColumn
onClosing: QtPotatoSettings.save()
Component {
id: settingsPage
SettingsPage {
translator: player.translator
mpvItem: player.mpvItem
function toggleFullScreen() {
if (isFullScreen) {
exitFullScreen()
} else {
showFullScreen()
}
}
Component {
id: aboutPage
FormCard.AboutPage {}
function exitFullScreen() {
if (previousVisibility === Window.Maximized) {
showMaximized()
} else {
showNormal()
}
}
function translateCurrentSubtitle() {
const track = video.subtitleTracks.find(track => track.id === video.subtitleId)
if (track) {
translationService.translate(video.currentUrl, track, video.mediaTitle)
}
}
visible: true
title: video.mediaTitle || KI18n.i18nc("@title:window", "QtPotato")
width: 1000
height: 600
minimumWidth: 400
minimumHeight: 200
color: Kirigami.Theme.backgroundColor
onVisibilityChanged: visibility => {
// Restored when leaving full screen.
if (visibility !== Window.FullScreen) {
previousVisibility = visibility
}
}
onClosing: QtPotatoSettings.save()
Component.onCompleted: {
if (initialUrl.toString().length > 0) {
openFile(initialUrl)
}
}
KConfig.WindowStateSaver {
configGroupName: "MainWindow"
}
menuBar: MainMenuBar {
actions: appActions
mpv: video
visible: !appWindow.isFullScreen && QtPotatoSettings.showMenuBar
}
header: Header {
id: headerItem
actions: appActions
mpv: video
translator: translationService
shown: !appWindow.isFullScreen && QtPotatoSettings.showHeader
menuBarShown: appWindow.menuBar.visible
}
Actions {
id: appActions
mpv: video
playlist: playlistModel
translator: translationService
osd: osdItem
fullscreen: appWindow.isFullScreen
shortcutsEnabled: !fileDialog.visible && !subtitleDialog.visible && !addToPlaylistDialog.visible && !openUrlPopup.visible
onOpenFileRequested: fileDialog.open()
onFileOpenRequested: url => appWindow.openFile(url)
onOpenUrlRequested: openUrlPopup.open()
onAddSubtitleRequested: subtitleDialog.open()
onSettingsRequested: page => settingsWindow.openPage(page)
onFullscreenToggleRequested: appWindow.toggleFullScreen()
onFullscreenExitRequested: appWindow.exitFullScreen()
onPlaylistToggleRequested: playlistPanel.toggle()
onContextMenuRequested: contextMenu.popup()
onTranslateRequested: appWindow.translateCurrentSubtitle()
onMenuBarToggleRequested: {
QtPotatoSettings.showMenuBar = !QtPotatoSettings.showMenuBar
QtPotatoSettings.save()
}
onHeaderToggleRequested: {
QtPotatoSettings.showHeader = !QtPotatoSettings.showHeader
QtPotatoSettings.save()
}
}
PlaylistModel {
id: playlistModel
onPlayRequested: url => {
video.loadFile(url)
RecentFilesModel.add(url)
}
}
Binding {
target: RecentFilesModel
property: "maxCount"
value: QtPotatoSettings.maxRecentFiles
}
MprisItem {
mpvItem: video
playlist: playlistModel
window: appWindow
onRaiseRequested: {
appWindow.show()
appWindow.raise()
appWindow.requestActivate()
}
onOpenUriRequested: uri => appWindow.openFile(uri)
onNextRequested: appActions.playNext()
onPreviousRequested: appActions.playPrevious()
}
TranslationService {
id: translationService
onTranslated: (subtitleUrl, fromCache) => {
video.showTranslatedSubtitleFile(subtitleUrl,
KI18n.i18nc("@item:inlistbox subtitle track, %1 is a language", "%1 (translated)", QtPotatoSettings.targetLanguage))
osdItem.message(fromCache
? KI18n.i18nc("@info:osd", "Loaded the saved translation")
: KI18n.i18nc("@info:osd", "Translation ready"))
}
}
MpvVideo {
id: video
actions: appActions
fullscreen: appWindow.isFullScreen
readonly property bool playlistBeside: playlistPanel.visible && !QtPotatoSettings.playlistOverlayVideo
anchors.top: parent.top
anchors.left: playlistBeside && playlistPanel.edge === Qt.LeftEdge ? playlistPanel.right : parent.left
anchors.right: playlistBeside && playlistPanel.edge === Qt.RightEdge ? playlistPanel.left : parent.right
height: parent.height - (footerItem.isDocked ? footerItem.height : 0)
onPointerMoved: (x, y) => {
footerItem.pointerMoved(y)
if (!QtPotatoSettings.playlistToggleWithMouse) {
return
}
const atEdge = playlistPanel.edge === Qt.RightEdge ? x > width - 50 : x < 50
playlistPanel.state = atEdge ? "visible" : "hidden"
}
onPointerLeft: footerItem.pointerLeft()
onFilesDropped: (urls, append) => {
if (append) {
playlistModel.append(urls)
} else if (urls.length === 1) {
appWindow.openFile(urls[0])
} else {
playlistModel.replace(urls)
}
}
onSubtitleDropped: url => video.addSubtitleFile(url)
onEndOfFile: {
switch (QtPotatoSettings.playbackBehavior) {
case "RepeatItem":
video.seek(0)
video.paused = false
break
case "StopAfterItem":
break
default:
appActions.playNext()
}
}
// A translation belongs to the file it was started for.
onCurrentUrlChanged: translationService.cancel()
Osd {
id: osdItem
maxWidth: video.width
}
}
Playlist {
id: playlistPanel
playlist: playlistModel
mpv: video
mainWindowWidth: appWindow.width
fullscreen: appWindow.isFullScreen
height: video.height
onAddFilesRequested: addToPlaylistDialog.open()
}
Footer {
id: footerItem
actions: appActions
mpv: video
fullscreen: appWindow.isFullScreen
showMenuButton: !headerItem.visible && !appWindow.menuBar.visible
width: parent.width
anchors.bottom: parent.bottom
}
Kirigami.InlineMessage {
width: Math.min(Kirigami.Units.gridUnit * 40, appWindow.width * 0.8)
anchors.centerIn: parent
type: Kirigami.MessageType.Error
text: translationService.errorText
visible: text.length > 0
showCloseButton: true
onVisibleChanged: if (!visible) translationService.clearError()
actions: Kirigami.Action {
icon.name: "configure"
text: KI18n.i18nc("@action:button", "Translation Settings")
onTriggered: settingsWindow.openPage("translation")
}
}
MpvContextMenu {
id: contextMenu
actions: appActions
mpv: video
}
SettingsWindow {
id: settingsWindow
mpv: video
translator: translationService
}
InputPopup {
id: openUrlPopup
x: Kirigami.Units.largeSpacing
y: Kirigami.Units.largeSpacing
width: Math.min(appWindow.width * 0.9, 600)
lastText: QtPotatoSettings.lastUrl
placeholderText: "https://"
buttonText: KI18n.i18nc("@action:button", "Open")
onSubmitted: text => {
QtPotatoSettings.lastUrl = text
QtPotatoSettings.save()
appWindow.openFile(text)
}
}
FileDialog {
id: fileDialog
title: KI18n.i18nc("@title:window", "Select File")
currentFolder: QtPotatoSettings.fileDialogLastLocation
fileMode: FileDialog.OpenFile
nameFilters: [
KI18n.i18nc("@item:inlistbox file filter", "Video files (*.mkv *.mp4 *.webm *.avi *.mov *.m4v *.ts *.flv *.wmv)"),
KI18n.i18nc("@item:inlistbox file filter", "All files (*)")
]
onAccepted: {
appWindow.openFile(selectedFile)
QtPotatoSettings.fileDialogLastLocation = currentFolder
QtPotatoSettings.save()
}
}
FileDialog {
id: addToPlaylistDialog
title: KI18n.i18nc("@title:window", "Add to Playlist")
currentFolder: QtPotatoSettings.fileDialogLastLocation
fileMode: FileDialog.OpenFiles
onAccepted: playlistModel.append(selectedFiles)
}
FileDialog {
id: subtitleDialog
title: KI18n.i18nc("@title:window", "Select Subtitles File")
currentFolder: {
const url = video.currentUrl.toString()
return url.substring(0, url.lastIndexOf("/"))
}
fileMode: FileDialog.OpenFile
nameFilters: [
KI18n.i18nc("@item:inlistbox file filter", "Subtitle files (*.srt *.ass *.ssa *.vtt *.sub *.sup)"),
KI18n.i18nc("@item:inlistbox file filter", "All files (*)")
]
onAccepted: video.addSubtitleFile(selectedFile)
}
}