Cleanup positioning of player elements

This commit is contained in:
Nicolas Werner 2021-11-11 19:18:45 +01:00
parent c5e8b2da15
commit ffc60180de
No known key found for this signature in database
GPG key ID: C8D75E610773F2D9
5 changed files with 173 additions and 260 deletions

View file

@ -10,7 +10,7 @@ import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15 import QtQuick.Layouts 1.15
import im.nheko 1.0 import im.nheko 1.0
ColumnLayout { Item {
id: content id: content
required property double proportionalHeight required property double proportionalHeight
@ -22,7 +22,13 @@ ColumnLayout {
required property string body required property string body
required property string filesize 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 { MxcMedia {
id: mxcmedia id: mxcmedia
@ -38,15 +44,10 @@ ColumnLayout {
Rectangle { Rectangle {
id: videoContainer 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" color: type == MtxEvent.VideoMessage ? Nheko.colors.window : "transparent"
Layout.preferredHeight: type == MtxEvent.VideoMessage ? tooHigh ? timelineRoot.height / divisor : tempHeight : 80 width: parent.width
Layout.preferredWidth: type == MtxEvent.VideoMessage ? tooHigh ? (timelineRoot.height / divisor) / proportionalHeight : tempWidth : 250 height: parent.height - fileInfoLabel.height
Image { Image {
anchors.fill: parent anchors.fill: parent
@ -65,14 +66,18 @@ ColumnLayout {
flushMode: VideoOutput.FirstFrame flushMode: VideoOutput.FirstFrame
} }
}
}
MediaControls { MediaControls {
id: mediaControls id: mediaControls
anchors.fill: parent anchors.left: content.left
x: type == MtxEvent.VideoMessage ? videoOutput.contentRect.x : videoContainer.x anchors.right: content.right
y: type == MtxEvent.VideoMessage ? videoOutput.contentRect.y : videoContainer.y anchors.bottom: fileInfoLabel.top
width: type == MtxEvent.VideoMessage ? videoOutput.contentRect.width : videoContainer.width
height: type == MtxEvent.VideoMessage ? videoOutput.contentRect.height : videoContainer.height
playingVideo: type == MtxEvent.VideoMessage playingVideo: type == MtxEvent.VideoMessage
positionValue: mxcmedia.position positionValue: mxcmedia.position
duration: mxcmedia.duration duration: mxcmedia.duration
@ -83,15 +88,12 @@ ColumnLayout {
onLoadActivated: mxcmedia.eventId = eventId onLoadActivated: mxcmedia.eventId = eventId
} }
}
}
// information about file name and file size // information about file name and file size
Label { Label {
id: fileInfoLabel id: fileInfoLabel
Layout.fillWidth: true anchors.bottom: content.bottom
text: body + " [" + filesize + "]" text: body + " [" + filesize + "]"
textFormat: Text.PlainText textFormat: Text.PlainText
elide: Text.ElideRight elide: Text.ElideRight

View file

@ -7,71 +7,42 @@ import QtQuick.Controls 2.15
import im.nheko 1.0 import im.nheko 1.0
Slider { Slider {
id: slider id: control
value: 0
property real sliderWidth property color progressColor: Nheko.colors.highlight
property real sliderHeight
property bool alwaysShowSlider: true property bool alwaysShowSlider: true
property int sliderRadius: 16
implicitHeight: sliderRadius
anchors.bottomMargin: orientation == Qt.Vertical ? Nheko.paddingMedium : undefined padding: 0
anchors.topMargin: orientation == Qt.Vertical ? Nheko.paddingMedium : undefined
anchors.leftMargin: orientation == Qt.Vertical ? undefined : Nheko.paddingMedium
anchors.rightMargin: orientation == Qt.Vertical ? undefined : Nheko.paddingMedium
background: Rectangle { background: Rectangle {
x: slider.leftPadding + (slider.orientation == Qt.Vertical ? slider.availableWidth / 2 - width / 2 : 0) x: control.leftPadding + handle.width/2
y: slider.topPadding + (slider.orientation == Qt.Vertical ? 0 : slider.availableHeight / 2 - height / 2) y: control.topPadding + control.availableHeight / 2 - height / 2
// implicitWidth: slider.orientation == Qt.Vertical ? 8 : 100 implicitWidth: 200
// implicitHeight: slider.orientation == Qt.Vertical ? 100 : 8 implicitHeight: control.sliderRadius/4
width: slider.orientation == Qt.Vertical ? sliderWidth : slider.availableWidth width: control.availableWidth - handle.width
height: slider.orientation == Qt.Vertical ? slider.availableHeight : sliderHeight height: implicitHeight
radius: 2 radius: height/2
color: { color: Nheko.colors.buttonText
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);
}
Rectangle { Rectangle {
width: slider.orientation == Qt.Vertical ? parent.width : slider.visualPosition * parent.width width: control.visualPosition * parent.width
height: slider.orientation == Qt.Vertical ? slider.visualPosition * parent.height : parent.height height: parent.height
color: { color: control.progressColor
if (slider.orientation == Qt.Vertical) {
return Nheko.colors.buttonText;
} else {
return Nheko.colors.highlight;
}
}
radius: 2 radius: 2
} }
} }
handle: Rectangle { handle: Rectangle {
x: { x: control.leftPadding + control.visualPosition * background.width
if (slider.orientation == Qt.Vertical) y: control.topPadding + control.availableHeight / 2 - height / 2
return slider.leftPadding + slider.availableWidth / 2 - width / 2; implicitWidth: control.sliderRadius
else implicitHeight: control.sliderRadius
return slider.leftPadding + slider.visualPosition * (slider.availableWidth - width); radius: control.sliderRadius/2
} color: control.progressColor
y: { visible: Settings.mobileMode || control.alwaysShowSlider || control.hovered || control.pressed
if (slider.orientation == Qt.Vertical) border.color: control.progressColor
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
} }
} }

View file

@ -3,28 +3,33 @@
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
import "../" import "../"
import "../../"
import QtMultimedia 5.15 import QtMultimedia 5.15
import QtQuick 2.15 import QtQuick 2.15
import QtQuick.Controls 2.15 import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15 import QtQuick.Layouts 1.15
import im.nheko 1.0 import im.nheko 1.0
Item { Rectangle {
id: control id: control
property alias desiredVolume: volumeSlider.desiredVolume property alias desiredVolume: volumeSlider.desiredVolume
property alias muted: volumeSlider.muted property bool muted: false
property bool playingVideo: false property bool playingVideo: false
property var mediaState property var mediaState
property bool mediaLoaded: false property bool mediaLoaded: false
property var duration property var duration
property var positionValue: 0 property var positionValue: 0
property var position property var position
property int controlHeight: 25
property bool shouldShowControls: !playingVideo || playerMouseArea.shouldShowControls || volumeSlider.controlsVisible 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 playPauseActivated()
signal loadActivated(real mouseX, real mouseY) signal loadActivated()
function durationToString(duration) { function durationToString(duration) {
function maybeZeroPrepend(time) { function maybeZeroPrepend(time) {
@ -51,7 +56,7 @@ Item {
property bool shouldShowControls: (containsMouse && controlHideTimer.running) || (control.mediaState != MediaPlayer.PlayingState) || controlLayout.contains(mapToItem(controlLayout, mouseX, mouseY)) property bool shouldShowControls: (containsMouse && controlHideTimer.running) || (control.mediaState != MediaPlayer.PlayingState) || controlLayout.contains(mapToItem(controlLayout, mouseX, mouseY))
onClicked: { onClicked: {
control.mediaLoaded ? control.playPauseActivated(mouseX, mouseY) : control.loadActivated(mouseX, mouseY); control.mediaLoaded ? control.playPauseActivated() : control.loadActivated();
} }
hoverEnabled: true hoverEnabled: true
onPositionChanged: controlHideTimer.start() onPositionChanged: controlHideTimer.start()
@ -66,102 +71,155 @@ Item {
id: controlLayout id: controlLayout
opacity: control.shouldShowControls ? 1 : 0 opacity: control.shouldShowControls ? 1 : 0
// spacing: Nheko.paddingSmall spacing: 0
anchors.bottom: control.bottom anchors.bottom: control.bottom
anchors.left: control.left anchors.left: control.left
anchors.right: control.right anchors.right: control.right
NhekoSlider { NhekoSlider {
Layout.fillWidth: true Layout.fillWidth: true
Layout.minimumWidth: 50 Layout.leftMargin: Nheko.paddingSmall
Layout.leftMargin: Nheko.paddingMedium Layout.rightMargin: Nheko.paddingSmall
Layout.rightMargin: Nheko.paddingMedium
height: control.controlHeight enabled: control.mediaLoaded
value: control.positionValue value: control.positionValue
onMoved: control.position = value onMoved: control.position = value
from: 0 from: 0
to: control.duration to: control.duration
sliderHeight: 8
alwaysShowSlider: false alwaysShowSlider: false
} }
Rectangle {
id: controlRect
// Window color with 128/255 alpha RowLayout {
color: { Layout.margins: Nheko.paddingSmall
var wc = Nheko.colors.alternateBase; spacing: Nheko.paddingSmall
return Qt.rgba(wc.r, wc.g, wc.b, 0.5);
}
Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom
height: 35
Layout.fillWidth: true Layout.fillWidth: true
RowLayout { // Cache/Play/pause button
anchors.left: controlRect.left ImageButton {
anchors.bottom: controlRect.bottom Layout.alignment: Qt.AlignLeft
anchors.right: controlRect.right id: playbackStateImage
anchors.margins: Nheko.paddingSmall
anchors.verticalCenter: controlRect.verticalCenter
spacing: Nheko.paddingSmall
// Cache/Play/pause button buttonTextColor: Nheko.colors.text
Image { Layout.preferredHeight: 24
Layout.alignment: Qt.AlignLeft Layout.preferredWidth: 24
id: playbackStateImage
property color controlColor: (playbackStateArea.containsMouse) ? Nheko.colors.highlight : Nheko.colors.text image: {
if (control.mediaLoaded) {
fillMode: Image.PreserveAspectFit if (control.mediaState == MediaPlayer.PlayingState)
Layout.preferredHeight: control.controlHeight return ":/icons/icons/ui/pause-symbol.png";
source: { else
if (control.mediaLoaded) { return ":/icons/icons/ui/play-sign.png";
if (control.mediaState == MediaPlayer.PlayingState) } else {
return "image://colorimage/:/icons/icons/ui/pause-symbol.png?" + controlColor; return ":/icons/icons/ui/arrow-pointing-down.png";
else
return "image://colorimage/:/icons/icons/ui/play-sign.png?" + controlColor;
} else {
return "image://colorimage/:/icons/icons/ui/arrow-pointing-down.png?" + controlColor;
}
} }
}
MouseArea { onClicked: control.mediaLoaded ? control.playPauseActivated() : control.loadActivated();
id: playbackStateArea
anchors.fill: parent }
hoverEnabled: true
onClicked: { ImageButton {
control.mediaLoaded ? control.playPauseActivated(mouseX, mouseY) : control.loadActivated(mouseX, mouseY); 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 { onClicked: control.muted = !control.muted
Layout.alignment: Qt.AlignLeft
id: volumeSlider }
orientation: Qt.Horizontal
Layout.rightMargin: 5 NhekoSlider {
Layout.preferredHeight: control.controlHeight 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.AlignLeft
Layout.alignment: Qt.AlignRight 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)) onDesiredVolumeChanged: {
color: Nheko.colors.text control.muted = !(desiredVolume > 0);
} }
Item { transitions: [
Layout.fillWidth: true 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 // Fade controls in/out
Behavior on opacity { Behavior on opacity {
OpacityAnimator { OpacityAnimator {

View file

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

View file

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