diff --git a/src/app/configs/data/shortcuts.xml b/src/app/configs/data/shortcuts.xml index 962f9345c0352..c9c01b6700bdb 100644 --- a/src/app/configs/data/shortcuts.xml +++ b/src/app/configs/data/shortcuts.xml @@ -600,6 +600,14 @@ add-hairpin-reverse Shift+. + + increase-dynamic + Alt+. + + + decrease-dynamic + Alt+, + time-delete Ctrl+Del diff --git a/src/app/configs/data/shortcuts_mac.xml b/src/app/configs/data/shortcuts_mac.xml index b75fcf42d3264..0c8ecce0ae8ca 100644 --- a/src/app/configs/data/shortcuts_mac.xml +++ b/src/app/configs/data/shortcuts_mac.xml @@ -603,6 +603,14 @@ add-hairpin-reverse Shift+. + + + increase-dynamic + Alt+. + + + decrease-dynamic + Alt+, time-delete diff --git a/src/notation/inotationinteraction.h b/src/notation/inotationinteraction.h index 1f86f5c71ee90..3c478b2d5db82 100644 --- a/src/notation/inotationinteraction.h +++ b/src/notation/inotationinteraction.h @@ -224,6 +224,9 @@ class INotationInteraction virtual void autoFlipHairpinsType(engraving::Dynamic* selDyn) = 0; virtual void toggleDynamicPopup() = 0; + virtual void increaseDecreaseDynamic(engraving::Dynamic* dynamic, int delta) = 0; + virtual void increaseDecreaseDynamicsForSelection(int delta) = 0; + virtual bool toggleLayoutBreakAvailable() const = 0; virtual void toggleLayoutBreak(LayoutBreakType breakType) = 0; virtual void moveMeasureToPrevSystem() = 0; diff --git a/src/notation/internal/notationinteraction.cpp b/src/notation/internal/notationinteraction.cpp index 1e8eadf0b0069..767a9a40eeddd 100644 --- a/src/notation/internal/notationinteraction.cpp +++ b/src/notation/internal/notationinteraction.cpp @@ -5764,6 +5764,82 @@ void NotationInteraction::addHairpinsToSelection(HairpinType type) } } +void NotationInteraction::increaseDecreaseDynamic(Dynamic* dynamic, int delta) +{ + const DynamicType currentType = dynamic->dynamicType(); + auto inRange = [](DynamicType type) { + return type >= DynamicType::PPPPP && type <= DynamicType::FFFFF; + }; + if (!inRange(currentType)) { + return; + } + DynamicType newType = static_cast(static_cast(currentType) + delta); + if (!inRange(newType)) { + return; + } + + dynamic->undoChangeProperty(Pid::DYNAMIC_TYPE, newType); + dynamic->undoChangeProperty(Pid::TEXT, Dynamic::dynamicText(newType)); + autoFlipHairpinsTypeImpl(dynamic); +} + +std::vector findDynamicsForElement(const EngravingItem* element) +{ + const ChordRest* cr = nullptr; + + if (element->isChordRest()) { + cr = toChordRest(element); + } else if (element->isNote()) { + auto parent = element->parent(); + if (parent && parent->isChordRest()) { + cr = toChordRest(parent); + } + } + + if (!cr) { + return {}; + } + + Segment* segment = cr->segment(); + + std::vector dynamics; + for (EngravingItem* item : segment->annotations()) { + if (!item->isDynamic()) { + continue; + } + + Dynamic* dynamic = toDynamic(item); + if (dynamic->elementAppliesToTrack(cr->track()) + && (dynamic->staffIdx() == cr->staffIdx() || dynamic->appliesToAllVoicesInInstrument())) { + dynamics.push_back(dynamic); + } + } + + return dynamics; +} + +void NotationInteraction::increaseDecreaseDynamicsForSelection(int delta) +{ + if (selection()->isNone()) { + return; + } + startEdit(TranslatableString("undoableAction", delta > 0 ? "Increase dynamics" : "Decrease dynamics")); + if (selection()->element() && !selection()->element()->isDynamic()) { + std::vector dynamics = findDynamicsForElement(selection()->element()); + for (Dynamic* dynamic : dynamics) { + increaseDecreaseDynamic(dynamic, delta); + } + } else { + for (EngravingItem* item : selection()->elements()) { + if (!item->isDynamic()) { + continue; + } + increaseDecreaseDynamic(toDynamic(item), delta); + } + } + apply(); +} + void NotationInteraction::putRestToSelection() { mu::engraving::InputState& is = score()->inputState(); @@ -6026,20 +6102,10 @@ void NotationInteraction::increaseDecreaseDuration(int steps, bool stepByDots) notifyAboutNotationChanged(); } -void NotationInteraction::autoFlipHairpinsType(Dynamic* selDyn) +void NotationInteraction::autoFlipHairpinsTypeImpl(Dynamic* selDyn) { - if (!selDyn) { - return; - } - - if (selDyn->dynamicType() == DynamicType::OTHER || selDyn->dynamicType() >= DynamicType::FP) { - return; - } - selDyn->findAdjacentHairpins(); - startEdit(TranslatableString("undoableAction", "Change hairpin type")); - if (Hairpin* leftHp = selDyn->leftHairpin()) { const Dynamic* startDyn = leftHp->dynamicSnappedBefore(); if (startDyn @@ -6065,6 +6131,20 @@ void NotationInteraction::autoFlipHairpinsType(Dynamic* selDyn) } } } +} + +void NotationInteraction::autoFlipHairpinsType(Dynamic* selDyn) +{ + if (!selDyn) { + return; + } + + if (selDyn->dynamicType() == DynamicType::OTHER || selDyn->dynamicType() >= DynamicType::FP) { + return; + } + + startEdit(TranslatableString("undoableAction", "Change hairpin type")); + autoFlipHairpinsTypeImpl(selDyn); apply(); } diff --git a/src/notation/internal/notationinteraction.h b/src/notation/internal/notationinteraction.h index 796b6dcb42937..6c258948cca4c 100644 --- a/src/notation/internal/notationinteraction.h +++ b/src/notation/internal/notationinteraction.h @@ -239,8 +239,12 @@ class NotationInteraction : public INotationInteraction, public muse::Contextabl void increaseDecreaseDuration(int steps, bool stepByDots) override; void autoFlipHairpinsType(engraving::Dynamic* selDyn) override; + void autoFlipHairpinsTypeImpl(engraving::Dynamic* selDyn); void toggleDynamicPopup() override; + void increaseDecreaseDynamic(engraving::Dynamic* dynamic, int delta) override; + void increaseDecreaseDynamicsForSelection(int delta) override; + bool toggleLayoutBreakAvailable() const override; void toggleLayoutBreak(LayoutBreakType breakType) override; void moveMeasureToPrevSystem() override; diff --git a/src/notation/tests/mocks/notationinteractionmock.h b/src/notation/tests/mocks/notationinteractionmock.h index 8269962de6366..09ac25cfe2a61 100644 --- a/src/notation/tests/mocks/notationinteractionmock.h +++ b/src/notation/tests/mocks/notationinteractionmock.h @@ -185,6 +185,7 @@ class NotationInteractionMock : public INotationInteraction MOCK_METHOD(void, autoFlipHairpinsType, (engraving::Dynamic * selDyn), (override)); MOCK_METHOD(void, toggleDynamicPopup, (), (override)); + MOCK_METHOD(void, increaseDecreaseDynamicsForSelection, (int), (override)); MOCK_METHOD(bool, toggleLayoutBreakAvailable, (), (const, override)); MOCK_METHOD(void, toggleLayoutBreak, (LayoutBreakType), (override)); MOCK_METHOD(void, moveMeasureToPrevSystem, (), (override)); diff --git a/src/notationscene/internal/notationactioncontroller.cpp b/src/notationscene/internal/notationactioncontroller.cpp index 19b59d361cb10..0caf036271990 100644 --- a/src/notationscene/internal/notationactioncontroller.cpp +++ b/src/notationscene/internal/notationactioncontroller.cpp @@ -341,6 +341,16 @@ void NotationActionController::init() registerAction("add-hairpin", &Interaction::addHairpinsToSelection, HairpinType::CRESC_HAIRPIN, &Controller::noteOrRestSelected); registerAction("add-hairpin-reverse", &Interaction::addHairpinsToSelection, HairpinType::DIM_HAIRPIN, &Controller::noteOrRestSelected); + + registerAction("increase-dynamic", [this]() { + currentNotationInteraction()->increaseDecreaseDynamicsForSelection( + 1); + }, &Controller::hasSelection); + registerAction("decrease-dynamic", [this]() { + currentNotationInteraction()->increaseDecreaseDynamicsForSelection( + -1); + }, &Controller::hasSelection); + registerAction("add-noteline", &Interaction::addAnchoredLineToSelectedNotes); registerAction("add-image", [this]() { addImage(); }); diff --git a/src/notationscene/internal/notationuiactions.cpp b/src/notationscene/internal/notationuiactions.cpp index c808570d547d2..728694c40b7ae 100644 --- a/src/notationscene/internal/notationuiactions.cpp +++ b/src/notationscene/internal/notationuiactions.cpp @@ -1263,6 +1263,18 @@ const UiActionList NotationUiActions::s_actions = { TranslatableString("action", "&Diminuendo"), TranslatableString("action", "Add hairpin: diminuendo") ), + UiAction("increase-dynamic", + mu::context::UiCtxProjectOpened, + mu::context::CTX_NOTATION_OPENED, + TranslatableString("action", "Increase dynamics"), + TranslatableString("action", "Increase selected dynamics") + ), + UiAction("decrease-dynamic", + mu::context::UiCtxProjectOpened, + mu::context::CTX_NOTATION_OPENED, + TranslatableString("action", "Decrease dynamics"), + TranslatableString("action", "Decrease selected dynamics") + ), UiAction("add-noteline", mu::context::UiCtxProjectOpened, mu::context::CTX_NOTATION_OPENED,