Skip to content

Commit

Permalink
Percussion panel - implement footer context menu
Browse files Browse the repository at this point in the history
Also includes a small refactor to PercussionPanelModel::layoutMenuItems (fixed translatable strings and simplified checkables)
  • Loading branch information
mathesoncalum committed Jan 3, 2025
1 parent 7335c37 commit 89156ff
Show file tree
Hide file tree
Showing 8 changed files with 282 additions and 52 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -121,10 +121,6 @@ DropArea {

accessible.visualItem: footerFocusBorder
accessible.enabled: footerNavCtrl.enabled

onTriggered: {
// TODO: trigger context menu (not yet implemented)
}
}

Rectangle {
Expand Down Expand Up @@ -188,13 +184,22 @@ DropArea {
id: padContentComponent

PercussionPanelPadContent {
id: padContent

padModel: root.padModel
panelMode: root.panelMode
useNotationPreview: root.useNotationPreview

footerHeight: prv.footerHeight

padSwapActive: dragHandler.active

Connections {
target: footerNavCtrl
function onTriggered() {
padContent.openFooterContextMenu()
}
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,13 @@ Column {

property bool padSwapActive: false

function openFooterContextMenu() {
if (!root.padModel) {
return
}
menuLoader.toggleOpened(root.padModel.footerContextMenuItems)
}

Item {
id: mainContentArea

Expand Down Expand Up @@ -154,6 +161,17 @@ Column {

color: Utils.colorWithAlpha(ui.theme.buttonColor, ui.theme.buttonOpacityNormal)

MouseArea {
id: footerMouseArea

anchors.fill: parent
enabled: root.panelMode !== PanelMode.EDIT_LAYOUT

onClicked: {
root.openFooterContextMenu()
}
}

StyledTextLabel {
id: shortcutLabel

Expand Down Expand Up @@ -190,5 +208,13 @@ Column {

text: Boolean(root.padModel) ? root.padModel.midiNote : ""
}

StyledMenuLoader {
id: menuLoader

onHandleMenuItem: function(itemId) {
root.padModel.handleMenuItem(itemId)
}
}
}
}
166 changes: 125 additions & 41 deletions src/notation/view/percussionpanel/percussionpanelmodel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -125,28 +125,24 @@ void PercussionPanelModel::init()

QList<QVariantMap> PercussionPanelModel::layoutMenuItems() const
{
const TranslatableString padNamesTitle("notation/percussion", "Pad names");
// Using IconCode for this instead of "checked" because we want the tick to display on the left
const int padNamesIcon = static_cast<int>(m_useNotationPreview ? IconCode::Code::NONE : IconCode::Code::TICK_RIGHT_ANGLE);
static const auto padNamesTitle = TranslatableString("notation/percussion", "Pad names");

const TranslatableString notationPreviewTitle("notation/percussion", "Notation preview");
// Using IconCode for this instead of "checked" because we want the tick to display on the left
const int notationPreviewIcon = static_cast<int>(m_useNotationPreview ? IconCode::Code::TICK_RIGHT_ANGLE : IconCode::Code::NONE);
static const auto notationPreviewTitle = TranslatableString("notation/percussion", "Notation preview");

const TranslatableString editLayoutTitle = m_currentPanelMode == PanelMode::Mode::EDIT_LAYOUT
? TranslatableString("notation/percussion", "Finish editing")
: TranslatableString("notation/percussion", "Edit layout");
const int editLayoutIcon = static_cast<int>(IconCode::Code::CONFIGURE);
static const auto editLayoutTitle = m_currentPanelMode == PanelMode::Mode::EDIT_LAYOUT
? TranslatableString("notation/percussion", "Finish editing")
: TranslatableString("notation/percussion", "Edit layout");
static constexpr int editLayoutIcon = static_cast<int>(IconCode::Code::CONFIGURE);

const TranslatableString resetLayoutTitle("notation/percussion", "Reset layout");
const int resetLayoutIcon = static_cast<int>(IconCode::Code::UNDO);
static const auto resetLayoutTitle = TranslatableString("notation/percussion", "Reset layout");
static constexpr int resetLayoutIcon = static_cast<int>(IconCode::Code::UNDO);

QList<QVariantMap> menuItems = {
{ { "id", PAD_NAMES_CODE },
{ "title", padNamesTitle.qTranslated() }, { "icon", padNamesIcon }, { "enabled", true } },
{ { "id", PAD_NAMES_CODE }, { "title", padNamesTitle.qTranslated() },
{ "checkable", true }, { "checked", !m_useNotationPreview }, { "enabled", true } },

{ { "id", NOTATION_PREVIEW_CODE },
{ "title", notationPreviewTitle.qTranslated() }, { "icon", notationPreviewIcon }, { "enabled", true } },
{ { "id", NOTATION_PREVIEW_CODE }, { "title", notationPreviewTitle.qTranslated() },
{ "checkable", true }, { "checked", m_useNotationPreview }, { "enabled", true } },

{ }, // separator

Expand Down Expand Up @@ -185,16 +181,11 @@ void PercussionPanelModel::finishEditing(bool discardChanges)

m_padListModel->removeEmptyRows();

NoteInputState inputState = interaction()->noteInput()->state();
const Staff* staff = inputState.staff;

IF_ASSERT_FAILED(staff && staff->part()) {
return;
}

Instrument* inst = staff->part()->instrument(inputState.segment->tick());
const std::pair<Instrument*, Part*> instAndPart = getCurrentInstrumentAndPart();
Instrument* inst = instAndPart.first;
Part* part = instAndPart.second;

IF_ASSERT_FAILED(inst && inst->drumset()) {
IF_ASSERT_FAILED(inst && inst->drumset() && part) {
return;
}

Expand Down Expand Up @@ -234,7 +225,7 @@ void PercussionPanelModel::finishEditing(bool discardChanges)
INotationUndoStackPtr undoStack = notation()->undoStack();

undoStack->prepareChanges(muse::TranslatableString("undoableAction", "Edit percussion panel layout"));
score()->undo(new engraving::ChangeDrumset(inst, &updatedDrumset, staff->part()));
score()->undo(new engraving::ChangeDrumset(inst, &updatedDrumset, part));
undoStack->commitChanges();

setCurrentPanelMode(m_panelModeToRestore);
Expand Down Expand Up @@ -283,11 +274,22 @@ void PercussionPanelModel::setUpConnections()
setEnabled(m_padListModel->hasActivePads());
});

m_padListModel->padTriggered().onReceive(this, [this](int pitch) {
switch (currentPanelMode()) {
case PanelMode::Mode::EDIT_LAYOUT: return;
case PanelMode::Mode::WRITE: writePitch(pitch); // fall through
case PanelMode::Mode::SOUND_PREVIEW: playPitch(pitch);
m_padListModel->padActionRequested().onReceive(this, [this](PercussionPanelPadModel::PadAction action, int pitch) {
switch (action) {
case PercussionPanelPadModel::PadAction::TRIGGER:
onPadTriggered(pitch);
break;
case PercussionPanelPadModel::PadAction::DUPLICATE:
onDuplicatePadRequested(pitch);
break;
case PercussionPanelPadModel::PadAction::DELETE:
onDeletePadRequested(pitch);
break;
case PercussionPanelPadModel::PadAction::DEFINE_SHORTCUT:
onDefinePadShortcutRequested(pitch);
break;
default:
break;
}
});

Expand Down Expand Up @@ -338,6 +340,76 @@ bool PercussionPanelModel::eventFilter(QObject* watched, QEvent* event)
return true;
}

void PercussionPanelModel::onPadTriggered(int pitch)
{
switch (currentPanelMode()) {
case PanelMode::Mode::EDIT_LAYOUT: return;
case PanelMode::Mode::WRITE: writePitch(pitch); // fall through
case PanelMode::Mode::SOUND_PREVIEW: playPitch(pitch);
default: break;
}
}

void PercussionPanelModel::onDuplicatePadRequested(int pitch)
{
const std::pair<Instrument*, Part*> instAndPart = getCurrentInstrumentAndPart();
Instrument* inst = instAndPart.first;
Part* part = instAndPart.second;

IF_ASSERT_FAILED(inst && part) {
return;
}

const int nextAvailablePitch = m_padListModel->nextAvailablePitch(pitch);
if (nextAvailablePitch < 0) {
// TODO: Show some sort of warning dialog?
LOGE() << "No space to duplicate drum pad";
return;
}
const int nextAvailableIndex = m_padListModel->nextAvailableIndex(pitch);

Drumset updatedDrumset = *m_padListModel->drumset();

engraving::DrumInstrument duplicateDrum = updatedDrumset.drum(pitch);

duplicateDrum.shortcut = '\0'; // Don't steal the shortcut
duplicateDrum.panelRow = nextAvailableIndex / m_padListModel->numColumns();
duplicateDrum.panelColumn = nextAvailableIndex % m_padListModel->numColumns();

updatedDrumset.setDrum(nextAvailablePitch, duplicateDrum);

INotationUndoStackPtr undoStack = notation()->undoStack();

undoStack->prepareChanges(muse::TranslatableString("undoableAction", "Duplicate percussion panel pad"));
score()->undo(new engraving::ChangeDrumset(inst, &updatedDrumset, part));
undoStack->commitChanges();
}

void PercussionPanelModel::onDeletePadRequested(int pitch)
{
const std::pair<Instrument*, Part*> instAndPart = getCurrentInstrumentAndPart();
Instrument* inst = instAndPart.first;
Part* part = instAndPart.second;

IF_ASSERT_FAILED(inst && part) {
return;
}

Drumset updatedDrumset = *m_padListModel->drumset();
updatedDrumset.setDrum(pitch, engraving::DrumInstrument());

INotationUndoStackPtr undoStack = notation()->undoStack();

undoStack->prepareChanges(muse::TranslatableString("undoableAction", "Delete percussion panel pad"));
score()->undo(new engraving::ChangeDrumset(inst, &updatedDrumset, part));
undoStack->commitChanges();
}

void PercussionPanelModel::onDefinePadShortcutRequested(int)
{
// TODO: Design in progress...
}

void PercussionPanelModel::writePitch(int pitch)
{
INotationUndoStackPtr undoStack = notation()->undoStack();
Expand Down Expand Up @@ -388,16 +460,11 @@ void PercussionPanelModel::resetLayout()
finishEditing(/*discardChanges*/ true);
}

NoteInputState inputState = interaction()->noteInput()->state();
const Staff* staff = inputState.staff;

IF_ASSERT_FAILED(staff && staff->part()) {
return;
}

Instrument* inst = staff->part()->instrument(inputState.segment->tick());
const std::pair<Instrument*, Part*> instAndPart = getCurrentInstrumentAndPart();
Instrument* inst = instAndPart.first;
Part* part = instAndPart.second;

IF_ASSERT_FAILED(inst) {
IF_ASSERT_FAILED(inst && inst->drumset() && part) {
return;
}

Expand All @@ -417,7 +484,7 @@ void PercussionPanelModel::resetLayout()
INotationUndoStackPtr undoStack = notation()->undoStack();

undoStack->prepareChanges(muse::TranslatableString("undoableAction", "Reset percussion panel layout"));
score()->undo(new engraving::ChangeDrumset(inst, &defaultLayout, staff->part()));
score()->undo(new engraving::ChangeDrumset(inst, &defaultLayout, part));
undoStack->commitChanges();
}

Expand All @@ -437,6 +504,23 @@ InstrumentTrackId PercussionPanelModel::currentTrackId() const
return { staff->part()->id(), staff->part()->instrumentId(inputState.segment->tick()) };
}

std::pair<mu::engraving::Instrument*, mu::engraving::Part*> PercussionPanelModel::getCurrentInstrumentAndPart() const
{
if (!interaction()) {
return { nullptr, nullptr };
}

NoteInputState inputState = interaction()->noteInput()->state();

const Staff* staff = inputState.staff;

Part* part = staff ? staff->part() : nullptr;

Instrument* inst = part ? part->instrument(inputState.segment->tick()) : nullptr;

return { inst, part };
}

const project::IProjectAudioSettingsPtr PercussionPanelModel::audioSettings() const
{
return globalContext()->currentProject() ? globalContext()->currentProject()->audioSettings() : nullptr;
Expand Down
8 changes: 8 additions & 0 deletions src/notation/view/percussionpanel/percussionpanelmodel.h
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,11 @@ class PercussionPanelModel : public QObject, public muse::Injectable, public mus

bool eventFilter(QObject* watched, QEvent* event) override;

void onPadTriggered(int pitch);
void onDuplicatePadRequested(int pitch);
void onDeletePadRequested(int pitch);
void onDefinePadShortcutRequested(int pitch);

void writePitch(int pitch);
void playPitch(int pitch);

Expand All @@ -121,6 +126,9 @@ class PercussionPanelModel : public QObject, public muse::Injectable, public mus
mu::engraving::InstrumentTrackId currentTrackId() const;

const project::IProjectAudioSettingsPtr audioSettings() const;

std::pair<mu::engraving::Instrument*, mu::engraving::Part*> getCurrentInstrumentAndPart() const;

const mu::notation::INotationPtr notation() const;
const mu::notation::INotationInteractionPtr interaction() const;

Expand Down
Loading

0 comments on commit 89156ff

Please sign in to comment.