[vlc-devel] [PATCH 5/9] qml: redesign playlist menu
Pierre Lamot
pierre at videolabs.io
Mon Nov 18 18:15:38 CET 2019
The icon only menu was pretty obscure, the new design use textual entries and
has the look and feel of the embed player menus.
More entries have been added to the menu (shuffle, sort, clear, ...)
Qt.Quick menus are not used here because modal menus can only be set as an
overlay of the whole application.
---
.../gui/qt/qml/playlist/PlaylistListView.qml | 108 +++--
modules/gui/qt/qml/playlist/PlaylistMenu.qml | 384 ++++++++----------
2 files changed, 242 insertions(+), 250 deletions(-)
diff --git a/modules/gui/qt/qml/playlist/PlaylistListView.qml b/modules/gui/qt/qml/playlist/PlaylistListView.qml
index f4978a6a50..0926ed920a 100644
--- a/modules/gui/qt/qml/playlist/PlaylistListView.qml
+++ b/modules/gui/qt/qml/playlist/PlaylistListView.qml
@@ -22,6 +22,7 @@ import QtQml.Models 2.2
import org.videolan.vlc 0.1
import "qrc:///utils/" as Utils
+import "qrc:///utils/KeyHelper.js" as KeyHelper
import "qrc:///style/"
Utils.NavigableFocusScope {
@@ -39,28 +40,75 @@ Utils.NavigableFocusScope {
id: dragItem
}
-
- /* popup side menu allowing to perform group action */
PlaylistMenu {
- id: overlay
-
- anchors.verticalCenter: root.verticalCenter
- anchors.right: view.right
+ id: overlayMenu
+ anchors.fill: parent
z: 2
- onMenuExit:{
- view.mode = "normal"
- view.focus = true
- }
- onClear: view.onDelete()
- onPlay: view.onPlay()
- onSelectionMode: {
- view.mode = selectionMode ? "select" : "normal"
- view.focus = true
- }
- onMoveMode: {
- view.mode = moveMode ? "move" : "normal"
- view.focus = true
+ navigationParent: root
+ navigationLeftItem: view
+
+ leftPadding: root.leftPadding
+ rightPadding: root.rightPadding
+
+ //rootmenu
+ Action { id:playAction; text: qsTr("Play"); onTriggered: view.onPlay(); icon.source: "qrc:///toolbar/play_b.svg" }
+ Action { id:deleteAction; text: qsTr("Delete"); onTriggered: view.onDelete() }
+ Action { id:clearAllAction; text: qsTr("Clear Playlist"); onTriggered: mainPlaylistController.clear() }
+ Action { id:selectAllAction; text: qsTr("Select All"); onTriggered: root.plmodel.selectAll() }
+ Action { id:shuffleAction; text: qsTr("Suffle playlist"); onTriggered: mainPlaylistController.shuffle(); icon.source: "qrc:///buttons/playlist/shuffle_on.svg" }
+ Action { id:sortAction; text: qsTr("Sort"); property string subMenu: "sortmenu"}
+ Action { id:selectTracksAction; text: qsTr("Select Tracks"); onTriggered: view.mode = "select" }
+ Action { id:moveTracksAction; text: qsTr("Move Selection"); onTriggered: view.mode = "move" }
+
+ //sortmenu
+ Action { id: sortTitleAction; text: qsTr("Tile");
+ onTriggered: mainPlaylistController.sort(PlaylistControllerModel.SORT_KEY_TITLE, PlaylistControllerModel.SORT_ORDER_ASC)}
+ Action { id: sortDurationAction;text: qsTr("Duration");
+ onTriggered: mainPlaylistController.sort(PlaylistControllerModel.SORT_KEY_DURATION, PlaylistControllerModel.SORT_ORDER_ASC)}
+ Action { id: sortArtistAction; text: qsTr("Artist");
+ onTriggered: mainPlaylistController.sort(PlaylistControllerModel.SORT_KEY_ARTIST, PlaylistControllerModel.SORT_ORDER_ASC)}
+ Action { id: sortAlbumAction; text: qsTr("Album");
+ onTriggered: mainPlaylistController.sort(PlaylistControllerModel.SORT_KEY_ALBUM, PlaylistControllerModel.SORT_ORDER_ASC)}
+ Action { id: sortGenreAction; text: qsTr("Genre");
+ onTriggered: mainPlaylistController.sort(PlaylistControllerModel.SORT_KEY_GENRE, PlaylistControllerModel.SORT_ORDER_ASC)}
+ Action { id: sortDateAction; text: qsTr("Date");
+ onTriggered: mainPlaylistController.sort(PlaylistControllerModel.SORT_KEY_DATE, PlaylistControllerModel.SORT_ORDER_ASC)}
+ Action { id: sortTrackAction; text: qsTr("Track number");
+ onTriggered: mainPlaylistController.sort(PlaylistControllerModel.SORT_KEY_TRACK_NUMBER, PlaylistControllerModel.SORT_ORDER_ASC)}
+ Action { id: sortURLAction; text: qsTr("URL");
+ onTriggered: mainPlaylistController.sort(PlaylistControllerModel.SORT_KEY_URL, PlaylistControllerModel.SORT_ORDER_ASC)}
+ Action { id: sortRatingAction; text: qsTr("Rating");
+ onTriggered: mainPlaylistController.sort(PlaylistControllerModel.SORT_KEY_RATIN, PlaylistControllerModel.SORT_ORDER_ASC)}
+
+ models: {
+ "rootmenu" : {
+ title: qsTr("Playlist"),
+ entries: [
+ playAction,
+ deleteAction,
+ clearAllAction,
+ selectAllAction,
+ shuffleAction,
+ sortAction,
+ selectTracksAction,
+ moveTracksAction
+ ]
+ },
+ "sortmenu" :{
+ title: qsTr("Sort Playlist"),
+ entries: [
+ sortTitleAction,
+ sortDurationAction,
+ sortArtistAction,
+ sortAlbumAction,
+ sortGenreAction,
+ sortDateAction,
+ sortTrackAction,
+ sortURLAction,
+ sortRatingAction,
+ ]
+ }
}
}
@@ -176,17 +224,16 @@ Utils.NavigableFocusScope {
}
Keys.onDeletePressed: onDelete()
+ Keys.onMenuPressed: overlayMenu.open()
navigationParent: root
- navigationRight: function() {
- overlay.state = "normal"
- overlay.focus = true
+ navigationRight: function(index) {
+ overlayMenu.open()
}
navigationLeft: function(index) {
if (mode === "normal") {
root.navigationLeft(index)
} else {
- overlay.state = "hidden"
mode = "normal"
}
}
@@ -194,7 +241,6 @@ Utils.NavigableFocusScope {
if (mode === "normal") {
root.navigationCancel(index)
} else {
- overlay.state = "hidden"
mode = "normal"
}
}
@@ -257,14 +303,14 @@ Utils.NavigableFocusScope {
}
}
}
- }
- Label {
- anchors.centerIn: parent
- visible: plmodel.count === 0
- font.pixelSize: VLCStyle.fontHeight_xxlarge
- color: root.activeFocus ? VLCStyle.colors.accent : VLCStyle.colors.text
- text: qsTr("playlist is empty")
+ Label {
+ anchors.centerIn: parent
+ visible: plmodel.count === 0
+ font.pixelSize: VLCStyle.fontHeight_xxlarge
+ color: view.activeFocus ? VLCStyle.colors.accent : VLCStyle.colors.text
+ text: qsTr("playlist is empty")
+ }
}
Keys.priority: Keys.AfterItem
diff --git a/modules/gui/qt/qml/playlist/PlaylistMenu.qml b/modules/gui/qt/qml/playlist/PlaylistMenu.qml
index 2c2330af48..8d771fba1e 100644
--- a/modules/gui/qt/qml/playlist/PlaylistMenu.qml
+++ b/modules/gui/qt/qml/playlist/PlaylistMenu.qml
@@ -17,259 +17,205 @@
*****************************************************************************/
import QtQuick 2.11
import QtQuick.Controls 2.4
+import QtQuick.Controls.impl 2.4
import QtQuick.Layouts 1.3
+import "qrc:///utils/" as Utils
+import "qrc:///utils/KeyHelper.js" as KeyHelper
import "qrc:///style/"
-FocusScope{
- id: root
-
- signal menuExit()
- signal play()
- signal clear()
- signal selectionMode()
- signal moveMode()
-
- Keys.onPressed: {
- if (event.matches(StandardKey.MoveToPreviousChar) //left
- || event.matches(StandardKey.MoveToNextChar) //right
- || event.matches(StandardKey.Back)
- || event.matches(StandardKey.Cancel) //esc
- ) {
- _exitMenu();
- event.accepted = true
- return;
+Utils.NavigableFocusScope {
+ id: overlayMenu
+ visible: false
+
+ property alias models: playlistMenu.models
+ property alias currentModel: playlistMenu.currentModel
+
+ property int leftPadding: 0
+ property int rightPadding: 0
+
+ onActiveFocusChanged: {
+ if (!activeFocus) {
+ overlayMenu.close()
}
}
- width: VLCStyle.icon_large
- height: VLCStyle.icon_large * 5
- property int _hiddentX: VLCStyle.icon_large
+ function close() {
+ overlayMenu.visible = false
+ view.forceActiveFocus()
+ }
- function _exitMenu() {
- root.state = "hidden"
- menuExit()
+ function open() {
+ playlistMenu.currentModel = "rootmenu"
+ playlistMenu.menuHierachy = []
+ overlayMenu.visible = true
+ overlayMenu.forceActiveFocus()
}
- Item {
- id: overlay
- anchors.fill: parent
+ function pushMenu(menu) {
+ playlistMenu.menuHierachy.push(playlistMenu.currentModel)
+ playlistMenu.currentModel = menu
+ }
- Column {
- anchors.right: parent.right
- spacing: VLCStyle.margin_xsmall
+ property real drawerRatio: 0
+ Behavior on drawerRatio {
+ NumberAnimation {
+ duration: 150
+ }
+ }
+ onVisibleChanged: {
+ drawerRatio = visible ? 0.9 : 0
+ }
- RoundButton {
- id: playButton
+ Rectangle {
+ color: "black"
+ anchors {
+ left: parent.left
+ top: parent.top
+ bottom: parent.bottom
+ }
+ width: parent.width * (1 - drawerRatio)
+ opacity: 0.4
+ }
- height: activeFocus ? VLCStyle.icon_normal * 1.3 : VLCStyle.icon_normal
- width: activeFocus ? VLCStyle.icon_normal * 1.3 : VLCStyle.icon_normal
- x: root._hiddentX
- KeyNavigation.down: clearButton
- contentItem: Label {
- text: VLCIcons.play
- font.family: VLCIcons.fontFamily
- font.pixelSize: VLCStyle.icon_normal
- color: "black"
- horizontalAlignment: Text.AlignHCenter
- verticalAlignment: Text.AlignVCenter
- }
- onClicked: {
- play()
- _exitMenu()
- }
- focus: true
- background: Rectangle {
- radius: parent.radius
- implicitHeight: parent.width
- implicitWidth: parent.height
- color: "palegreen"
- }
- }
- RoundButton {
- id: clearButton
+ Rectangle {
+ color: "black"
+ anchors {
+ right: parent.right
+ top: parent.top
+ bottom: parent.bottom
+ }
+ width: parent.width * drawerRatio
+ opacity: 0.9
- height: activeFocus ? VLCStyle.icon_normal * 1.3 : VLCStyle.icon_normal
- width: activeFocus ? VLCStyle.icon_normal * 1.3 : VLCStyle.icon_normal
- x: root._hiddentX
- KeyNavigation.down: selectButton
- contentItem: Label {
- text: VLCIcons.clear
- font.family: VLCIcons.fontFamily
- font.pixelSize: VLCStyle.icon_normal
- color: "black"
- horizontalAlignment: Text.AlignHCenter
- verticalAlignment: Text.AlignVCenter
- }
- onClicked: {
- clear()
- _exitMenu()
- }
- background: Rectangle {
- radius: parent.radius
- implicitHeight: parent.width
- implicitWidth: parent.height
- color: "pink"
- }
- }
- RoundButton {
- id: selectButton
+ //avoid mouse event to be propagated to the widget below
+ MouseArea {
+ anchors.fill: parent
+ hoverEnabled: true
+ }
- height: activeFocus ? VLCStyle.icon_normal * 1.3 : VLCStyle.icon_normal
- width: activeFocus ? VLCStyle.icon_normal * 1.3 : VLCStyle.icon_normal
- x: root._hiddentX
- KeyNavigation.down: moveButton
- contentItem: Label {
- text: VLCIcons.playlist
- font.family: VLCIcons.fontFamily
- font.pixelSize: VLCStyle.icon_normal
- color: "black"
- horizontalAlignment: Text.AlignHCenter
- verticalAlignment: Text.AlignVCenter
- }
+ Utils.KeyNavigableListView {
+ id: playlistMenu
+ anchors.fill: parent
+ focus: true
- checkable: true
- checked: false
- onClicked: root.state = checked ? "select" : "normal"
- onCheckedChanged: selectionMode(checked)
- background: Rectangle {
- radius: parent.radius
- implicitHeight: parent.width
- implicitWidth: parent.height
- color: "lightblue"
+ property var models: {
+ "rootmenu" : {
+ "title" : "",
+ "entries" : []
}
}
- RoundButton {
- id: moveButton
+ property string currentModel: "rootmenu"
+ property var menuHierachy: []
+
+ model: models[currentModel]["entries"]
+ modelCount: models[currentModel]["entries"].length
+
+ header: Label {
+ text: models[currentModel]["title"]
+ color: "white"
+ font.pixelSize: VLCStyle.fontSize_xlarge
+ font.bold: true
+
+ leftPadding: VLCStyle.margin_small
+ rightPadding: VLCStyle.margin_small
+ topPadding: VLCStyle.margin_xsmall
+ bottomPadding: VLCStyle.margin_xsmall
+ height: VLCStyle.fontHeight_xlarge + topPadding + bottomPadding
+ }
+
+ delegate: Button {
+ id: control
+ text: modelData.text
+ width: playlistMenu.width
- height: activeFocus ? VLCStyle.icon_normal * 1.3 : VLCStyle.icon_normal
- width: activeFocus ? VLCStyle.icon_normal * 1.3 : VLCStyle.icon_normal
- x: root._hiddentX
+ leftPadding: VLCStyle.margin_small + root.leftPadding
+ rightPadding: VLCStyle.margin_small + root.rightPadding
- KeyNavigation.down: backButton
+ icon.width: VLCStyle.fontHeight_normal
+ icon.height: VLCStyle.fontHeight_normal
contentItem: Label {
- text: VLCIcons.space
- font.family: VLCIcons.fontFamily
- font.pixelSize: VLCStyle.icon_normal
- color: "black"
- horizontalAlignment: Text.AlignHCenter
- verticalAlignment: Text.AlignVCenter
+ text: control.text
+ color: "white"
+ font.pixelSize: VLCStyle.fontSize_normal
+ leftPadding: VLCStyle.icon_small
+
}
- checkable: true
- checked: false
- onClicked: root.state = checked ? "move" : "normal"
- onCheckedChanged: moveMode(checked)
background: Rectangle {
- radius: parent.radius
- implicitHeight: parent.width
- implicitWidth: parent.height
- color: "lightyellow"
- }
- }
- RoundButton {
- id: backButton
- height: activeFocus ? VLCStyle.icon_normal * 1.3 : VLCStyle.icon_normal
- width: activeFocus ? VLCStyle.icon_normal * 1.3 : VLCStyle.icon_normal
- x: root._hiddentX
+ implicitWidth: 100
+ implicitHeight: VLCStyle.fontHeight_normal
+ color: control.activeFocus ? "orange" : "transparent"
- contentItem: Label {
- text: VLCIcons.exit
- font.family: VLCIcons.fontFamily
- font.pixelSize: VLCStyle.icon_normal
- color: "black"
- horizontalAlignment: Text.AlignHCenter
- verticalAlignment: Text.AlignVCenter
- }
+ ColorImage {
+ width: control.icon.width
+ height: control.icon.height
- onClicked: _exitMenu()
+ x: control.mirrored ? control.width - width - control.rightPadding : control.leftPadding
+ y: control.topPadding + (control.availableHeight - height) / 2
- background: Rectangle {
- radius: parent.radius
- implicitHeight: parent.width
- implicitWidth: parent.height
- color: "lightgrey"
- }
- }
- }
- }
+ source: control.checked ? "qrc:/qt-project.org/imports/QtQuick/Controls.2/images/check.png"
+ : modelData.icon.source ? modelData.icon.source
+ : ""
+ visible: true
+ color: control.enabled ? VLCStyle.colors.playerFg : VLCStyle.colors.playerFgInactive
+ }
- state: "hidden"
- states: [
- State {
- name: "hidden"
- PropertyChanges { target: selectButton; checked: false }
- PropertyChanges { target: moveButton; checked: false }
- },
- State {
- name: "normal"
- PropertyChanges { target: moveButton; checked: false }
- PropertyChanges { target: selectButton; checked: false }
- },
- State {
- name: "select"
- PropertyChanges { target: selectButton; checked: true }
- PropertyChanges { target: moveButton; checked: false }
- },
- State {
- name: "move"
- PropertyChanges { target: selectButton; checked: false }
- PropertyChanges { target: moveButton; checked: true }
- }
- ]
-
- transitions: [
- Transition {
- from: "hidden"; to: "*"
- ParallelAnimation {
- SequentialAnimation {
- NumberAnimation { target: playButton; properties: "x"; duration: 200; from: _hiddentX; to: 0 }
- }
- SequentialAnimation {
- PauseAnimation { duration: 25 }
- NumberAnimation { target: clearButton; properties: "x"; duration: 200; from: _hiddentX; to: 0 }
- }
- SequentialAnimation {
- PauseAnimation { duration: 75 }
- NumberAnimation { target: selectButton; properties: "x"; duration: 200; from: _hiddentX; to: 0 }
- }
- SequentialAnimation {
- PauseAnimation { duration: 50 }
- NumberAnimation { target: moveButton; properties: "x"; duration: 200; from: _hiddentX; to: 0 }
- }
- SequentialAnimation {
- PauseAnimation { duration: 100 }
- NumberAnimation { target: backButton; properties: "x"; duration: 200; from: _hiddentX; to: 0 }
- }
- }
- },
- Transition {
- from: "*"; to: "hidden"
- ParallelAnimation {
- SequentialAnimation {
- PauseAnimation { duration: 100 }
- NumberAnimation { target: playButton; properties: "x"; duration: 200; from: 0; to: _hiddentX }
- }
- SequentialAnimation {
- PauseAnimation { duration: 75 }
- NumberAnimation { target: clearButton; properties: "x"; duration: 200; from: 0; to: _hiddentX }
- }
- SequentialAnimation {
- PauseAnimation { duration: 50 }
- NumberAnimation { target: selectButton; properties: "x"; duration: 200; from: 0; to: _hiddentX }
- }
- SequentialAnimation {
- PauseAnimation { duration: 25 }
- NumberAnimation { target: moveButton; properties: "x"; duration: 200; from: 0; to: _hiddentX }
+ ColorImage {
+ x: control.mirrored ? control.leftPadding : control.width - width - control.rightPadding
+ y: control.topPadding + (control.availableHeight - height) / 2
+
+ width: VLCStyle.icon_xsmall
+ height: VLCStyle.icon_xsmall
+
+ visible: !!modelData["subMenu"]
+ mirror: control.mirrored
+ color: control.enabled ? VLCStyle.colors.playerFg : VLCStyle.colors.playerFgInactive
+ source: "qrc:/qt-project.org/imports/QtQuick/Controls.2/images/arrow-indicator.png"
+ }
}
- SequentialAnimation {
- NumberAnimation { target: backButton; properties: "x"; duration: 200; from: 0; to: _hiddentX }
+
+ onClicked: {
+
+ if (!!modelData["subMenu"]) {
+ pushMenu(modelData["subMenu"])
+ } else {
+ modelData.trigger()
+ overlayMenu.close()
+ }
+ }
+
+ Keys.onPressed: {
+ if (KeyHelper.matchRight(event)) {
+ if (!!modelData["subMenu"]) {
+ pushMenu(modelData["subMenu"])
+ event.accepted = true
+ }
+ } else if (KeyHelper.matchLeft(event)) {
+ if (playlistMenu.menuHierachy.length > 0) {
+ playlistMenu.currentModel = playlistMenu.menuHierachy.pop()
+ event.accepted = true
+ } else {
+ overlayMenu.close()
+ }
+ }
+ }
+
+ Keys.onReleased: {
+ if (KeyHelper.matchCancel(event)) {
+ event.accepted = true
+ if (playlistMenu.menuHierachy.length > 0) {
+ playlistMenu.currentModel = playlistMenu.menuHierachy.pop()
+ } else {
+ overlayMenu.close()
+ }
+ }
}
}
}
- ]
+ }
}
--
2.17.1
More information about the vlc-devel
mailing list