diff --git a/src/notation/inotationinteraction.h b/src/notation/inotationinteraction.h index 93130230f226b..44b9033adbb73 100644 --- a/src/notation/inotationinteraction.h +++ b/src/notation/inotationinteraction.h @@ -45,8 +45,10 @@ class INotationInteraction // Shadow note virtual bool showShadowNote(const muse::PointF& pos) = 0; + virtual void showShadowNoteForMidiPitch(const uint8_t note) = 0; virtual void hideShadowNote() = 0; virtual muse::RectF shadowNoteRect() const = 0; + virtual muse::async::Channel pianoKeyboardShadowNoteChanged() const = 0; // Visibility virtual void toggleVisible() = 0; diff --git a/src/notation/internal/notationinteraction.cpp b/src/notation/internal/notationinteraction.cpp index 1ec1c53defa16..c9f2a1161dacd 100644 --- a/src/notation/internal/notationinteraction.cpp +++ b/src/notation/internal/notationinteraction.cpp @@ -78,6 +78,7 @@ #include "engraving/dom/textedit.h" #include "engraving/dom/tuplet.h" #include "engraving/dom/undo.h" +#include "engraving/dom/utils.h" #include "engraving/compat/dummyelement.h" #include "engraving/rw/xmlreader.h" @@ -338,6 +339,63 @@ bool NotationInteraction::showShadowNote(const PointF& pos) return false; } + updateShadowNoteProperties(position); + + return true; +} + +void NotationInteraction::showShadowNoteForMidiPitch(const uint8_t key) +{ + const mu::engraving::InputState& inputState = score()->inputState(); + + if (!inputState.isValid()) { + return; + } + + mu::engraving::ShadowNote& shadowNote = *score()->shadowNote(); + Segment* inputSegment = inputState.segment(); + staff_idx_t staffIdx = inputState.track() / VOICES; + const Staff* inputStaff = score()->staff(staffIdx); + const Fraction tick = inputSegment->tick(); + const double mag = inputStaff->staffMag(tick); + double lineDist = inputStaff->staffType(tick)->lineDistance().val() + * (inputStaff->isTabStaff(tick) ? 1 : .5) + * mag + * score()->style().spatium(); + int rLine = 0; + if (inputStaff->isDrumStaff(tick)) { + // different behavior? ignoring for now + hideShadowNote(); + return; + } else if (inputStaff->isTabStaff(tick)) { + // different behavior? ignoring for now + hideShadowNote(); + return; + } else { + int octave = key / 12; + int line = octave * 7 + mu::engraving::pitch2step(key); + ClefType clef = inputStaff->clef(tick); + rLine = mu::engraving::relStep(line, clef); + + shadowNote.setStaffIdx(staffIdx); + shadowNote.setLineIndex(rLine); + } + + double xPos = inputSegment->canvasPos().x(); + double yPos = inputSegment->system()->staffCanvasYpage(staffIdx) + rLine * lineDist; + + PointF p = { xPos, yPos }; + + Position position = { inputSegment, staffIdx, rLine, mu::engraving::INVALID_FRET_INDEX, p }; + updateShadowNoteProperties(position); + + m_pianoKeyboardShadowNoteChanged.send(true); +} + +void NotationInteraction::updateShadowNoteProperties(Position& position) +{ + const mu::engraving::InputState& inputState = score()->inputState(); + mu::engraving::ShadowNote& shadowNote = *score()->shadowNote(); Staff* staff = score()->staff(position.staffIdx); const mu::engraving::Instrument* instr = staff->part()->instrument(); @@ -410,13 +468,12 @@ bool NotationInteraction::showShadowNote(const PointF& pos) score()->renderer()->layoutItem(&shadowNote); shadowNote.setPos(position.pos); - - return true; } void NotationInteraction::hideShadowNote() { score()->shadowNote()->setVisible(false); + m_pianoKeyboardShadowNoteChanged.send(false); } RectF NotationInteraction::shadowNoteRect() const @@ -440,6 +497,11 @@ RectF NotationInteraction::shadowNoteRect() const return rect; } +muse::async::Channel NotationInteraction::pianoKeyboardShadowNoteChanged() const +{ + return m_pianoKeyboardShadowNoteChanged; +} + void NotationInteraction::toggleVisible() { startEdit(); diff --git a/src/notation/internal/notationinteraction.h b/src/notation/internal/notationinteraction.h index 6746ddd51abe1..fb6fe8be437fb 100644 --- a/src/notation/internal/notationinteraction.h +++ b/src/notation/internal/notationinteraction.h @@ -65,8 +65,10 @@ class NotationInteraction : public INotationInteraction, public muse::Injectable // Shadow note bool showShadowNote(const muse::PointF& pos) override; + void showShadowNoteForMidiPitch(const uint8_t note) override; void hideShadowNote() override; muse::RectF shadowNoteRect() const override; + muse::async::Channel pianoKeyboardShadowNoteChanged() const override; // Visibility void toggleVisible() override; @@ -395,6 +397,8 @@ class NotationInteraction : public INotationInteraction, public muse::Injectable template void execute(void (mu::engraving::Score::* function)(P), P param); + void updateShadowNoteProperties(mu::engraving::Position& position); + struct HitMeasureData { Measure* measure = nullptr; @@ -445,6 +449,7 @@ class NotationInteraction : public INotationInteraction, public muse::Injectable DropData m_dropData; muse::async::Notification m_dropChanged; + muse::async::Channel m_pianoKeyboardShadowNoteChanged; muse::async::Channel m_scoreConfigChanged; engraving::BspTree m_droppableTree; diff --git a/src/notation/tests/mocks/notationinteractionmock.h b/src/notation/tests/mocks/notationinteractionmock.h index d6b720307a7f3..8613c9ea6abac 100644 --- a/src/notation/tests/mocks/notationinteractionmock.h +++ b/src/notation/tests/mocks/notationinteractionmock.h @@ -33,8 +33,10 @@ class NotationInteractionMock : public INotationInteraction MOCK_METHOD(INotationNoteInputPtr, noteInput, (), (const, override)); MOCK_METHOD(bool, showShadowNote, (const muse::PointF&), (override)); + MOCK_METHOD(void, showShadowNoteForMidiPitch, (const uint8_t), (override)); MOCK_METHOD(void, hideShadowNote, (), (override)); MOCK_METHOD(muse::RectF, shadowNoteRect, (), (const, override)); + MOCK_METHOD(muse::async::Channel, pianoKeyboardShadowNoteChanged, (), (const, override)); MOCK_METHOD(void, toggleVisible, (), (override)); diff --git a/src/notation/view/abstractnotationpaintview.cpp b/src/notation/view/abstractnotationpaintview.cpp index 45a53f6e2c932..9983092e1c2f9 100644 --- a/src/notation/view/abstractnotationpaintview.cpp +++ b/src/notation/view/abstractnotationpaintview.cpp @@ -277,6 +277,26 @@ void AbstractNotationPaintView::onLoadNotation(INotationPtr) } }); + interaction->pianoKeyboardShadowNoteChanged().onReceive(this, [this](bool visible) { + if (m_shadowNoteRect.isValid()) { + scheduleRedraw(m_shadowNoteRect); + + if (!visible) { + m_shadowNoteRect = RectF(); + return; + } + } + + RectF shadowNoteRect = fromLogical(notationInteraction()->shadowNoteRect()); + + if (shadowNoteRect.isValid()) { + compensateFloatPart(shadowNoteRect); + scheduleRedraw(shadowNoteRect); + } + + m_shadowNoteRect = shadowNoteRect; + }); + updateLoopMarkers(); notationPlayback()->loopBoundariesChanged().onNotify(this, [this]() { updateLoopMarkers(); diff --git a/src/notation/view/pianokeyboard/pianokeyboardcontroller.cpp b/src/notation/view/pianokeyboard/pianokeyboardcontroller.cpp index 66c8901d75508..727e21bd6cb80 100644 --- a/src/notation/view/pianokeyboard/pianokeyboardcontroller.cpp +++ b/src/notation/view/pianokeyboard/pianokeyboardcontroller.cpp @@ -87,6 +87,22 @@ void PianoKeyboardController::setPressedKey(std::optional key) m_keyStatesChanged.notify(); } +void PianoKeyboardController::setHoveredKey(std::optional key) +{ + auto notation = currentNotation(); + if (m_hoveredKey == key) { + return; + } + + if (notation && notation->interaction()->noteInput()->isNoteInputMode() && key.has_value()) { + notation->interaction()->showShadowNoteForMidiPitch(key.value()); + } else { + notation->interaction()->hideShadowNote(); + } + + m_hoveredKey = key; +} + void PianoKeyboardController::onNotationChanged() { if (auto notation = currentNotation()) { diff --git a/src/notation/view/pianokeyboard/pianokeyboardcontroller.h b/src/notation/view/pianokeyboard/pianokeyboardcontroller.h index 140ead36b72b2..4143e33418bf2 100644 --- a/src/notation/view/pianokeyboard/pianokeyboardcontroller.h +++ b/src/notation/view/pianokeyboard/pianokeyboardcontroller.h @@ -39,6 +39,7 @@ class PianoKeyboardController : public muse::Injectable, public muse::async::Asy std::optional pressedKey() const; void setPressedKey(std::optional key); + void setHoveredKey(std::optional key); KeyState keyState(piano_key_t key) const; muse::async::Notification keyStatesChanged() const; @@ -55,6 +56,7 @@ class PianoKeyboardController : public muse::Injectable, public muse::async::Asy void sendNoteOff(piano_key_t key); std::optional m_pressedKey = std::nullopt; + std::optional m_hoveredKey = std::nullopt; std::unordered_set m_keys; std::unordered_set m_otherNotesInChord; diff --git a/src/notation/view/pianokeyboard/pianokeyboardview.cpp b/src/notation/view/pianokeyboard/pianokeyboardview.cpp index 31406afad39da..81db18ec1fe3d 100644 --- a/src/notation/view/pianokeyboard/pianokeyboardview.cpp +++ b/src/notation/view/pianokeyboard/pianokeyboardview.cpp @@ -56,6 +56,7 @@ PianoKeyboardView::PianoKeyboardView(QQuickItem* parent) : muse::uicomponents::QuickPaintedView(parent), muse::Injectable(muse::iocCtxForQmlObject(this)) { setAcceptedMouseButtons(Qt::LeftButton); + setAcceptHoverEvents(true); } PianoKeyboardView::~PianoKeyboardView() @@ -552,3 +553,21 @@ void PianoKeyboardView::mouseReleaseEvent(QMouseEvent*) { m_controller->setPressedKey(std::nullopt); } + +void PianoKeyboardView::hoverMoveEvent(QHoverEvent* event) +{ + QPointF oldPos = event->oldPosF(); + QPointF pos = event->position(); + + if (oldPos == pos) { + return; + } + + m_controller->setHoveredKey(keyAt(event->position())); +} + +void PianoKeyboardView::hoverLeaveEvent(QHoverEvent* event) +{ + m_controller->setHoveredKey(std::nullopt); + event->accept(); +} diff --git a/src/notation/view/pianokeyboard/pianokeyboardview.h b/src/notation/view/pianokeyboard/pianokeyboardview.h index d208d447d7ea2..354e6317a93e3 100644 --- a/src/notation/view/pianokeyboard/pianokeyboardview.h +++ b/src/notation/view/pianokeyboard/pianokeyboardview.h @@ -91,6 +91,9 @@ class PianoKeyboardView : public muse::uicomponents::QuickPaintedView, public mu void mouseMoveEvent(QMouseEvent* event) override; void mouseReleaseEvent(QMouseEvent* event) override; + void hoverMoveEvent(QHoverEvent* event) override; + void hoverLeaveEvent(QHoverEvent* event) override; + std::optional keyAt(const QPointF& position) const; static constexpr piano_key_t MIN_KEY = 0;