diff --git a/resources/qml/delegates/PlayableMediaMessage.qml b/resources/qml/delegates/PlayableMediaMessage.qml index 2b4c4d49..67214dd7 100644 --- a/resources/qml/delegates/PlayableMediaMessage.qml +++ b/resources/qml/delegates/PlayableMediaMessage.qml @@ -10,7 +10,7 @@ import QtQuick.Controls 2.15 import QtQuick.Layouts 1.15 import im.nheko 1.0 -ColumnLayout { +Item { id: content required property double proportionalHeight @@ -22,7 +22,13 @@ ColumnLayout { required property string body required property string filesize - Layout.fillWidth: true + property double tempWidth: Math.min(parent ? parent.width : undefined, originalWidth < 1 ? 400 : originalWidth) + property double tempHeight: tempWidth * proportionalHeight + property double divisor: isReply ? 4 : 2 + property bool tooHigh: tempHeight > timelineRoot.height / divisor + + height: (type == MtxEvent.VideoMessage ? tooHigh ? timelineRoot.height / divisor : tempHeight : 80) + fileInfoLabel.height + width: type == MtxEvent.VideoMessage ? tooHigh ? (timelineRoot.height / divisor) / proportionalHeight : tempWidth : 250 MxcMedia { id: mxcmedia @@ -38,15 +44,10 @@ ColumnLayout { Rectangle { id: videoContainer - - property double tempWidth: Math.min(parent ? parent.width : undefined, originalWidth < 1 ? 400 : originalWidth) - property double tempHeight: tempWidth * proportionalHeight - property double divisor: isReply ? 4 : 2 - property bool tooHigh: tempHeight > timelineRoot.height / divisor - color: type == MtxEvent.VideoMessage ? Nheko.colors.window : "transparent" - Layout.preferredHeight: type == MtxEvent.VideoMessage ? tooHigh ? timelineRoot.height / divisor : tempHeight : 80 - Layout.preferredWidth: type == MtxEvent.VideoMessage ? tooHigh ? (timelineRoot.height / divisor) / proportionalHeight : tempWidth : 250 + width: parent.width + height: parent.height - fileInfoLabel.height + Image { anchors.fill: parent @@ -65,14 +66,18 @@ ColumnLayout { flushMode: VideoOutput.FirstFrame } + + } + + } + MediaControls { id: mediaControls - anchors.fill: parent - x: type == MtxEvent.VideoMessage ? videoOutput.contentRect.x : videoContainer.x - y: type == MtxEvent.VideoMessage ? videoOutput.contentRect.y : videoContainer.y - width: type == MtxEvent.VideoMessage ? videoOutput.contentRect.width : videoContainer.width - height: type == MtxEvent.VideoMessage ? videoOutput.contentRect.height : videoContainer.height + anchors.left: content.left + anchors.right: content.right + anchors.bottom: fileInfoLabel.top + playingVideo: type == MtxEvent.VideoMessage positionValue: mxcmedia.position duration: mxcmedia.duration @@ -83,15 +88,12 @@ ColumnLayout { onLoadActivated: mxcmedia.eventId = eventId } - } - - } - // information about file name and file size Label { id: fileInfoLabel - Layout.fillWidth: true + anchors.bottom: content.bottom + text: body + " [" + filesize + "]" textFormat: Text.PlainText elide: Text.ElideRight diff --git a/resources/qml/ui/NhekoSlider.qml b/resources/qml/ui/NhekoSlider.qml index 887cb80c..6cf1fd2d 100644 --- a/resources/qml/ui/NhekoSlider.qml +++ b/resources/qml/ui/NhekoSlider.qml @@ -7,71 +7,42 @@ import QtQuick.Controls 2.15 import im.nheko 1.0 Slider { - id: slider + id: control + value: 0 - property real sliderWidth - property real sliderHeight + property color progressColor: Nheko.colors.highlight property bool alwaysShowSlider: true + property int sliderRadius: 16 + implicitHeight: sliderRadius - anchors.bottomMargin: orientation == Qt.Vertical ? Nheko.paddingMedium : undefined - anchors.topMargin: orientation == Qt.Vertical ? Nheko.paddingMedium : undefined - anchors.leftMargin: orientation == Qt.Vertical ? undefined : Nheko.paddingMedium - anchors.rightMargin: orientation == Qt.Vertical ? undefined : Nheko.paddingMedium + padding: 0 background: Rectangle { - x: slider.leftPadding + (slider.orientation == Qt.Vertical ? slider.availableWidth / 2 - width / 2 : 0) - y: slider.topPadding + (slider.orientation == Qt.Vertical ? 0 : slider.availableHeight / 2 - height / 2) - // implicitWidth: slider.orientation == Qt.Vertical ? 8 : 100 - // implicitHeight: slider.orientation == Qt.Vertical ? 100 : 8 - width: slider.orientation == Qt.Vertical ? sliderWidth : slider.availableWidth - height: slider.orientation == Qt.Vertical ? slider.availableHeight : sliderHeight - radius: 2 - color: { - if (slider.orientation == Qt.Vertical) { - return Nheko.colors.highlight; - } else { - var col = Nheko.colors.buttonText; - return Qt.rgba(col.r, col.g, col.b, 0.5); - } - } - border.color: { - var col = Nheko.colors.base; - return Qt.rgba(col.r, col.g, col.b, 0.5); - } + x: control.leftPadding + handle.width/2 + y: control.topPadding + control.availableHeight / 2 - height / 2 + implicitWidth: 200 + implicitHeight: control.sliderRadius/4 + width: control.availableWidth - handle.width + height: implicitHeight + radius: height/2 + color: Nheko.colors.buttonText Rectangle { - width: slider.orientation == Qt.Vertical ? parent.width : slider.visualPosition * parent.width - height: slider.orientation == Qt.Vertical ? slider.visualPosition * parent.height : parent.height - color: { - if (slider.orientation == Qt.Vertical) { - return Nheko.colors.buttonText; - } else { - return Nheko.colors.highlight; - } - } + width: control.visualPosition * parent.width + height: parent.height + color: control.progressColor radius: 2 } - } handle: Rectangle { - x: { - if (slider.orientation == Qt.Vertical) - return slider.leftPadding + slider.availableWidth / 2 - width / 2; - else - return slider.leftPadding + slider.visualPosition * (slider.availableWidth - width); - } - y: { - if (slider.orientation == Qt.Vertical) - return slider.topPadding + slider.visualPosition * (slider.availableHeight - height); - else - return slider.topPadding + slider.availableHeight / 2 - height / 2; - } - implicitWidth: 16 - implicitHeight: 16 - radius: slider.width / 2 - color: Nheko.colors.highlight - visible: alwaysShowSlider || slider.hovered || slider.pressed || Settings.mobileMode + x: control.leftPadding + control.visualPosition * background.width + y: control.topPadding + control.availableHeight / 2 - height / 2 + implicitWidth: control.sliderRadius + implicitHeight: control.sliderRadius + radius: control.sliderRadius/2 + color: control.progressColor + visible: Settings.mobileMode || control.alwaysShowSlider || control.hovered || control.pressed + border.color: control.progressColor } - } diff --git a/resources/qml/ui/media/MediaControls.qml b/resources/qml/ui/media/MediaControls.qml index b529462d..f5321dca 100644 --- a/resources/qml/ui/media/MediaControls.qml +++ b/resources/qml/ui/media/MediaControls.qml @@ -3,28 +3,33 @@ // SPDX-License-Identifier: GPL-3.0-or-later import "../" +import "../../" import QtMultimedia 5.15 import QtQuick 2.15 import QtQuick.Controls 2.15 import QtQuick.Layouts 1.15 import im.nheko 1.0 -Item { +Rectangle { id: control property alias desiredVolume: volumeSlider.desiredVolume - property alias muted: volumeSlider.muted + property bool muted: false property bool playingVideo: false property var mediaState property bool mediaLoaded: false property var duration property var positionValue: 0 property var position - property int controlHeight: 25 property bool shouldShowControls: !playingVideo || playerMouseArea.shouldShowControls || volumeSlider.controlsVisible + color: { + var wc = Nheko.colors.alternateBase; + return Qt.rgba(wc.r, wc.g, wc.b, 0.5); + } + height: controlLayout.implicitHeight - signal playPauseActivated(real mouseX, real mouseY) - signal loadActivated(real mouseX, real mouseY) + signal playPauseActivated() + signal loadActivated() function durationToString(duration) { function maybeZeroPrepend(time) { @@ -51,7 +56,7 @@ Item { property bool shouldShowControls: (containsMouse && controlHideTimer.running) || (control.mediaState != MediaPlayer.PlayingState) || controlLayout.contains(mapToItem(controlLayout, mouseX, mouseY)) onClicked: { - control.mediaLoaded ? control.playPauseActivated(mouseX, mouseY) : control.loadActivated(mouseX, mouseY); + control.mediaLoaded ? control.playPauseActivated() : control.loadActivated(); } hoverEnabled: true onPositionChanged: controlHideTimer.start() @@ -66,102 +71,155 @@ Item { id: controlLayout opacity: control.shouldShowControls ? 1 : 0 - // spacing: Nheko.paddingSmall + spacing: 0 anchors.bottom: control.bottom anchors.left: control.left anchors.right: control.right NhekoSlider { Layout.fillWidth: true - Layout.minimumWidth: 50 - Layout.leftMargin: Nheko.paddingMedium - Layout.rightMargin: Nheko.paddingMedium - height: control.controlHeight + Layout.leftMargin: Nheko.paddingSmall + Layout.rightMargin: Nheko.paddingSmall + + enabled: control.mediaLoaded + value: control.positionValue onMoved: control.position = value from: 0 to: control.duration - sliderHeight: 8 alwaysShowSlider: false } - Rectangle { - id: controlRect - // Window color with 128/255 alpha - color: { - var wc = Nheko.colors.alternateBase; - return Qt.rgba(wc.r, wc.g, wc.b, 0.5); - } - - Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom - - height: 35 + RowLayout { + Layout.margins: Nheko.paddingSmall + spacing: Nheko.paddingSmall Layout.fillWidth: true - RowLayout { - anchors.left: controlRect.left - anchors.bottom: controlRect.bottom - anchors.right: controlRect.right - anchors.margins: Nheko.paddingSmall - anchors.verticalCenter: controlRect.verticalCenter - spacing: Nheko.paddingSmall + // Cache/Play/pause button + ImageButton { + Layout.alignment: Qt.AlignLeft + id: playbackStateImage - // Cache/Play/pause button - Image { - Layout.alignment: Qt.AlignLeft - id: playbackStateImage + buttonTextColor: Nheko.colors.text + Layout.preferredHeight: 24 + Layout.preferredWidth: 24 - property color controlColor: (playbackStateArea.containsMouse) ? Nheko.colors.highlight : Nheko.colors.text - - fillMode: Image.PreserveAspectFit - Layout.preferredHeight: control.controlHeight - source: { - if (control.mediaLoaded) { - if (control.mediaState == MediaPlayer.PlayingState) - return "image://colorimage/:/icons/icons/ui/pause-symbol.png?" + controlColor; - else - return "image://colorimage/:/icons/icons/ui/play-sign.png?" + controlColor; - } else { - return "image://colorimage/:/icons/icons/ui/arrow-pointing-down.png?" + controlColor; - } + image: { + if (control.mediaLoaded) { + if (control.mediaState == MediaPlayer.PlayingState) + return ":/icons/icons/ui/pause-symbol.png"; + else + return ":/icons/icons/ui/play-sign.png"; + } else { + return ":/icons/icons/ui/arrow-pointing-down.png"; } + } - MouseArea { - id: playbackStateArea + onClicked: control.mediaLoaded ? control.playPauseActivated() : control.loadActivated(); - anchors.fill: parent - hoverEnabled: true - onClicked: { - control.mediaLoaded ? control.playPauseActivated(mouseX, mouseY) : control.loadActivated(mouseX, mouseY); - } + } + + ImageButton { + Layout.alignment: Qt.AlignLeft + id: volumeButton + + buttonTextColor: Nheko.colors.text + Layout.preferredHeight: 24 + Layout.preferredWidth: 24 + + image: { + if (control.muted || control.desiredVolume <= 0) { + return ":/icons/icons/ui/volume-off-indicator.png"; + } else { + return ":/icons/icons/ui/volume-up.png"; } - } - VolumeControl { - Layout.alignment: Qt.AlignLeft - id: volumeSlider - orientation: Qt.Horizontal - Layout.rightMargin: 5 - Layout.preferredHeight: control.controlHeight + onClicked: control.muted = !control.muted + + } + + NhekoSlider { + state: "" + + states: State { + name: "shown" + when: Settings.mobileMode || volumeButton.hovered || volumeSlider.hovered || volumeSlider.pressed + PropertyChanges {target: volumeSlider; Layout.preferredWidth: 100} + PropertyChanges {target: volumeSlider; opacity: 1} } - Label { - Layout.alignment: Qt.AlignRight + Layout.alignment: Qt.AlignLeft + Layout.preferredWidth: 0 + opacity: 0 + id: volumeSlider + orientation: Qt.Horizontal + property real desiredVolume: QtMultimedia.convertVolume(volumeSlider.value, QtMultimedia.LogarithmicVolumeScale, QtMultimedia.LinearVolumeScale) + value: 1 - text: (!control.mediaLoaded) ? "-/-" : (durationToString(control.positionValue) + "/" + durationToString(control.duration)) - color: Nheko.colors.text + onDesiredVolumeChanged: { + control.muted = !(desiredVolume > 0); } - Item { - Layout.fillWidth: true - } + transitions: [ + Transition { + from: "" + to: "shown" + SequentialAnimation { + PauseAnimation { duration: 50 } + NumberAnimation { + duration: 100 + properties: "opacity" + easing.type: Easing.InQuad + } + } + + NumberAnimation { + properties: "Layout.preferredWidth" + duration: 150 + } + }, + Transition { + from: "shown" + to: "" + + SequentialAnimation { + PauseAnimation { duration: 100 } + + ParallelAnimation { + NumberAnimation { + duration: 100 + properties: "opacity" + easing.type: Easing.InQuad + } + + NumberAnimation { + properties: "Layout.preferredWidth" + duration: 150 + } + } + } + + } + ] + } + + Label { + Layout.alignment: Qt.AlignRight + + text: (!control.mediaLoaded) ? "-- / --" : (durationToString(control.positionValue) + " / " + durationToString(control.duration)) + color: Nheko.colors.text + } + + Item { + Layout.fillWidth: true } } + // Fade controls in/out Behavior on opacity { OpacityAnimator { diff --git a/resources/qml/ui/media/VolumeControl.qml b/resources/qml/ui/media/VolumeControl.qml deleted file mode 100644 index e87550ac..00000000 --- a/resources/qml/ui/media/VolumeControl.qml +++ /dev/null @@ -1,117 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Nheko Contributors -// -// SPDX-License-Identifier: GPL-3.0-or-later - -import "../" - -import QtMultimedia 5.15 -import QtQuick 2.15 -import QtQuick.Controls 2.15 - -import im.nheko 1.0 - -// Volume slider activator -Image { - // TODO: add icons for different volume levels - id: volumeImage - - property alias desiredVolume: volumeSlider.desiredVolume - property alias orientation: volumeSlider.orientation - property alias controlsVisible: volumeSliderRect.visible - property bool muted: false - property color controlColor: (volumeImageArea.containsMouse) ? Nheko.colors.highlight : Nheko.colors.text - width: sourceSize.width + volumeSliderRect.implicitWidth - - source: (desiredVolume > 0 && !muted) ? "image://colorimage/:/icons/icons/ui/volume-up.png?" + controlColor : "image://colorimage/:/icons/icons/ui/volume-off-indicator.png?" + controlColor - fillMode: Image.PreserveAspectFit - - MouseArea { - id: volumeImageArea - - anchors.fill: parent - hoverEnabled: true - onExited: volumeSliderHideTimer.start() - onPositionChanged: volumeSliderHideTimer.start() - onClicked: volumeImage.muted = !volumeImage.muted - - // For hiding volume slider after a while - Timer { - id: volumeSliderHideTimer - - interval: 1500 - repeat: false - running: false - } - - } - - Rectangle { - id: volumeSliderRect - - opacity: (visible) ? 1 : 0 - anchors.bottom: volumeSlider.orientation == Qt.Vertical ? volumeImage.top : undefined - anchors.left: volumeSlider.orientation == Qt.Vertical ? undefined : volumeImage.right - anchors.horizontalCenter: volumeSlider.orientation == Qt.Vertical ? volumeImage.horizontalCenter : undefined - anchors.verticalCenter: volumeSlider.orientation == Qt.Vertical ? undefined : volumeImage.verticalCenter - color: { - if (volumeSlider.orientation == Qt.Vertical) { - var wc = Nheko.colors.window; - return Qt.rgba(wc.r, wc.g, wc.b, 0.5); - } else { - return "transparent"; - } - } - /* TODO: base width on the slider width (some issue with it not having a geometry - when using the width here?) */ - width: volumeSlider.orientation == Qt.Vertical ? volumeImage.width * 0.7 : 100 - radius: volumeSlider.width / 2 - height: volumeSlider.orientation == Qt.Vertical ? 100 : volumeImage.height * 0.7 - visible: volumeImageArea.containsMouse || volumeSliderHideTimer.running || volumeSliderRectMouseArea.containsMouse - - NhekoSlider { - // TODO: the slider is slightly off-center on the left for some reason... - id: volumeSlider - - sliderWidth: 8 - sliderHeight: 8 - // Desired value to avoid loop onMoved -> media.volume -> value -> onMoved... - property real desiredVolume: QtMultimedia.convertVolume(volumeSlider.value, QtMultimedia.LogarithmicVolumeScale, QtMultimedia.LinearVolumeScale) - - value: 1 - anchors.fill: volumeSliderRect - anchors.horizontalCenter: orientation == Qt.Vertical ? volumeSliderRect.horizontalCenter : undefined - anchors.verticalCenter: orientation == Qt.Vertical ? undefined : volumeSliderRect.verticalCenter - orientation: Qt.Vertical - onDesiredVolumeChanged: { - volumeImage.muted = !(desiredVolume > 0); - } - } - // Used for resetting the timer on mouse moves on volumeSliderRect - - MouseArea { - id: volumeSliderRectMouseArea - - anchors.fill: parent - hoverEnabled: true - propagateComposedEvents: true - onExited: volumeSliderHideTimer.start() - onClicked: mouse.accepted = false - onPressed: mouse.accepted = false - onReleased: mouse.accepted = false - onPressAndHold: mouse.accepted = false - onPositionChanged: { - mouse.accepted = false; - volumeSliderHideTimer.start(); - } - } - - Behavior on opacity { - OpacityAnimator { - duration: 100 - } - - } - - } - -} diff --git a/resources/res.qrc b/resources/res.qrc index 4e243251..a60f4ab0 100644 --- a/resources/res.qrc +++ b/resources/res.qrc @@ -185,7 +185,6 @@ qml/ui/Spinner.qml qml/ui/animations/BlinkAnimation.qml qml/ui/media/MediaControls.qml - qml/ui/media/VolumeControl.qml qml/voip/ActiveCallBar.qml qml/voip/CallDevices.qml qml/voip/CallInvite.qml