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 Component.onDestruction: videoItem.stop()
178
179 clip: true
180
181 Behavior on width {
182 NumberAnimation {duration: Kirigami.Units.longDuration; easing.type: Easing.InOutCubic}
183 }
184 Behavior on height {
185 NumberAnimation {duration: Kirigami.Units.longDuration; easing.type: Easing.InOutCubic}
186 }
187
188 Image {
189 id: tempImage
190 anchors.centerIn: parent
191 width: root.sourceWidth > 0 || (videoItem.metaData.resolution && videoItem.metaData.resolution.width > 0) ? root.sourceWidth : tempImage.sourceSize.width
192 height: root.sourceHeight > 0 || (videoItem.metaData.resolution && videoItem.metaData.resolution.height > 0) ? root.sourceHeight : tempImage.sourceSize.height
193 visible: source && status === Image.Ready && !videoItem.source.toString().length > 0
194
195 source: root.tempSource
196 }
197
198 QQC2.ProgressBar {
199 anchors.centerIn: parent
200 visible: root.downloadAction.started && !root.downloadAction.completed
201 width: videoItem.width * 0.8
202
203 from: 0.0
204 to: 100.0
205 value: root.downloadAction.progress
206 }
207
208 QQC2.Button {
209 anchors.centerIn: parent
210 icon.width: Kirigami.Units.iconSizes.large
211 icon.height: Kirigami.Units.iconSizes.large
212 visible: !videoItem.source.toString().length > 0 && !root.downloadAction.started
213 display: QQC2.AbstractButton.IconOnly
214 action: root.downloadAction
215 }
216
217 transform: [
218 Scale {
219 origin.x: videoItem.width / 2
220 origin.y: videoItem.height / 2
221 xScale: root.scaleFactor
222 yScale: root.scaleFactor
223
224 Behavior on xScale {
225 NumberAnimation {duration: Kirigami.Units.longDuration; easing.type: Easing.InOutCubic}
226 }
227 Behavior on yScale {
228 NumberAnimation {duration: Kirigami.Units.longDuration; easing.type: Easing.InOutCubic}
229 }
230 }
231 ]
232
233 QQC2.Control {
234 id: videoControls
235 anchors.bottom: videoItem.bottom
236 anchors.left: videoItem.left
237 anchors.right: videoItem.right
238 visible: videoArea.hovered || volumePopupHoverHandler.hovered || volumeSlider.hovered || videoControlTimer.running
239
240 contentItem: RowLayout {
241 id: controlRow
242 QQC2.ToolButton {
243 id: playButton
244 z: 1
245 icon.name: videoItem.playbackState === MediaPlayer.PlayingState ? "media-playback-pause" : "media-playback-start"
246 action: videoItem.playbackState === MediaPlayer.PlayingState ? root.pauseAction : root.playAction
247 }
248 QQC2.Slider {
249 Layout.fillWidth: true
250 from: 0
251 to: videoItem.duration
252 value: videoItem.position
253 onMoved: videoItem.seek(value)
254 }
255 QQC2.Label {
256 text: root.getTimeString(videoItem.position) + "/" + root.getTimeString(videoItem.duration)
257 }
258 QQC2.ToolButton {
259 id: volumeButton
260 property var unmuteVolume: videoItem.volume
261
262 icon.name: videoItem.volume <= 0 ? "player-volume-muted" : "player-volume"
263
264 QQC2.ToolTip.visible: hovered
265 QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
266 QQC2.ToolTip.timeout: Kirigami.Units.toolTipDelay
267 QQC2.ToolTip.text: i18ndc("kirigami-addons6", "@action:button", "Volume")
268
269 onClicked: {
270 if (videoItem.volume > 0) {
271 videoItem.volume = 0
272 } else {
273 if (unmuteVolume === 0) {
274 videoItem.volume = 1
275 } else {
276 videoItem.volume = unmuteVolume
277 }
278 }
279 }
280 onHoveredChanged: {
281 if (!hovered) {
282 videoControlTimer.restart()
283 volumePopupTimer.restart()
284 }
285 }
286
287 QQC2.Popup {
288 id: volumePopup
289 y: -height
290 width: volumeButton.width
291 visible: volumeButton.hovered || volumePopupHoverHandler.hovered || volumeSlider.hovered || volumePopupTimer.running
292
293 focus: true
294 padding: Kirigami.Units.smallSpacing
295 closePolicy: QQC2.Popup.NoAutoClose
296
297 QQC2.Slider {
298 id: volumeSlider
299 anchors.centerIn: parent
300 implicitHeight: Kirigami.Units.gridUnit * 7
301 orientation: Qt.Vertical
302 padding: 0
303 from: 0
304 to: 1
305 value: videoItem.volume
306 onMoved: {
307 videoItem.volume = value
308 volumeButton.unmuteVolume = value
309 }
310 onHoveredChanged: {
311 if (!hovered) {
312 videoControlTimer.restart()
313 volumePopupTimer.restart()
314 }
315 }
316 }
317 Timer {
318 id: volumePopupTimer
319 interval: 500
320 }
321 HoverHandler {
322 id: volumePopupHoverHandler
323 onHoveredChanged: {
324 if (!hovered) {
325 videoControlTimer.restart()
326 volumePopupTimer.restart()
327 }
328 }
329 }
330 background: Kirigami.ShadowedRectangle {
331 radius: 4
332 color: Kirigami.Theme.backgroundColor
333 opacity: 0.8
334
335 border.color: Kirigami.ColorUtils.linearInterpolation(Kirigami.Theme.backgroundColor, Kirigami.Theme.textColor, Kirigami.Theme.frameContrast)
336 border.width: 1
337
338 shadow.xOffset: 0
339 shadow.yOffset: 4
340 shadow.color: Qt.rgba(0, 0, 0, 0.3)
341 shadow.size: 8
342 }
343 }
344 }
345 }
346 background: Kirigami.ShadowedRectangle {
347 radius: 4
348 color: Kirigami.Theme.backgroundColor
349 opacity: 0.8
350
351 border.color: Kirigami.ColorUtils.linearInterpolation(Kirigami.Theme.backgroundColor, Kirigami.Theme.textColor, Kirigami.Theme.frameContrast)
352 border.width: 1
353
354 shadow.xOffset: 0
355 shadow.yOffset: 4
356 shadow.color: Qt.rgba(0, 0, 0, 0.3)
357 shadow.size: 8
358 }
359 }
360 Timer {
361 id: videoControlTimer
362 interval: 1000
363 }
364
365 TapHandler {
366 acceptedButtons: Qt.RightButton
367 onTapped: root.itemRightClicked()
368 }
369 HoverHandler {
370 id: videoArea
371 onHoveredChanged: {
372 if (!hovered) {
373 videoControlTimer.restart()
374 }
375 }
376 }
377 }
378 TapHandler {
379 acceptedButtons: Qt.LeftButton
380 onTapped: root.backgroundClicked()
381 }
382
383 function formatTimer(time){
384 return (time < 10 ? "0" : "") + time
385 }
386
387 function getTimeString(timeMilliseconds){
388 let hours = root.formatTimer(Math.floor(timeMilliseconds/3600000));
389 let minutes = root.formatTimer(Math.floor(timeMilliseconds%3600000/60000));
390 let seconds = root.formatTimer(Math.floor(timeMilliseconds%60000/1000));
391
392 return (hours + ":" + minutes + ":" + seconds);
393 }
394}
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 Jul 26 2024 11:54:39 by doxygen 1.11.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.