[vlc-commits] [Git][videolan/vlc][master] 8 commits: qml: add drop indicator to KeyNavigableListView
Steve Lhomme (@robUx4)
gitlab at videolan.org
Sat Aug 3 16:07:33 UTC 2024
Steve Lhomme pushed to branch master at VideoLAN / VLC
Commits:
e43b3626 by Fatih Uzunoglu at 2024-08-03T15:53:55+00:00
qml: add drop indicator to KeyNavigableListView
- - - - -
ce7c23ab by Fatih Uzunoglu at 2024-08-03T15:53:55+00:00
qt: add displaced and add animation to KeyNavigableListView
- - - - -
a45911eb by Fatih Uzunoglu at 2024-08-03T15:53:55+00:00
qml: set content width in KeyNavigableListView
- - - - -
96e2c197 by Fatih Uzunoglu at 2024-08-03T15:53:55+00:00
qml: enable dropping items to footer in KeyNavigableListView
- - - - -
a6c23257 by Fatih Uzunoglu at 2024-08-03T15:53:55+00:00
qml: accept drops to footer in PlaylistMedia
- - - - -
e77ed3e0 by Fatih Uzunoglu at 2024-08-03T15:53:55+00:00
qml: add drag auto scroll functionality in KeyNavigableListView
- - - - -
b852ff26 by Fatih Uzunoglu at 2024-08-03T15:53:55+00:00
qml: provide auto scroll functionality to KeyNavigableTableView
- - - - -
41cfa87f by Fatih Uzunoglu at 2024-08-03T15:53:55+00:00
qml: remove broken contentWidth adjustment in KeyNavigableTableView
- - - - -
5 changed files:
- modules/gui/qt/medialibrary/qml/PlaylistMedia.qml
- modules/gui/qt/playlist/qml/PlaylistListView.qml
- modules/gui/qt/widgets/qml/KeyNavigableListView.qml
- modules/gui/qt/widgets/qml/KeyNavigableTableView.qml
- modules/gui/qt/widgets/qml/TableViewDelegate.qml
Changes:
=====================================
modules/gui/qt/medialibrary/qml/PlaylistMedia.qml
=====================================
@@ -103,6 +103,17 @@ MainTableView {
sortModel: (availableRowWidth < VLCStyle.colWidth(4)) ? _modelSmall
: _modelMedium
+
+ listView.acceptDropFunc: function(index, drop) {
+ // FIXME: The DnD API seems quite poorly designed in this file.
+ // Why does it ask for both index and "before"
+ // When index + 1 is essentially the same as
+ // before being false?
+ // What is "delegate" and why is it passed in applyDrop()?
+ // We have to come up with a shim function here...
+ return applyDrop(drop, index - 1, null, false)
+ }
+
//---------------------------------------------------------------------------------------------
// Events
//---------------------------------------------------------------------------------------------
@@ -151,7 +162,7 @@ MainTableView {
//---------------------------------------------------------------------------------------------
// Drop interface
- function isDroppable(drop, index) {
+ listView.isDropAcceptableFunc: function(drop, index) {
if (drop.source === dragItem) {
return Helpers.itemsMovable(selectionModel.sortedSelectedIndexesFlat, index)
} else if (Helpers.isValidInstanceOf(drop.source, Widgets.DragItem)) {
@@ -164,9 +175,9 @@ MainTableView {
}
function applyDrop(drop, index, delegate, before) {
- if (root.isDroppable(drop, index + (before ? 0 : 1)) === false) {
+ if (listView.isDropAcceptableFunc(drop, index + (before ? 0 : 1)) === false) {
root.hideLine(delegate)
- return
+ return Promise.resolve()
}
const item = drop.source;
@@ -176,27 +187,31 @@ MainTableView {
// NOTE: Move implementation.
if (dragItem === item) {
model.move(selectionModel.selectedRows(), destinationIndex)
+ root.forceActiveFocus()
+ root.hideLine(delegate)
// NOTE: Dropping medialibrary content into the playlist.
} else if (Helpers.isValidInstanceOf(item, Widgets.DragItem)) {
- item.getSelectedInputItem()
- .then(inputItems => {
- model.insert(inputItems, destinationIndex)
- })
+ return item.getSelectedInputItem()
+ .then(inputItems => {
+ model.insert(inputItems, destinationIndex)
+ })
+ .then(() => { root.forceActiveFocus(); root.hideLine(delegate); })
} else if (drop.hasUrls) {
const urlList = []
for (let url in drop.urls)
urlList.push(drop.urls[url])
model.insert(urlList, destinationIndex)
- }
- root.forceActiveFocus()
+ root.forceActiveFocus()
+ root.hideLine(delegate)
+ }
- root.hideLine(delegate)
+ return Promise.resolve()
}
function _dropUpdatePosition(drag, index, delegate, before) {
- if (root.isDroppable(drag, index + (before ? 0 : 1)) === false) {
+ if (listView.isDropAcceptableFunc(drag, index + (before ? 0 : 1)) === false) {
root.hideLine(delegate)
return
}
=====================================
modules/gui/qt/playlist/qml/PlaylistListView.qml
=====================================
@@ -67,50 +67,6 @@ T.Pane {
enabled: root.enabled
}
- function isDropAcceptable(drop, index) {
- if (drop.source === dragItem)
- return Helpers.itemsMovable(selectionModel.sortedSelectedIndexesFlat, index)
- else if (Helpers.isValidInstanceOf(drop.source, Widgets.DragItem))
- return true
- else if (drop.hasUrls)
- return true
- else
- return false
- }
-
- function acceptDrop(index, drop) {
- const item = drop.source;
-
- // NOTE: Move implementation.
- if (dragItem === item) {
- model.moveItemsPre(root.selectionModel.sortedSelectedIndexesFlat, index);
- listView.forceActiveFocus();
- // NOTE: Dropping medialibrary content into the queue.
- } else if (Helpers.isValidInstanceOf(item, Widgets.DragItem)) {
- return item.getSelectedInputItem().then((inputItems) => {
- if (!Helpers.isArray(inputItems) || inputItems.length === 0) {
- console.warn("can't convert items to input items");
- return
- }
- MainPlaylistController.insert(index, inputItems, false)
- }).then(() => { listView.forceActiveFocus(); })
- // NOTE: Dropping an external item (i.e. filesystem) into the queue.
- } else if (drop.hasUrls) {
- const urlList = [];
-
- for (let url in drop.urls)
- urlList.push(drop.urls[url]);
-
- MainPlaylistController.insert(index, urlList, false);
-
- // NOTE This is required otherwise backend may handle the drop as well yielding double addition.
- drop.accept(Qt.IgnoreAction);
- listView.forceActiveFocus();
- }
-
- return Promise.resolve()
- }
-
Widgets.DragItem {
id: dragItem
@@ -254,23 +210,54 @@ T.Pane {
model: root.model
- Binding on fadingEdge.enableBeginningFade {
- when: (autoScroller.scrollingDirection === ViewDragAutoScrollHandler.Direction.Backward)
- value: false
- }
+ fadingEdge.backgroundColor: (root.background && (root.background.color.a >= 1.0)) ? root.background.color
+ : "transparent"
- Binding on fadingEdge.enableEndFade {
- when: (autoScroller.scrollingDirection === ViewDragAutoScrollHandler.Direction.Forward)
- value: false
+ isDropAcceptableFunc: function(drop, index) {
+ if (drop.source === dragItem)
+ return Helpers.itemsMovable(selectionModel.sortedSelectedIndexesFlat, index)
+ else if (Helpers.isValidInstanceOf(drop.source, Widgets.DragItem))
+ return true
+ else if (drop.hasUrls)
+ return true
+ else
+ return false
}
- fadingEdge.backgroundColor: (root.background && (root.background.color.a >= 1.0)) ? root.background.color
- : "transparent"
+ acceptDropFunc: function(index, drop) {
+ const item = drop.source;
+
+ // NOTE: Move implementation.
+ if (dragItem === item) {
+ model.moveItemsPre(root.selectionModel.sortedSelectedIndexesFlat, index);
+ listView.forceActiveFocus();
+ // NOTE: Dropping medialibrary content into the queue.
+ } else if (Helpers.isValidInstanceOf(item, Widgets.DragItem)) {
+ return item.getSelectedInputItem().then((inputItems) => {
+ if (!Helpers.isArray(inputItems) || inputItems.length === 0) {
+ console.warn("can't convert items to input items");
+ return
+ }
+ MainPlaylistController.insert(index, inputItems, false)
+ }).then(() => { listView.forceActiveFocus(); })
+ // NOTE: Dropping an external item (i.e. filesystem) into the queue.
+ } else if (drop.hasUrls) {
+ const urlList = [];
+
+ for (let url in drop.urls)
+ urlList.push(drop.urls[url]);
+
+ MainPlaylistController.insert(index, urlList, false);
+
+ // NOTE This is required otherwise backend may handle the drop as well yielding double addition.
+ drop.accept(Qt.IgnoreAction);
+ listView.forceActiveFocus();
+ }
- contentWidth: width
+ return Promise.resolve()
+ }
property int shiftIndex: -1
- property Item itemContainsDrag: null
onShowContextMenu: (globalPos) => {
contextMenu.popup(-1, globalPos)
@@ -290,127 +277,6 @@ T.Pane {
}
}
- ViewDragAutoScrollHandler {
- id: autoScroller
-
- view: listView
- dragging: !!listView.itemContainsDrag && listView.itemContainsDrag !== listView.footerItem
- dragPosProvider: function () {
- const source = listView.itemContainsDrag
- const point = source.drag
- return listView.mapFromItem(source, point.x, point.y)
- }
- }
-
- footer: Item {
- implicitWidth: parent.width
-
- Binding on implicitHeight {
- delayed: true
- value: Math.max(VLCStyle.icon_normal, listView.height - y)
- }
-
- property alias firstItemIndicatorVisible: firstItemIndicator.visible
-
- readonly property bool containsDrag: dropArea.containsDrag
-
- readonly property point drag: Qt.point(dropArea.drag.x, dropArea.drag.y)
-
- onContainsDragChanged: {
- if (root.model.count > 0) {
- listView.updateItemContainsDrag(this, containsDrag)
- } else if (!containsDrag && listView.itemContainsDrag === this) {
- // In case model count is changed somehow while
- // containsDrag is set
- listView.updateItemContainsDrag(this, false)
- }
- }
-
- Rectangle {
- id: firstItemIndicator
-
- anchors.fill: parent
- anchors.margins: VLCStyle.margin_small
-
- border.width: VLCStyle.dp(2)
- border.color: theme.accent
-
- color: "transparent"
-
- visible: (root.model.count === 0 && (dropArea.containsDrag || dropArea.dropOperationOngoing))
-
- opacity: 0.8
-
- Widgets.IconLabel {
- anchors.centerIn: parent
-
- text: VLCIcons.add
-
- font.pixelSize: VLCStyle.fontHeight_xxxlarge
-
- color: theme.accent
- }
- }
-
- DropArea {
- id: dropArea
-
- anchors.fill: parent
-
- property bool dropOperationOngoing: false
-
- onEntered: (drag) => {
- if(!root.isDropAcceptable(drag, root.model.count)) {
- drag.accepted = false
- return
- }
- }
-
- onDropped: (drop) => {
- dropOperationOngoing = true
- root.acceptDrop(root.model.count, drop)
- .then(() => { dropOperationOngoing = false })
- }
- }
- }
-
- Rectangle {
- id: dropIndicator
-
- parent: listView.itemContainsDrag
-
- z: 99
-
- anchors {
- left: !!parent ? parent.left : undefined
- right: !!parent ? parent.right : undefined
- top: !!parent ? (parent.bottomContainsDrag === true ? parent.bottom : parent.top)
- : undefined
- }
-
- implicitHeight: VLCStyle.dp(1)
-
- visible: !!parent
- color: theme.accent
- }
-
- function updateItemContainsDrag(item, set) {
- if (set) {
- // This callLater is needed because in Qt 5.15,
- // an item might set itemContainsDrag, before
- // the owning item releases it.
- Qt.callLater(function() {
- if (itemContainsDrag)
- console.debug(item + " set itemContainsDrag before it was released!")
- itemContainsDrag = item
- })
- } else {
- if (itemContainsDrag !== item)
- console.debug(item + " released itemContainsDrag that is not owned!")
- itemContainsDrag = null
- }
- }
-
delegate: PlaylistDelegate {
id: delegate
@@ -421,30 +287,12 @@ T.Pane {
dragItem: root.dragItem
- isDropAcceptable: root.isDropAcceptable
- acceptDrop: root.acceptDrop
+ isDropAcceptable: listView.isDropAcceptableFunc
+ acceptDrop: listView.acceptDropFunc
onContainsDragChanged: listView.updateItemContainsDrag(this, containsDrag)
}
- add: Transition {
- OpacityAnimator {
- from: 0.0
- to: 1.0
- duration: VLCStyle.duration_long
- easing.type: Easing.OutSine
- }
- }
-
- displaced: Transition {
- NumberAnimation {
- // TODO: Use YAnimator >= Qt 6.0 (QTBUG-66475)
- property: "y"
- duration: VLCStyle.duration_long
- easing.type: Easing.OutSine
- }
- }
-
Keys.onDeletePressed: model.removeItems(selectionModel.selectedIndexesFlat)
Navigation.parentItem: root
=====================================
modules/gui/qt/widgets/qml/KeyNavigableListView.qml
=====================================
@@ -24,6 +24,7 @@ import Qt5Compat.GraphicalEffects
import VLC.MainInterface
import VLC.Style
import VLC.Util
+import VLC.Widgets
ListView {
id: root
@@ -37,6 +38,13 @@ ListView {
model: root.model
}
+ // Optional property for drop indicator placement and auto scroll feature:
+ property var itemContainsDrag: undefined
+
+ // Optional functions for the optional drag accessory footer:
+ property var isDropAcceptableFunc
+ property var acceptDropFunc
+
// Private
property bool _keyPressed: false
@@ -48,6 +56,8 @@ ListView {
// if the effect is not wanted.
property alias fadingEdge: fadingEdge
+ property alias autoScrollDirection: viewDragAutoScrollHandlerLoader.scrollingDirection
+
//forward view properties
property alias buttonLeft: buttonLeft
@@ -88,8 +98,117 @@ ListView {
section.criteria: ViewSection.FullString
section.delegate: sectionHeading
+ // Content width is set to the width by default
+ // If the delegate does not obey it, calculate
+ // the content width appropriately.
+ contentWidth: width
+
+ footer: !!root.acceptDropFunc ? footerDragAccessoryComponent : null
+
+ Component {
+ id: footerDragAccessoryComponent
+
+ Item {
+ id: footerItem
+
+ implicitWidth: root.contentWidth
+
+ Binding on implicitHeight {
+ delayed: true
+ value: Math.max(VLCStyle.icon_normal, root.height - y - (root.headerItem?.height ?? 0))
+ }
+
+ property alias firstItemIndicatorVisible: firstItemIndicator.visible
+
+ readonly property bool containsDrag: dropArea.containsDrag
+ readonly property bool topContainsDrag: containsDrag
+ readonly property bool bottomContainsDrag: false
+
+ onContainsDragChanged: {
+ if (root.model.count > 0) {
+ root.updateItemContainsDrag(this, containsDrag)
+ } else if (!containsDrag && root.itemContainsDrag === this) {
+ // In case model count is changed somehow while
+ // containsDrag is set
+ root.updateItemContainsDrag(this, false)
+ }
+ }
+
+ property alias drag: dropArea.drag
+
+ Rectangle {
+ id: firstItemIndicator
+
+ anchors.fill: parent
+ anchors.margins: VLCStyle.margin_small
+
+ border.width: VLCStyle.dp(2)
+ border.color: theme.accent
+
+ color: "transparent"
+
+ visible: (root.model.count === 0 && (dropArea.containsDrag || dropArea.dropOperationOngoing))
+
+ opacity: 0.8
+
+ IconLabel {
+ anchors.centerIn: parent
+
+ text: VLCIcons.add
+
+ font.pixelSize: VLCStyle.fontHeight_xxxlarge
+
+ color: theme.accent
+ }
+ }
+
+ DropArea {
+ id: dropArea
+
+ anchors.fill: parent
+
+ property bool dropOperationOngoing: false
+
+ onEntered: function(drag) {
+ if (!root.isDropAcceptableFunc || !root.isDropAcceptableFunc(drag, root.model.rowCount())
+ || !root.acceptDropFunc) {
+ drag.accepted = false
+ return
+ }
+
+ drag.accepted = true
+ }
+
+ onDropped: function(drop) {
+ console.assert(!!root.acceptDropFunc)
+ dropOperationOngoing = true
+ root.acceptDropFunc(root.model.count, drop)
+ .then(() => { dropOperationOngoing = false })
+ }
+ }
+ }
+ }
+
Accessible.role: Accessible.List
+ add: Transition {
+ OpacityAnimator {
+ from: 0.0 // QTBUG-66475
+ to: 1.0
+ duration: VLCStyle.duration_long
+ easing.type: Easing.OutSine
+ }
+ }
+
+ displaced: Transition {
+ NumberAnimation {
+ // TODO: Use YAnimator >= Qt 6.0 (QTBUG-66475)
+ property: "y"
+ duration: VLCStyle.duration_long
+ easing.type: Easing.OutSine
+ }
+ }
+
// Events
// NOTE: We always want a valid 'currentIndex' by default.
@@ -165,6 +284,23 @@ ListView {
selectionModel.updateSelection(modifiers, oldIndex, newIndex)
}
+ function updateItemContainsDrag(item, set) {
+ if (set) {
+ // This callLater is needed because in Qt 5.15,
+ // an item might set itemContainsDrag, before
+ // the owning item releases it.
+ Qt.callLater(function() {
+ if (itemContainsDrag)
+ console.debug(item + " set itemContainsDrag before it was released!")
+ itemContainsDrag = item
+ })
+ } else {
+ if (itemContainsDrag !== item)
+ console.debug(item + " released itemContainsDrag that is not owned!")
+ itemContainsDrag = null
+ }
+ }
+
Keys.onPressed: (event) => {
let newIndex = -1
@@ -263,6 +399,34 @@ ListView {
listView: root
backgroundColor: theme.bg.primary
+
+ Binding on enableBeginningFade {
+ when: (root.autoScrollDirection === ViewDragAutoScrollHandler.Direction.Backward)
+ value: false
+ }
+
+ Binding on enableEndFade {
+ when: (root.autoScrollDirection === ViewDragAutoScrollHandler.Direction.Forward)
+ value: false
+ }
+ }
+
+ Loader {
+ id: viewDragAutoScrollHandlerLoader
+
+ active: root.itemContainsDrag !== undefined
+
+ readonly property int scrollingDirection: item ? item.scrollingDirection : -1
+
+ sourceComponent: ViewDragAutoScrollHandler {
+ view: root
+ dragging: root.itemContainsDrag !== null
+ dragPosProvider: function () {
+ const source = root.itemContainsDrag
+ const point = source.drag
+ return root.mapFromItem(source, point.x, point.y)
+ }
+ }
}
Component {
@@ -330,6 +494,39 @@ ListView {
}
}
+ Rectangle {
+ id: dropIndicator
+
+ parent: {
+ const item = root.itemContainsDrag
+ if (!item || item.topContainsDrag === undefined || item.bottomContainsDrag === undefined)
+ return null
+ return item
+ }
+
+ z: 99
+
+ anchors {
+ left: !!parent ? parent.left : undefined
+ right: !!parent ? parent.right : undefined
+ top: {
+ if (parent === null)
+ return undefined
+ else if (parent.topContainsDrag === true)
+ return parent.top
+ else if (parent.bottomContainsDrag === true)
+ return parent.bottom
+ else
+ return undefined
+ }
+ }
+
+ implicitHeight: VLCStyle.dp(1)
+
+ visible: !!parent
+ color: theme.accent
+ }
+
// FIXME: We probably need to upgrade these RoundButton(s) eventually. And we probably need
// to have some kind of animation when switching pages.
=====================================
modules/gui/qt/widgets/qml/KeyNavigableTableView.qml
=====================================
@@ -242,8 +242,6 @@ FocusScope {
anchors.fill: parent
- contentWidth: root.width - root.contentLeftMargin - root.contentRightMargin
-
focus: true
headerPositioning: ListView.OverlayHeader
@@ -449,6 +447,8 @@ FocusScope {
tableDelegate.forceActiveFocus(focusReason)
}
+ onContainsDragChanged: view.updateItemContainsDrag(this, containsDrag)
+
Connections {
target: selectionModel
=====================================
modules/gui/qt/widgets/qml/TableViewDelegate.qml
=====================================
@@ -37,6 +37,8 @@ T.Control {
required property bool acceptDrop
readonly property bool dragActive: dragHandler.active
+ property alias containsDrag: dropArea.containsDrag
+ property alias drag: dropArea.drag
required property real fixedColumnWidth
required property real weightedColumnWidth
@@ -281,6 +283,8 @@ T.Control {
}
DropArea {
+ id: dropArea
+
enabled: delegate.acceptDrop
anchors.fill: parent
View it on GitLab: https://code.videolan.org/videolan/vlc/-/compare/7510943c25755ba38c9a5e29042604f12de14bff...41cfa87fb33d7b1a3399c940bb42348c02358e7b
--
View it on GitLab: https://code.videolan.org/videolan/vlc/-/compare/7510943c25755ba38c9a5e29042604f12de14bff...41cfa87fb33d7b1a3399c940bb42348c02358e7b
You're receiving this email because of your account on code.videolan.org.
VideoLAN code repository instance
More information about the vlc-commits
mailing list