Cleanup positioning of player elements

pull/812/head
Nicolas Werner 3 years ago
parent c5e8b2da15
commit ffc60180de
No known key found for this signature in database
GPG Key ID: C8D75E610773F2D9
  1. 42
      resources/qml/delegates/PlayableMediaMessage.qml
  2. 79
      resources/qml/ui/NhekoSlider.qml
  3. 196
      resources/qml/ui/media/MediaControls.qml
  4. 117
      resources/qml/ui/media/VolumeControl.qml
  5. 1
      resources/res.qrc

@ -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

@ -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
}
}

@ -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);
RowLayout {
Layout.margins: Nheko.paddingSmall
spacing: Nheko.paddingSmall
Layout.fillWidth: true
// Cache/Play/pause button
ImageButton {
Layout.alignment: Qt.AlignLeft
id: playbackStateImage
buttonTextColor: Nheko.colors.text
Layout.preferredHeight: 24
Layout.preferredWidth: 24
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";
}
}
onClicked: control.mediaLoaded ? control.playPauseActivated() : control.loadActivated();
}
Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom
ImageButton {
Layout.alignment: Qt.AlignLeft
id: volumeButton
height: 35
Layout.fillWidth: true
buttonTextColor: Nheko.colors.text
Layout.preferredHeight: 24
Layout.preferredWidth: 24
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
Image {
Layout.alignment: Qt.AlignLeft
id: playbackStateImage
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.muted || control.desiredVolume <= 0) {
return ":/icons/icons/ui/volume-off-indicator.png";
} else {
return ":/icons/icons/ui/volume-up.png";
}
}
MouseArea {
id: playbackStateArea
onClicked: control.muted = !control.muted
anchors.fill: parent
hoverEnabled: true
onClicked: {
control.mediaLoaded ? control.playPauseActivated(mouseX, mouseY) : control.loadActivated(mouseX, mouseY);
}
}
}
}
NhekoSlider {
state: ""
VolumeControl {
Layout.alignment: Qt.AlignLeft
id: volumeSlider
orientation: Qt.Horizontal
Layout.rightMargin: 5
Layout.preferredHeight: control.controlHeight
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 {

@ -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
}
}
}
}

@ -185,7 +185,6 @@
<file>qml/ui/Spinner.qml</file>
<file>qml/ui/animations/BlinkAnimation.qml</file>
<file>qml/ui/media/MediaControls.qml</file>
<file>qml/ui/media/VolumeControl.qml</file>
<file>qml/voip/ActiveCallBar.qml</file>
<file>qml/voip/CallDevices.qml</file>
<file>qml/voip/CallInvite.qml</file>

Loading…
Cancel
Save