From 5a5261d3db6f30e05cbc55b72aaf394128242d01 Mon Sep 17 00:00:00 2001 From: Calum Matheson Date: Tue, 3 Dec 2024 15:17:07 +0000 Subject: [PATCH 1/4] Percussion panel - implement keyboard navigation for toolbar --- .../NotationScene/PercussionPanel.qml | 4 +- .../internal/PercussionPanelToolBar.qml | 119 ++++++++++++++---- 2 files changed, 100 insertions(+), 23 deletions(-) diff --git a/src/notation/qml/MuseScore/NotationScene/PercussionPanel.qml b/src/notation/qml/MuseScore/NotationScene/PercussionPanel.qml index d7f78415a598f..a62afcd536eae 100644 --- a/src/notation/qml/MuseScore/NotationScene/PercussionPanel.qml +++ b/src/notation/qml/MuseScore/NotationScene/PercussionPanel.qml @@ -69,8 +69,8 @@ Item { width: parent.width height: 36 - navigation.section: root.navigationSection - navigation.order: root.contentNavigationPanelOrderStart + navigationSection: root.navigationSection + navigationOrderStart: root.contentNavigationPanelOrderStart model: percModel diff --git a/src/notation/qml/MuseScore/NotationScene/internal/PercussionPanelToolBar.qml b/src/notation/qml/MuseScore/NotationScene/internal/PercussionPanelToolBar.qml index 51630a4bb51dc..d31a25f436e87 100644 --- a/src/notation/qml/MuseScore/NotationScene/internal/PercussionPanelToolBar.qml +++ b/src/notation/qml/MuseScore/NotationScene/internal/PercussionPanelToolBar.qml @@ -32,7 +32,9 @@ Item { required property var model required property int panelWidth - property alias navigation: navPanel + property NavigationSection navigationSection: null + property int navigationOrderStart: 1 + readonly property int navigationOrderEnd: rightSideNavPanel.order QtObject { id: prv @@ -44,9 +46,10 @@ Item { } NavigationPanel { - id: navPanel - name: "PercussionPanelToolBar" - enabled: root.enabled && root.visible + id: centralNavPanel + name: "PercussionPanelToolBarCentral" + section: root.navigationSection + order: root.navigationOrderStart + 1 } Row { @@ -57,16 +60,6 @@ Item { anchors.verticalCenter: parent.verticalCenter x: prv.centerX - (centralButtonsRow.width / 2) - // We only want to round the outer corners of the buttons... - layer.enabled: ui.isEffectsAllowed - layer.effect: EffectOpacityMask { - maskSource: Rectangle { - width: centralButtonsRow.width - height: centralButtonsRow.height - radius: 3 - } - } - visible: root.model.currentPanelMode !== PanelMode.EDIT_LAYOUT FlatButton { @@ -82,9 +75,48 @@ Item { accentButton: root.model.currentPanelMode === PanelMode.WRITE backgroundRadius: 0 - navigation.panel: navPanel + navigation.panel: centralNavPanel navigation.row: 0 + backgroundItem: RoundedRectangle { + id: writeButtonBackground + + property real backgroundOpacity: ui.theme.buttonOpacityNormal + color: Utils.colorWithAlpha(writeButton.accentButton ? ui.theme.accentColor : ui.theme.buttonColor, backgroundOpacity) + + topLeftRadius: 3 + topRightRadius: 0 + bottomLeftRadius: 3 + bottomRightRadius: 0 + + NavigationFocusBorder { + drawOutsideParent: false + navigationCtrl: writeButton.navigation + } + + states: [ + State { + name: "PRESSED" + when: writeButton.mouseArea.pressed + + PropertyChanges { + target: writeButtonBackground + backgroundOpacity: ui.theme.buttonOpacityHit + } + }, + + State { + name: "HOVERED" + when: !writeButton.mouseArea.pressed && writeButton.mouseArea.containsMouse + + PropertyChanges { + target: writeButtonBackground + backgroundOpacity: ui.theme.buttonOpacityHover + } + } + ] + } + onClicked: { root.model.currentPanelMode = PanelMode.WRITE } @@ -110,9 +142,47 @@ Item { accentButton: root.model.currentPanelMode === PanelMode.SOUND_PREVIEW backgroundRadius: 0 - navigation.panel: navPanel - navigation.row: 0 + navigation.panel: centralNavPanel + navigation.row: 1 + backgroundItem: RoundedRectangle { + id: previewButtonBackground + + property real backgroundOpacity: ui.theme.buttonOpacityNormal + color: Utils.colorWithAlpha(previewButton.accentButton ? ui.theme.accentColor : ui.theme.buttonColor, backgroundOpacity) + + topLeftRadius: 0 + topRightRadius: 3 + bottomLeftRadius: 0 + bottomRightRadius: 3 + + NavigationFocusBorder { + drawOutsideParent: false + navigationCtrl: previewButton.navigation + } + + states: [ + State { + name: "PRESSED" + when: previewButton.mouseArea.pressed + + PropertyChanges { + target: previewButtonBackground + backgroundOpacity: ui.theme.buttonOpacityHit + } + }, + + State { + name: "HOVERED" + when: !previewButton.mouseArea.pressed && previewButton.mouseArea.containsMouse + + PropertyChanges { + target: previewButtonBackground + backgroundOpacity: ui.theme.buttonOpacityHover + } + } + ] + } onClicked: { root.model.currentPanelMode = PanelMode.SOUND_PREVIEW } @@ -132,7 +202,7 @@ Item { orientation: Qt.Horizontal accentButton: true - navigation.panel: navPanel + navigation.panel: centralNavPanel navigation.row: 0 onClicked: { @@ -140,6 +210,13 @@ Item { } } + NavigationPanel { + id: rightSideNavPanel + name: "PercussionPanelToolBarRightSide" + section: root.navigationSection + order: centralNavPanel.order + 1 + } + Row { id: rightSideButtonsRow @@ -156,7 +233,7 @@ Item { text: qsTrc("notation", "Layout") orientation: Qt.Horizontal - navigation.panel: navPanel + navigation.panel: rightSideNavPanel navigation.row: 0 onClicked: { @@ -177,8 +254,8 @@ Item { text: qsTrc("notation", "Customize kit") orientation: Qt.Horizontal - navigation.panel: navPanel - navigation.row: 0 + navigation.panel: rightSideNavPanel + navigation.row: 1 onClicked: { root.model.customizeKit() From 0762c395c9a7d5ad3c402bb42f8704a9398606ec Mon Sep 17 00:00:00 2001 From: Calum Matheson Date: Wed, 4 Dec 2024 09:08:08 +0000 Subject: [PATCH 2/4] Percussion panel - implement keyboard navigation basics for pad grid --- .../NotationScene/PercussionPanel.qml | 76 +++++++++++++++ .../internal/PercussionPanelPad.qml | 94 +++++++++++++++++++ .../internal/PercussionPanelPadContent.qml | 3 +- 3 files changed, 172 insertions(+), 1 deletion(-) diff --git a/src/notation/qml/MuseScore/NotationScene/PercussionPanel.qml b/src/notation/qml/MuseScore/NotationScene/PercussionPanel.qml index a62afcd536eae..ef37738b362a7 100644 --- a/src/notation/qml/MuseScore/NotationScene/PercussionPanel.qml +++ b/src/notation/qml/MuseScore/NotationScene/PercussionPanel.qml @@ -103,6 +103,16 @@ Item { height: padGrid.cellHeight * padGrid.numRows spacing: padGrid.spacing / 2 + NavigationPanel { + id: deleteButtonsPanel + + name: "PercussionPanelDeleteRowButtons" + section: root.navigationSection + order: toolbar.navigationOrderEnd + 3 + + enabled: deleteButtonsColumn.visible + } + Column { id: deleteButtonsColumn @@ -135,6 +145,9 @@ Item { icon: IconCode.DELETE_TANK backgroundRadius: deleteButton.width / 2 + navigation.panel: deleteButtonsPanel + navigation.row: model.index + onClicked: { padGrid.model.deleteRow(model.index) } @@ -163,6 +176,14 @@ Item { property Item draggedPad: null + QtObject { + id: gridPrv + + // This variable ensures we stay within a given pad when tabbing back-and-forth between + // "main" and "footer" controls + property var currentPadNavigationIndex: [0, 0] + } + Layout.alignment: Qt.AlignTop Layout.fillHeight: true @@ -175,6 +196,36 @@ Item { model: percModel.padListModel + NavigationPanel { + id: padsNavPanel + + name: "PercussionPanelPads" + section: root.navigationSection + order: toolbar.navigationOrderEnd + 1 + + onNavigationEvent: function(event) { + if (event.type === NavigationEvent.AboutActive) { + event.setData("controlIndex", gridPrv.currentPadNavigationIndex) + } + } + } + + NavigationPanel { + id: padFootersNavPanel + + name: "PercussionPanelFooters" + section: root.navigationSection + order: toolbar.navigationOrderEnd + 2 + + enabled: percModel.currentPanelMode !== PanelMode.EDIT_LAYOUT + + onNavigationEvent: function(event) { + if (event.type === NavigationEvent.AboutActive) { + event.setData("controlIndex", gridPrv.currentPadNavigationIndex) + } + } + } + delegate: Item { id: padArea @@ -201,6 +252,11 @@ Item { dragParent: root + navigationRow: index / padGrid.numColumns + navigationColumn: index % padGrid.numColumns + padNavigation.panel: padsNavPanel + footerNavigation.panel: padFootersNavPanel + onDragStarted: { padGrid.draggedPad = pad padGrid.model.startDrag(index) @@ -216,6 +272,13 @@ Item { padGrid.draggedPad = null padGrid.model.endDrag(-1) } + + onHasActiveControlChanged: { + if (!pad.hasActiveControl) { + return; + } + gridPrv.currentPadNavigationIndex = [pad.navigationRow, pad.navigationColumn] + } } states: [ @@ -242,6 +305,16 @@ Item { } } + NavigationPanel { + id: addRowButtonPanel + + name: "PercussionPanelAddRowButton" + section: root.navigationSection + order: toolbar.navigationOrderEnd + 4 + + enabled: addRowButton.visible + } + FlatButton { id: addRowButton @@ -254,6 +327,9 @@ Item { icon: IconCode.PLUS text: qsTrc("notation", "Add row") orientation: Qt.Horizontal + + navigation.panel: addRowButtonPanel + onClicked: { padGrid.model.addEmptyRow() flickable.goToBottom() diff --git a/src/notation/qml/MuseScore/NotationScene/internal/PercussionPanelPad.qml b/src/notation/qml/MuseScore/NotationScene/internal/PercussionPanelPad.qml index 781f8db5e9d11..0865caafb2e1d 100644 --- a/src/notation/qml/MuseScore/NotationScene/internal/PercussionPanelPad.qml +++ b/src/notation/qml/MuseScore/NotationScene/internal/PercussionPanelPad.qml @@ -41,6 +41,13 @@ DropArea { property alias showOriginBackground: originBackground.visible property alias draggableArea: draggableArea + property int navigationRow: -1 + property int navigationColumn: -1 + property alias padNavigation: padNavCtrl + property alias footerNavigation: footerNavCtrl + + readonly property bool hasActiveControl: padNavCtrl.active || footerNavCtrl.active + property var dragParent: null signal dragStarted() signal dragCancelled() @@ -49,6 +56,66 @@ DropArea { id: prv readonly property color enabledBackgroundColor: Utils.colorWithAlpha(ui.theme.buttonColor, ui.theme.buttonOpacityNormal) readonly property color disabledBackgroundColor: Utils.colorWithAlpha(ui.theme.buttonColor, ui.theme.itemOpacityDisabled) + readonly property real footerHeight: 24 + readonly property string accessibleDescription: { + //: %1 will be the row number of a percussion panel pad + let line1 = qsTrc("notation", "Row: %1").arg(root.navigationRow + 1) + + //: %1 will be the column number of a percussion panel pad + let line2 = qsTrc("notation", "Column: %1").arg(root.navigationColumn + 1) + + return line1 + ", " + line2 + } + } + + NavigationControl { + id: padNavCtrl + + row: root.navigationRow + column: root.navigationColumn + + name: root.objectName !== "" ? root.objectName : "PercussionPanelPad" + + // Only navigate to empty slots when we're in edit mode + enabled: Boolean(root.padModel) || root.panelMode === PanelMode.EDIT_LAYOUT + + accessible.role: MUAccessible.Button + accessible.name: Boolean(root.padModel) ? root.padModel.instrumentName : qsTrc("notation", "Empty pad") + + accessible.description: prv.accessibleDescription + + accessible.visualItem: padFocusBorder + accessible.enabled: padNavCtrl.enabled + + onTriggered: { + if (!Boolean(root.padModel)) { + return + } + root.padModel.triggerPad() + } + } + + NavigationControl { + id: footerNavCtrl + + row: root.navigationRow + column: root.navigationColumn + + name: root.objectName !== "" ? root.objectName : "PercussionPanelPadFooter" + + enabled: Boolean(root.padModel) + + accessible.role: MUAccessible.Button + accessible.name: Boolean(root.padModel) ? root.padModel.instrumentName + " " + qsTrc("notation", "footer") : "" + + accessible.description: prv.accessibleDescription + + accessible.visualItem: footerFocusBorder + accessible.enabled: footerNavCtrl.enabled + + onTriggered: { + // TODO: trigger context menu (not yet implemented) + } } Rectangle { @@ -115,6 +182,9 @@ DropArea { padModel: root.padModel panelMode: root.panelMode useNotationPreview: root.useNotationPreview + + footerHeight: prv.footerHeight + dragActive: dragHandler.active } } @@ -129,6 +199,30 @@ DropArea { } } + NavigationFocusBorder { + id: padFocusBorder + + padding: root.panelMode === PanelMode.EDIT_LAYOUT ? 0 : root.totalBorderWidth * -1 + navigationCtrl: padNavCtrl + } + + NavigationFocusBorder { + id: footerFocusBorder + + anchors { + fill: null + + left: padLoader.left + right: padLoader.right + bottom: padLoader.bottom + } + + height: prv.footerHeight + root.totalBorderWidth + radius: 0 + + navigationCtrl: footerNavCtrl + } + states: [ State { name: "DRAGGED" diff --git a/src/notation/qml/MuseScore/NotationScene/internal/PercussionPanelPadContent.qml b/src/notation/qml/MuseScore/NotationScene/internal/PercussionPanelPadContent.qml index f273822f682df..18130e79cfd9a 100644 --- a/src/notation/qml/MuseScore/NotationScene/internal/PercussionPanelPadContent.qml +++ b/src/notation/qml/MuseScore/NotationScene/internal/PercussionPanelPadContent.qml @@ -33,6 +33,8 @@ Column { property int panelMode: -1 property bool useNotationPreview: false + property alias footerHeight: footerArea.height + property bool dragActive: false Rectangle { @@ -115,7 +117,6 @@ Column { id: footerArea width: parent.width - height: 24 color: Utils.colorWithAlpha(ui.theme.buttonColor, ui.theme.buttonOpacityNormal) From fea25a99fab12605b23e4df2cded47b02c4cb7e8 Mon Sep 17 00:00:00 2001 From: Calum Matheson Date: Tue, 19 Nov 2024 08:12:19 +0000 Subject: [PATCH 3/4] Minor refactor - rename drag to swap --- .../NotationScene/PercussionPanel.qml | 42 +++++++++---------- .../internal/PercussionPanelPad.qml | 38 ++++++++--------- .../internal/PercussionPanelPadContent.qml | 6 +-- .../percussionpanelpadlistmodel.cpp | 12 +++--- .../percussionpanelpadlistmodel.h | 6 +-- 5 files changed, 52 insertions(+), 52 deletions(-) diff --git a/src/notation/qml/MuseScore/NotationScene/PercussionPanel.qml b/src/notation/qml/MuseScore/NotationScene/PercussionPanel.qml index ef37738b362a7..0b58bfdb17795 100644 --- a/src/notation/qml/MuseScore/NotationScene/PercussionPanel.qml +++ b/src/notation/qml/MuseScore/NotationScene/PercussionPanel.qml @@ -174,7 +174,7 @@ Item { readonly property int numColumns: model.numColumns readonly property int spacing: 12 - property Item draggedPad: null + property Item swapOriginPad: null QtObject { id: gridPrv @@ -245,10 +245,10 @@ Item { panelMode: percModel.currentPanelMode useNotationPreview: percModel.useNotationPreview - // When dragging, only show the outline for the dragged pad and the drag target... + // When swapping, only show the outline for the swap origin and the swap target... showEditOutline: percModel.currentPanelMode === PanelMode.EDIT_LAYOUT - && (!Boolean(padGrid.draggedPad) || padGrid.draggedPad === pad || pad.containsDrag) - showOriginBackground: pad.containsDrag || pad === padGrid.draggedPad + && (!Boolean(padGrid.swapOriginPad) || padGrid.swapOriginPad === pad || pad.containsDrag) + showOriginBackground: pad.containsDrag || pad === padGrid.swapOriginPad dragParent: root @@ -257,20 +257,20 @@ Item { padNavigation.panel: padsNavPanel footerNavigation.panel: padFootersNavPanel - onDragStarted: { - padGrid.draggedPad = pad - padGrid.model.startDrag(index) + onStartPadSwapRequested: { + padGrid.swapOriginPad = pad + padGrid.model.startPadSwap(index) } onDropped: function(dropEvent) { - padGrid.draggedPad = null - padGrid.model.endDrag(index) + padGrid.swapOriginPad = null + padGrid.model.endPadSwap(index) dropEvent.accepted = true } - onDragCancelled: { - padGrid.draggedPad = null - padGrid.model.endDrag(-1) + onCancelPadSwapRequested: { + padGrid.swapOriginPad = null + padGrid.model.endPadSwap(-1) } onHasActiveControlChanged: { @@ -282,22 +282,22 @@ Item { } states: [ - // If this is the drop target - move the draggable area to the origin of the dragged pad (preview the drop) + // If this is the swap target - move the swappable area to the swap origin (preview the swap) State { - name: "DROP_TARGET" - when: Boolean(padGrid.draggedPad) && pad.containsDrag && padGrid.draggedPad !== pad + name: "SWAP_TARGET" + when: Boolean(padGrid.swapOriginPad) && pad.containsDrag && padGrid.swapOriginPad !== pad ParentChange { - target: pad.draggableArea - parent: padGrid.draggedPad + target: pad.swappableArea + parent: padGrid.swapOriginPad } AnchorChanges { - target: pad.draggableArea - anchors.verticalCenter: padGrid.draggedPad.verticalCenter - anchors.horizontalCenter: padGrid.draggedPad.horizontalCenter + target: pad.swappableArea + anchors.verticalCenter: padGrid.swapOriginPad.verticalCenter + anchors.horizontalCenter: padGrid.swapOriginPad.horizontalCenter } // Origin background not needed for the dragged pad when a preview is taking place... PropertyChanges { - target: padGrid.draggedPad + target: padGrid.swapOriginPad showOriginBackground: false } } diff --git a/src/notation/qml/MuseScore/NotationScene/internal/PercussionPanelPad.qml b/src/notation/qml/MuseScore/NotationScene/internal/PercussionPanelPad.qml index 0865caafb2e1d..14e8c128cb62b 100644 --- a/src/notation/qml/MuseScore/NotationScene/internal/PercussionPanelPad.qml +++ b/src/notation/qml/MuseScore/NotationScene/internal/PercussionPanelPad.qml @@ -39,7 +39,7 @@ DropArea { property alias totalBorderWidth: padLoader.anchors.margins property alias showOriginBackground: originBackground.visible - property alias draggableArea: draggableArea + property alias swappableArea: swappableArea property int navigationRow: -1 property int navigationColumn: -1 @@ -49,8 +49,8 @@ DropArea { readonly property bool hasActiveControl: padNavCtrl.active || footerNavCtrl.active property var dragParent: null - signal dragStarted() - signal dragCancelled() + signal startPadSwapRequested() + signal cancelPadSwapRequested() QtObject { id: prv @@ -119,7 +119,7 @@ DropArea { } Rectangle { - id: draggableArea + id: swappableArea // Protrudes slightly from behind the components in the loader to produce the edit mode "border with gap" effect width: root.width @@ -135,18 +135,18 @@ DropArea { DragHandler { id: dragHandler - target: draggableArea + target: swappableArea enabled: Boolean(root.padModel) && root.panelMode === PanelMode.EDIT_LAYOUT dragThreshold: 0 // prevents the flickable from stealing drag events onActiveChanged: { if (dragHandler.active) { - root.dragStarted() + root.startPadSwapRequested() return } - if (!draggableArea.Drag.drop()) { - root.dragCancelled() + if (!swappableArea.Drag.drop()) { + root.cancelPadSwapRequested() } } } @@ -161,7 +161,7 @@ DropArea { anchors.fill: parent // Defined as 1 in the spec, but causes some aliasing in practice... - anchors.margins: 2 + draggableArea.border.width + anchors.margins: 2 + swappableArea.border.width // Can't simply use clip as this won't take into account radius... layer.enabled: ui.isEffectsAllowed @@ -169,7 +169,7 @@ DropArea { maskSource: Rectangle { width: padLoader.width height: padLoader.height - radius: draggableArea.radius - padLoader.anchors.margins + radius: swappableArea.radius - padLoader.anchors.margins } } @@ -185,7 +185,7 @@ DropArea { footerHeight: prv.footerHeight - dragActive: dragHandler.active + padSwapActive: dragHandler.active } } @@ -228,11 +228,11 @@ DropArea { name: "DRAGGED" when: dragHandler.active ParentChange { - target: draggableArea + target: swappableArea parent: root.dragParent } AnchorChanges { - target: draggableArea + target: swappableArea anchors.horizontalCenter: undefined anchors.verticalCenter: undefined } @@ -243,7 +243,7 @@ DropArea { name: "DROPPED" when: !dragHandler.active ParentChange { - target: draggableArea + target: swappableArea parent: root } } @@ -255,19 +255,19 @@ DropArea { anchors.fill: parent - radius: draggableArea.radius + radius: swappableArea.radius - border.color: draggableArea.border.color - border.width: draggableArea.border.width + border.color: swappableArea.border.color + border.width: swappableArea.border.width - color: draggableArea.color + color: swappableArea.color Rectangle { id: originBackgroundFill anchors.fill: parent anchors.margins: padLoader.anchors.margins - radius: draggableArea.radius - originBackgroundFill.anchors.margins + radius: swappableArea.radius - originBackgroundFill.anchors.margins color: root.containsDrag ? ui.theme.buttonColor : prv.enabledBackgroundColor } diff --git a/src/notation/qml/MuseScore/NotationScene/internal/PercussionPanelPadContent.qml b/src/notation/qml/MuseScore/NotationScene/internal/PercussionPanelPadContent.qml index 18130e79cfd9a..63b79e6a1a8f2 100644 --- a/src/notation/qml/MuseScore/NotationScene/internal/PercussionPanelPadContent.qml +++ b/src/notation/qml/MuseScore/NotationScene/internal/PercussionPanelPadContent.qml @@ -35,7 +35,7 @@ Column { property alias footerHeight: footerArea.height - property bool dragActive: false + property bool padSwapActive: false Rectangle { id: mainContentArea @@ -87,7 +87,7 @@ Column { states: [ State { name: "MOUSE_HOVERED" - when: mouseArea.containsMouse && !mouseArea.pressed && !root.dragActive + when: mouseArea.containsMouse && !mouseArea.pressed && !root.padSwapActive PropertyChanges { target: mainContentArea color: Utils.colorWithAlpha(ui.theme.accentColor, ui.theme.buttonOpacityHover) @@ -95,7 +95,7 @@ Column { }, State { name: "MOUSE_HIT" - when: mouseArea.pressed || root.dragActive + when: mouseArea.pressed || root.padSwapActive PropertyChanges { target: mainContentArea color: Utils.colorWithAlpha(ui.theme.accentColor, ui.theme.buttonOpacityHit) diff --git a/src/notation/view/percussionpanel/percussionpanelpadlistmodel.cpp b/src/notation/view/percussionpanel/percussionpanelpadlistmodel.cpp index bd85c341f217b..e64c1d2a4a569 100644 --- a/src/notation/view/percussionpanel/percussionpanelpadlistmodel.cpp +++ b/src/notation/view/percussionpanel/percussionpanelpadlistmodel.cpp @@ -106,19 +106,19 @@ bool PercussionPanelPadListModel::rowIsEmpty(int row) const return numEmptySlotsAtRow(row) == NUM_COLUMNS; } -void PercussionPanelPadListModel::startDrag(int startIndex) +void PercussionPanelPadListModel::startPadSwap(int startIndex) { - m_dragStartIndex = startIndex; + m_padSwapStartIndex = startIndex; } -void PercussionPanelPadListModel::endDrag(int endIndex) +void PercussionPanelPadListModel::endPadSwap(int endIndex) { - if (indexIsValid(m_dragStartIndex) && indexIsValid(endIndex)) { - movePad(m_dragStartIndex, endIndex); + if (indexIsValid(m_padSwapStartIndex) && indexIsValid(endIndex)) { + movePad(m_padSwapStartIndex, endIndex); } else { emit layoutChanged(); } - m_dragStartIndex = -1; + m_padSwapStartIndex = -1; } void PercussionPanelPadListModel::setDrumset(const engraving::Drumset* drumset) diff --git a/src/notation/view/percussionpanel/percussionpanelpadlistmodel.h b/src/notation/view/percussionpanel/percussionpanelpadlistmodel.h index 02cbba7cb780b..df4fb975c18af 100644 --- a/src/notation/view/percussionpanel/percussionpanelpadlistmodel.h +++ b/src/notation/view/percussionpanel/percussionpanelpadlistmodel.h @@ -56,8 +56,8 @@ class PercussionPanelPadListModel : public QAbstractListModel, public muse::asyn Q_INVOKABLE bool rowIsEmpty(int row) const; - Q_INVOKABLE void startDrag(int startIndex); - Q_INVOKABLE void endDrag(int endIndex); + Q_INVOKABLE void startPadSwap(int startIndex); + Q_INVOKABLE void endPadSwap(int endIndex); bool hasActivePads() const { return m_drumset; } @@ -97,7 +97,7 @@ class PercussionPanelPadListModel : public QAbstractListModel, public muse::asyn engraving::Drumset* m_drumset = nullptr; //! NOTE: Pointer may be invalid, see PercussionPanelModel::setUpConnections QList m_padModels; - int m_dragStartIndex = -1; + int m_padSwapStartIndex = -1; muse::async::Notification m_hasActivePadsChanged; muse::async::Channel m_triggeredChannel; From 15c15619334018b90aa8d1e9d2b3a60b1da693f9 Mon Sep 17 00:00:00 2001 From: Calum Matheson Date: Thu, 28 Nov 2024 17:17:32 +0000 Subject: [PATCH 4/4] Percussion panel - implement keyboard swapping --- .../NotationScene/PercussionPanel.qml | 44 ++++++++++++++--- .../internal/PercussionPanelPad.qml | 47 ++++++++++++++----- 2 files changed, 73 insertions(+), 18 deletions(-) diff --git a/src/notation/qml/MuseScore/NotationScene/PercussionPanel.qml b/src/notation/qml/MuseScore/NotationScene/PercussionPanel.qml index 0b58bfdb17795..c014a05e6f11b 100644 --- a/src/notation/qml/MuseScore/NotationScene/PercussionPanel.qml +++ b/src/notation/qml/MuseScore/NotationScene/PercussionPanel.qml @@ -58,6 +58,15 @@ Item { Component.onCompleted: { percModel.init() } + + onCurrentPanelModeChanged: { + // Cancel any active keyboard swaps when the panel mode changes + if (padGrid.isKeyboardSwapActive) { + padGrid.swapOriginPad = null + padGrid.isKeyboardSwapActive = false + padGrid.model.endPadSwap(-1) + } + } } // TODO: Will live inside percussion panel until #22050 is implemented @@ -122,6 +131,7 @@ Item { width: rowLayout.sideColumnsWidth visible: percModel.currentPanelMode === PanelMode.EDIT_LAYOUT + enabled: !padGrid.isKeyboardSwapActive Repeater { id: deleteRepeater @@ -175,6 +185,7 @@ Item { readonly property int spacing: 12 property Item swapOriginPad: null + property bool isKeyboardSwapActive: false QtObject { id: gridPrv @@ -247,9 +258,10 @@ Item { // When swapping, only show the outline for the swap origin and the swap target... showEditOutline: percModel.currentPanelMode === PanelMode.EDIT_LAYOUT - && (!Boolean(padGrid.swapOriginPad) || padGrid.swapOriginPad === pad || pad.containsDrag) - showOriginBackground: pad.containsDrag || pad === padGrid.swapOriginPad + && (!Boolean(padGrid.swapOriginPad) || padGrid.swapOriginPad === pad) + showOriginBackground: pad.containsDrag || (pad === padGrid.swapOriginPad && !padGrid.isKeyboardSwapActive) + panelHasActiveKeyboardSwap: padGrid.isKeyboardSwapActive dragParent: root navigationRow: index / padGrid.numColumns @@ -257,19 +269,21 @@ Item { padNavigation.panel: padsNavPanel footerNavigation.panel: padFootersNavPanel - onStartPadSwapRequested: { + onStartPadSwapRequested: function(isKeyboardSwap) { padGrid.swapOriginPad = pad + padGrid.isKeyboardSwapActive = isKeyboardSwap padGrid.model.startPadSwap(index) } - onDropped: function(dropEvent) { + onEndPadSwapRequested: { padGrid.swapOriginPad = null + padGrid.isKeyboardSwapActive = false padGrid.model.endPadSwap(index) - dropEvent.accepted = true } onCancelPadSwapRequested: { padGrid.swapOriginPad = null + padGrid.isKeyboardSwapActive = false padGrid.model.endPadSwap(-1) } @@ -285,7 +299,8 @@ Item { // If this is the swap target - move the swappable area to the swap origin (preview the swap) State { name: "SWAP_TARGET" - when: Boolean(padGrid.swapOriginPad) && pad.containsDrag && padGrid.swapOriginPad !== pad + when: Boolean(padGrid.swapOriginPad) && (pad.containsDrag || pad.padNavigationCtrl.active) && padGrid.swapOriginPad !== pad + ParentChange { target: pad.swappableArea parent: padGrid.swapOriginPad @@ -295,11 +310,27 @@ Item { anchors.verticalCenter: padGrid.swapOriginPad.verticalCenter anchors.horizontalCenter: padGrid.swapOriginPad.horizontalCenter } + PropertyChanges { + target: pad + showEditOutline: true + } + // Origin background not needed for the dragged pad when a preview is taking place... PropertyChanges { target: padGrid.swapOriginPad showOriginBackground: false } + + // In the case of a keyboard swap, we also need to move the origin pad + ParentChange { + target: padGrid.isKeyboardSwapActive && Boolean(padGrid.swapOriginPad) ? padGrid.swapOriginPad.swappableArea : null + parent: pad + } + AnchorChanges { + target: padGrid.isKeyboardSwapActive && Boolean(padGrid.swapOriginPad) ? padGrid.swapOriginPad.swappableArea : null + anchors.verticalCenter: pad.verticalCenter + anchors.horizontalCenter: pad.horizontalCenter + } } ] } @@ -323,6 +354,7 @@ Item { Layout.bottomMargin: (padGrid.cellHeight / 2) - (height / 2) visible: percModel.currentPanelMode === PanelMode.EDIT_LAYOUT + enabled: !padGrid.isKeyboardSwapActive icon: IconCode.PLUS text: qsTrc("notation", "Add row") diff --git a/src/notation/qml/MuseScore/NotationScene/internal/PercussionPanelPad.qml b/src/notation/qml/MuseScore/NotationScene/internal/PercussionPanelPad.qml index 14e8c128cb62b..fd1671b603119 100644 --- a/src/notation/qml/MuseScore/NotationScene/internal/PercussionPanelPad.qml +++ b/src/notation/qml/MuseScore/NotationScene/internal/PercussionPanelPad.qml @@ -48,10 +48,18 @@ DropArea { readonly property bool hasActiveControl: padNavCtrl.active || footerNavCtrl.active + property bool panelHasActiveKeyboardSwap: false + property var dragParent: null - signal startPadSwapRequested() + signal startPadSwapRequested(var isKeyboardSwap) + signal endPadSwapRequested() signal cancelPadSwapRequested() + onDropped: function(dropEvent) { + root.endPadSwapRequested() + dropEvent.accepted = true + } + QtObject { id: prv readonly property color enabledBackgroundColor: Utils.colorWithAlpha(ui.theme.buttonColor, ui.theme.buttonOpacityNormal) @@ -88,10 +96,11 @@ DropArea { accessible.enabled: padNavCtrl.enabled onTriggered: { - if (!Boolean(root.padModel)) { + if (Boolean(root.padModel) && root.panelMode !== PanelMode.EDIT_LAYOUT) { + root.padModel.triggerPad() return } - root.padModel.triggerPad() + root.panelHasActiveKeyboardSwap ? root.endPadSwapRequested() : root.startPadSwapRequested(true) } } @@ -136,13 +145,13 @@ DropArea { id: dragHandler target: swappableArea - enabled: Boolean(root.padModel) && root.panelMode === PanelMode.EDIT_LAYOUT + enabled: Boolean(root.padModel) && root.panelMode === PanelMode.EDIT_LAYOUT && !root.panelHasActiveKeyboardSwap dragThreshold: 0 // prevents the flickable from stealing drag events onActiveChanged: { if (dragHandler.active) { - root.startPadSwapRequested() + root.startPadSwapRequested(false) return } if (!swappableArea.Drag.drop()) { @@ -199,13 +208,6 @@ DropArea { } } - NavigationFocusBorder { - id: padFocusBorder - - padding: root.panelMode === PanelMode.EDIT_LAYOUT ? 0 : root.totalBorderWidth * -1 - navigationCtrl: padNavCtrl - } - NavigationFocusBorder { id: footerFocusBorder @@ -250,6 +252,27 @@ DropArea { ] } + //! NOTE: Ideally this would be swappableArea's child, but we don't want it to move when "previewing" a drop + NavigationFocusBorder { + id: padFocusBorder + + property real extraSize: root.showEditOutline ? padFocusBorder.border.width * 2 : 0 + property real extraRadius: root.showEditOutline ? root.totalBorderWidth + padFocusBorder.anchors.margins : 0 + + anchors { + fill: null + centerIn: parent + } + + width: swappableArea.width + padFocusBorder.extraSize + height: swappableArea.height + padFocusBorder.extraSize + + radius: swappableArea.radius + padFocusBorder.extraRadius + + padding: root.showEditOutline ? 0 : root.totalBorderWidth * -1 + navigationCtrl: padNavCtrl + } + Rectangle { id: originBackground