From fe2f159660eb93a071afd749a2ab44ee5609670b Mon Sep 17 00:00:00 2001 From: Tri Vo Khac Date: Tue, 27 Oct 2026 23:00:17 +0700 Subject: [PATCH] feat: auto-hide controls in full screen, add subtitle size and outline settings --- src/qml/Main.qml | 1 + src/qml/PlayerPage.qml | 143 +++++++++++++++++++++++++++-- src/qml/SettingsPage.qml | 26 ++++++ src/settings/qtpotatosettings.kcfg | 14 +++ 4 files changed, 176 insertions(+), 8 deletions(-) diff --git a/src/qml/Main.qml b/src/qml/Main.qml index 40c3dfb..19bb0a5 100644 --- a/src/qml/Main.qml +++ b/src/qml/Main.qml @@ -18,6 +18,7 @@ Kirigami.ApplicationWindow { 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 diff --git a/src/qml/PlayerPage.qml b/src/qml/PlayerPage.qml index d2c2bce..00ccb5b 100644 --- a/src/qml/PlayerPage.qml +++ b/src/qml/PlayerPage.qml @@ -9,6 +9,9 @@ Kirigami.Page { id: page property url initialUrl + // Set by the window. In full screen the bars float over the video and hide when the + // pointer rests. + property bool fullScreen: false readonly property alias mediaTitle: mpv.mediaTitle readonly property alias translator: translator @@ -18,6 +21,27 @@ Kirigami.Page { title: KI18n.i18nc("@title", "Player") padding: 0 + globalToolBarStyle: fullScreen ? Kirigami.ApplicationHeaderStyle.None : Kirigami.ApplicationHeaderStyle.ToolBar + + // Kept true for a moment after the pointer moves. + property bool pointerActive: false + readonly property bool controlsVisible: !fullScreen || pointerActive || topBarHover.hovered || footerHover.hovered + || subtitlePopup.visible || seekBar.pressed || !mpv.fileLoaded || menuOpen + // Set while the full screen "More" menu is open, so the bars stay. + property bool menuOpen: false + + onFullScreenChanged: page.revealControls() + + function revealControls() { + pointerActive = true + idleTimer.restart() + } + + Timer { + id: idleTimer + interval: 2500 + onTriggered: page.pointerActive = false + } actions: [ Kirigami.Action { @@ -27,10 +51,10 @@ Kirigami.Page { onTriggered: fileDialog.open() }, Kirigami.Action { - icon.name: "view-fullscreen" - text: KI18n.i18nc("@action", "Full Screen") + // F and Esc are handled by the Shortcuts below, which work while the bars are hidden. + icon.name: page.fullScreen ? "view-restore" : "view-fullscreen" + text: page.fullScreen ? KI18n.i18nc("@action", "Exit Full Screen") : KI18n.i18nc("@action", "Full Screen") displayHint: Kirigami.DisplayHint.IconOnly - shortcut: "F" onTriggered: page.fullScreenToggled() }, Kirigami.Action { @@ -49,11 +73,23 @@ Kirigami.Page { ] Component.onCompleted: { + page.applySubtitleStyle() if (page.initialUrl.toString().length > 0) { mpv.loadFile(page.initialUrl) } } + // Plain-text and translated subtitles; styled ASS subtitles keep their own fonts. + function applySubtitleStyle() { + mpv.setPropertyAsync("sub-font-size", QtPotatoSettings.subtitleFontSize) + mpv.setPropertyAsync("sub-outline-size", QtPotatoSettings.subtitleOutlineSize) + } + + readonly property int subtitleFontSize: QtPotatoSettings.subtitleFontSize + onSubtitleFontSizeChanged: applySubtitleStyle() + readonly property int subtitleOutlineSize: QtPotatoSettings.subtitleOutlineSize + onSubtitleOutlineSizeChanged: applySubtitleStyle() + function formatTime(totalSeconds: real): string { const s = Math.max(0, Math.floor(totalSeconds)) const h = Math.floor(s / 3600) @@ -66,6 +102,15 @@ Kirigami.Page { sequence: "Space" onActivated: mpv.togglePause() } + Shortcut { + sequence: "F" + onActivated: page.fullScreenToggled() + } + Shortcut { + sequence: "Escape" + enabled: page.fullScreen + onActivated: page.fullScreenToggled() + } Shortcut { sequence: "Left" onActivated: mpv.seek(mpv.position - 5) @@ -125,14 +170,17 @@ Kirigami.Page { onAccepted: mpv.loadFile(selectedFile) } - ColumnLayout { + Item { anchors.fill: parent - spacing: 0 MpvItem { id: mpv - Layout.fillWidth: true - Layout.fillHeight: true + anchors { + left: parent.left + right: parent.right + top: parent.top + bottom: page.fullScreen ? parent.bottom : footer.top + } Rectangle { anchors.fill: parent @@ -153,6 +201,9 @@ Kirigami.Page { MouseArea { anchors.fill: parent + hoverEnabled: true + cursorShape: page.controlsVisible ? Qt.ArrowCursor : Qt.BlankCursor + onPositionChanged: page.revealControls() onClicked: mpv.togglePause() onDoubleClicked: page.fullScreenToggled() } @@ -173,9 +224,85 @@ Kirigami.Page { } } + // Stands in for the page header in full screen. QQC2.ToolBar { - Layout.fillWidth: true + id: topBar + anchors { + left: parent.left + right: parent.right + top: parent.top + } + position: QQC2.ToolBar.Header + // No fade: over a paused video Qt Quick renders frames too rarely for a smooth one. + visible: page.fullScreen && page.controlsVisible + + HoverHandler { + id: topBarHover + } + + contentItem: RowLayout { + spacing: Kirigami.Units.smallSpacing + + Kirigami.Heading { + Layout.fillWidth: true + Layout.leftMargin: Kirigami.Units.smallSpacing + level: 1 + elide: Text.ElideRight + text: mpv.mediaTitle + } + + // Plain buttons: Kirigami.ActionToolBar dropped the full screen action here. + QQC2.ToolButton { + action: page.actions[0] + focusPolicy: Qt.NoFocus + } + + QQC2.ToolButton { + action: page.actions[1] + focusPolicy: Qt.NoFocus + display: QQC2.AbstractButton.IconOnly + + QQC2.ToolTip.text: text + QQC2.ToolTip.visible: hovered + QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay + } + + QQC2.ToolButton { + id: moreButton + focusPolicy: Qt.NoFocus + icon.name: "overflow-menu" + text: KI18n.i18nc("@action:button", "More") + display: QQC2.AbstractButton.IconOnly + down: moreMenu.visible + onPressed: moreMenu.popup(moreButton, 0, moreButton.height) + + QQC2.Menu { + id: moreMenu + onVisibleChanged: page.menuOpen = visible + QQC2.MenuItem { + action: page.actions[2] + } + QQC2.MenuItem { + action: page.actions[3] + } + } + } + } + } + + QQC2.ToolBar { + id: footer + anchors { + left: parent.left + right: parent.right + bottom: parent.bottom + } position: QQC2.ToolBar.Footer + visible: page.controlsVisible + + HoverHandler { + id: footerHover + } RowLayout { anchors.fill: parent diff --git a/src/qml/SettingsPage.qml b/src/qml/SettingsPage.qml index 81d58e8..c8593f1 100644 --- a/src/qml/SettingsPage.qml +++ b/src/qml/SettingsPage.qml @@ -27,6 +27,32 @@ FormCard.FormCardPage { onVisibleChanged: if (!visible) page.translator.clearError() } + FormCard.FormHeader { + title: KI18n.i18nc("@title:group", "Subtitles") + } + + FormCard.FormCard { + FormCard.FormSpinBoxDelegate { + label: KI18n.i18nc("@label:spinbox", "Text size") + description: KI18n.i18nc("@info", "Text size and outline apply to translated and plain-text subtitles. Styled subtitles (ASS) keep the look they were made with.") + from: 20 + to: 150 + stepSize: 2 + value: QtPotatoSettings.subtitleFontSize + onValueModified: QtPotatoSettings.subtitleFontSize = value + } + + FormCard.FormDelegateSeparator {} + + FormCard.FormSpinBoxDelegate { + label: KI18n.i18nc("@label:spinbox", "Outline size") + from: 0 + to: 10 + value: QtPotatoSettings.subtitleOutlineSize + onValueModified: QtPotatoSettings.subtitleOutlineSize = value + } + } + FormCard.FormHeader { title: KI18n.i18nc("@title:group", "Subtitle Translation") } diff --git a/src/settings/qtpotatosettings.kcfg b/src/settings/qtpotatosettings.kcfg index b53ef97..0695c94 100644 --- a/src/settings/qtpotatosettings.kcfg +++ b/src/settings/qtpotatosettings.kcfg @@ -3,6 +3,20 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.kde.org/standards/kcfg/1.0 http://www.kde.org/standards/kcfg/1.0/kcfg.xsd"> + + + + 52 + 20 + 150 + + + + 3 + 0 + 10 + +