Kirigami-addons

VideoMaximizeDelegate.qml
1// SPDX-FileCopyrightText: 2023 James Graham <james.h.graham@protonmail.com>
2// SPDX-License-Identifier: LGPL-2.0-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
3
4import QtQuick
5import QtQuick.Controls as QQC2
6import QtQuick.Layouts
7import QtMultimedia
8
9import org.kde.kirigami as Kirigami
10
11Item {
12 id: root
13
14 /**
15 * @brief The source for the image to be viewed.
16 */
17 required property string source
18
19 /**
20 * @brief Source for the temporary content.
21 *
22 * Typically used when downloading the image to show a thumbnail or other
23 * temporary image while the main image downloads.
24 */
25 required property string tempSource
26
27 /**
28 * @brief The size of the source image.
29 *
30 * This is used to calculate the maximum size of the content and temporary image.
31 */
32 required property real sourceWidth
33
34 /**
35 * @brief The size of the source image.
36 *
37 * This is used to calculate the maximum size of the content and temporary image.
38 */
39 required property real sourceHeight
40
41 /**
42 * @brief The caption for the item.
43 *
44 * Typically set to the filename if no caption is available.
45 *
46 * @note Declared here so that parent components can access this parameter
47 * when used as a listView delegate.
48 */
49 required property string caption
50
51 /**
52 * @brief The delegate type for this item.
53 *
54 * @note Declared here so that parent components can access this parameter
55 * when used as a listView delegate.
56 */
57 readonly property int type: AlbumModelItem.Video
58
59 /**
60 * @brief Whether the source video should auto-load.
61 *
62 * @deprecated due to changes in the Video API this will be removed in KF6. It
63 * currently does nothing but is kept to avoid breakage. The loss
64 * of this API has been worked around in a way that doesn't break KF5.
65 */
66 property bool autoLoad
67
68 /**
69 * @brief Whether the source video should auto-play.
70 */
71 property bool autoPlay
72
73 /**
74 * @brief The default action triggered when the download button is pressed.
75 *
76 * This exists as a property so that the action can be overridden. The most common
77 * use case for this is where a custom URI scheme is used.
78 */
79 property DownloadAction downloadAction: DownloadAction {
80 onTriggered: videoItem.play()
81 }
82
83 /**
84 * @brief The default action triggered when the play button is pressed.
85 *
86 * This exists as a property so that the action can be overridden. For example
87 * if you want to be able to interface with a media manager.
88 */
89 property Kirigami.Action playAction: Kirigami.Action {
90 onTriggered: videoItem.play()
91 }
92
93 /**
94 * @brief The default action triggered when the pause button is pressed.
95 *
96 * This exists as a property so that the action can be overridden. For example
97 * if you want to be able to interface with a media manager.
98 */
99 property Kirigami.Action pauseAction: Kirigami.Action {
100 onTriggered: videoItem.pause()
101 }
102
103 /**
104 * @brief The playback state of the media.
105 */
106 property alias playbackState: videoItem.playbackState
107
108 /**
109 * @brief The padding around the content image.
110 *
111 * The padding is factored in when calculating the maximum size of the content
112 * image.
113 */
114 property var padding: Kirigami.Units.largeSpacing
115
116 /**
117 * @brief Multiple by which the image is scaled.
118 */
119 property var scaleFactor: 1
120
121 /**
122 * @brief Emitted when the background space around the content item is clicked.
123 */
124 signal backgroundClicked()
125
126 /**
127 * @brief Emitted when the content image is right clicked.
128 */
129 signal itemRightClicked()
130
131 /**
132 * @brief Start media playback.
133 */
134 function play() {
135 videoItem.play()
136 }
137
138 /**
139 * @brief Pause media playback.
140 */
141 function pause() {
142 videoItem.pause()
143 }
144
145 clip: true
146
147 Video {
148 id: videoItem
149
150 anchors.centerIn: parent
151 width: {
152 if (root.sourceWidth > 0 ) {
153 return Math.min(root.sourceWidth, root.width - root.padding * 2)
154 } else if (metaData.resolution && metaData.resolution.width) {
155 return Math.min(metaData.resolution.width, root.width - root.padding * 2)
156 } else {
157 return 0
158 }
159 }
160 height: {
161 if (root.sourceHeight > 0 ) {
162 return Math.min(root.sourceHeight, root.height - root.padding * 2)
163 } else if (metaData.resolution && metaData.resolution.height) {
164 return Math.min(metaData.resolution.height, root.height - root.padding * 2)
165 } else {
166 return 0
167 }
168 }
169
170 source: root.source
171 onSourceChanged: {
172 if (source.toString().length > 0 && root.autoPlay && root.index === root.ListView.view.currentIndex) {
173 root.playAction.trigger()
174 }
175 }
176
177 clip: true
178
179 Behavior on width {
180 NumberAnimation {duration: Kirigami.Units.longDuration; easing.type: Easing.InOutCubic}
181 }
182 Behavior on height {
183 NumberAnimation {duration: Kirigami.Units.longDuration; easing.type: Easing.InOutCubic}
184 }
185
186 Image {
187 id: tempImage
188 anchors.centerIn: parent
189 width: root.sourceWidth > 0 || (videoItem.metaData.resolution && videoItem.metaData.resolution.width > 0) ? root.sourceWidth : tempImage.sourceSize.width
190 height: root.sourceHeight > 0 || (videoItem.metaData.resolution && videoItem.metaData.resolution.height > 0) ? root.sourceHeight : tempImage.sourceSize.height
191 visible: source && status === Image.Ready && !videoItem.source.toString().length > 0
192
193 source: root.tempSource
194 }
195
196 QQC2.ProgressBar {
197 anchors.centerIn: parent
198 visible: root.downloadAction.started && !root.downloadAction.completed
199 width: videoItem.width * 0.8
200
201 from: 0.0
202 to: 100.0
203 value: root.downloadAction.progress
204 }
205
206 QQC2.Button {
207 anchors.centerIn: parent
208 icon.width: Kirigami.Units.iconSizes.large
209 icon.height: Kirigami.Units.iconSizes.large
210 visible: !videoItem.source.toString().length > 0 && !root.downloadAction.started
211 display: QQC2.AbstractButton.IconOnly
212 action: root.downloadAction
213 }
214
215 transform: [
216 Scale {
217 origin.x: videoItem.width / 2
218 origin.y: videoItem.height / 2
219 xScale: root.scaleFactor
220 yScale: root.scaleFactor
221
222 Behavior on xScale {
223 NumberAnimation {duration: Kirigami.Units.longDuration; easing.type: Easing.InOutCubic}
224 }
225 Behavior on yScale {
226 NumberAnimation {duration: Kirigami.Units.longDuration; easing.type: Easing.InOutCubic}
227 }
228 }
229 ]
230
231 QQC2.Control {
232 id: videoControls
233 anchors.bottom: videoItem.bottom
234 anchors.left: videoItem.left
235 anchors.right: videoItem.right
236 visible: videoArea.hovered || volumePopupHoverHandler.hovered || volumeSlider.hovered || videoControlTimer.running
237
238 contentItem: RowLayout {
239 id: controlRow
240 QQC2.ToolButton {
241 id: playButton
242 z: 1
243 icon.name: videoItem.playbackState === MediaPlayer.PlayingState ? "media-playback-pause" : "media-playback-start"
244 action: videoItem.playbackState === MediaPlayer.PlayingState ? root.pauseAction : root.playAction
245 }
246 QQC2.Slider {
247 Layout.fillWidth: true
248 from: 0
249 to: videoItem.duration
250 value: videoItem.position
251 onMoved: videoItem.seek(value)
252 }
253 QQC2.Label {
254 text: root.getTimeString(videoItem.position) + "/" + root.getTimeString(videoItem.duration)
255 }
256 QQC2.ToolButton {
257 id: volumeButton
258 property var unmuteVolume: videoItem.volume
259
260 icon.name: videoItem.volume <= 0 ? "player-volume-muted" : "player-volume"
261
262 QQC2.ToolTip.visible: hovered
263 QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
264 QQC2.ToolTip.timeout: Kirigami.Units.toolTipDelay
265 QQC2.ToolTip.text: i18ndc("kirigami-addons6", "@action:button", "Volume")
266
267 onClicked: {
268 if (videoItem.volume > 0) {
269 videoItem.volume = 0
270 } else {
271 if (unmuteVolume === 0) {
272 videoItem.volume = 1
273 } else {
274 videoItem.volume = unmuteVolume
275 }
276 }
277 }
278 onHoveredChanged: {
279 if (!hovered) {
280 videoControlTimer.restart()
281 volumePopupTimer.restart()
282 }
283 }
284
285 QQC2.Popup {
286 id: volumePopup
287 y: -height
288 width: volumeButton.width
289 visible: volumeButton.hovered || volumePopupHoverHandler.hovered || volumeSlider.hovered || volumePopupTimer.running
290
291 focus: true
292 padding: Kirigami.Units.smallSpacing
293 closePolicy: QQC2.Popup.NoAutoClose
294
295 QQC2.Slider {
296 id: volumeSlider
297 anchors.centerIn: parent
298 implicitHeight: Kirigami.Units.gridUnit * 7
299 orientation: Qt.Vertical
300 padding: 0
301 from: 0
302 to: 1
303 value: videoItem.volume
304 onMoved: {
305 videoItem.volume = value
306 volumeButton.unmuteVolume = value
307 }
308 onHoveredChanged: {
309 if (!hovered) {
310 videoControlTimer.restart()
311 volumePopupTimer.restart()
312 }
313 }
314 }
315 Timer {
316 id: volumePopupTimer
317 interval: 500
318 }
319 HoverHandler {
320 id: volumePopupHoverHandler
321 onHoveredChanged: {
322 if (!hovered) {
323 videoControlTimer.restart()
324 volumePopupTimer.restart()
325 }
326 }
327 }
328 background: Kirigami.ShadowedRectangle {
329 radius: 4
330 color: Kirigami.Theme.backgroundColor
331 opacity: 0.8
332
333 border.color: Kirigami.ColorUtils.linearInterpolation(Kirigami.Theme.backgroundColor, Kirigami.Theme.textColor, Kirigami.Theme.frameContrast)
334 border.width: 1
335
336 shadow.xOffset: 0
337 shadow.yOffset: 4
338 shadow.color: Qt.rgba(0, 0, 0, 0.3)
339 shadow.size: 8
340 }
341 }
342 }
343 }
344 background: Kirigami.ShadowedRectangle {
345 radius: 4
346 color: Kirigami.Theme.backgroundColor
347 opacity: 0.8
348
349 border.color: Kirigami.ColorUtils.linearInterpolation(Kirigami.Theme.backgroundColor, Kirigami.Theme.textColor, Kirigami.Theme.frameContrast)
350 border.width: 1
351
352 shadow.xOffset: 0
353 shadow.yOffset: 4
354 shadow.color: Qt.rgba(0, 0, 0, 0.3)
355 shadow.size: 8
356 }
357 }
358 Timer {
359 id: videoControlTimer
360 interval: 1000
361 }
362
363 TapHandler {
364 acceptedButtons: Qt.RightButton
365 onTapped: root.itemRightClicked()
366 }
367 HoverHandler {
368 id: videoArea
369 onHoveredChanged: {
370 if (!hovered) {
371 videoControlTimer.restart()
372 }
373 }
374 }
375 }
376 TapHandler {
377 acceptedButtons: Qt.LeftButton
378 onTapped: root.backgroundClicked()
379 }
380
381 function formatTimer(time){
382 return (time < 10 ? "0" : "") + time
383 }
384
385 function getTimeString(timeMilliseconds){
386 let hours = root.formatTimer(Math.floor(timeMilliseconds/3600000));
387 let minutes = root.formatTimer(Math.floor(timeMilliseconds%3600000/60000));
388 let seconds = root.formatTimer(Math.floor(timeMilliseconds%60000/1000));
389
390 return (hours + ":" + minutes + ":" + seconds);
391 }
392}
Q_SCRIPTABLE CaptureState status()
QString i18ndc(const char *domain, const char *context, const char *text, const TYPE &arg...)
char * toString(const EngineQuery &query)
KDOCTOOLS_EXPORT QString transform(const QString &file, const QString &stylesheet, const QList< const char * > &params=QList< const char * >())
QString left(qsizetype n) const const
QString right(qsizetype n) const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri May 3 2024 11:46:57 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.