[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