Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GSoC '24 - Dynamics Popup #23038

Draft
wants to merge 26 commits into
base: master
Choose a base branch
from
Draft

Conversation

re1san
Copy link
Contributor

@re1san re1san commented May 31, 2024

Pull request for Dynamics Popup Project.

Current Status

  • Implemented the UI

  • Added functionality to change the dynamic from popup
  • Added functionality to draw hairpin when either grip is dragged
  • Grips are not shown if the dynamic already has a hairpin on either side or if it's tick is zero like fff

  • Added shortcut to open the popup


  • I signed the CLA
  • The title of the PR describes the problem it addresses
  • Each commit's message describes its purpose and effects, and references the issue it resolves
  • If changes are extensive, there is a sequence of easily reviewable commits
  • The code in the PR follows the coding rules
  • There are no unnecessary changes
  • The code compiles and runs on my machine, preferably after each commit individually
  • I created a unit test or vtest to verify the changes I made (if applicable)

@cbjeukendrup cbjeukendrup marked this pull request as draft May 31, 2024 02:38
@re1san re1san force-pushed the gsoc_dynamics_popup branch 4 times, most recently from 5904da1 to 0565ec7 Compare June 13, 2024 08:50
@cbjeukendrup cbjeukendrup added the GSoC PRs created by GSoC participants during coding period label Jun 16, 2024
Copy link
Contributor

@cbjeukendrup cbjeukendrup left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

First round of code review! I see a lot of good things, which is nice. And I also have a few suggestions to make it even better.

src/notation/view/internal/dynamicpopupmodel.h Outdated Show resolved Hide resolved
src/notation/view/internal/dynamicpopupmodel.h Outdated Show resolved Hide resolved
src/notation/view/internal/dynamicpopupmodel.h Outdated Show resolved Hide resolved
src/notation/view/internal/dynamicpopupmodel.cpp Outdated Show resolved Hide resolved
src/notation/view/internal/dynamicpopupmodel.h Outdated Show resolved Hide resolved
src/notation/view/internal/dynamicpopupmodel.cpp Outdated Show resolved Hide resolved
@avvvvve

This comment was marked as resolved.

@re1san

This comment was marked as resolved.

@diedeno

This comment was marked as resolved.

@re1san

This comment was marked as resolved.

@re1san re1san force-pushed the gsoc_dynamics_popup branch 3 times, most recently from cf50bc6 to e081307 Compare August 13, 2024 05:48
…mics (for SFF dynamic as it doesnot have a SymId)
Copy link
Contributor

@cbjeukendrup cbjeukendrup left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here is a mostly complete review of the changes in src/engraving. @mike-spa May I ask you to check the remaining methods?

Comment on lines +733 to +749
// Right grip (when two grips)
if (int(ed.curGrip) == 1 && hasLeftGrip() && hasRightGrip()) {
m_rightDragOffset += ed.evtDelta.x();
if (rightDragOffset() < 0) {
m_rightDragOffset = 0;
}
return;
}

// Right grip (when single grip)
if (int(ed.curGrip) == 0 && !hasLeftGrip() && hasRightGrip()) {
m_rightDragOffset += ed.evtDelta.x();
if (rightDragOffset() < 0) {
m_rightDragOffset = 0;
}
return;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like these can be consolidated into one if statement, like

if ((int(ed.curGrip) == 1 && hasLeftGrip() && hasRightGrip())
    || (int(ed.curGrip) == 0 && !hasLeftGrip() && hasRightGrip())) {

Also, instead of calling has…Grip again and again, you might want to call them once and use a variable, like this:

    const bool hasLeftGrip = this->hasLeftGrip();

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup, will do that

// findAdjacentHairpins
//---------------------------------------------------------

void Dynamic::findAdjacentHairpins()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mike-spa Please check this method

}
}

return;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant return statement

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, I forgot to remove it


if (hasLeftGrip() && hasRightGrip()) {
return 2;
} else if (hasLeftGrip() ^ hasRightGrip()) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The && case has already been checked, so no need to use xor here. The use of ^ is a bit questionable because officially it's a bitwise operator and not a boolean operator.

Suggested change
} else if (hasLeftGrip() ^ hasRightGrip()) {
} else if (hasLeftGrip() || hasRightGrip()) {

Also, the same applies about not calling has…Grip again and again.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it, will use OR here

Comment on lines +1064 to +1065
PointF leftOffset(-ldata->bbox().width() / 2 - md + m_leftDragOffset, -11.408);
PointF rightOffset(ldata->bbox().width() / 2 + md + m_rightDragOffset, -11.408);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The number -11.408 is what we call a "magic number". When a cryptic hardcoded number is necessary, it is usually a good idea to extract it into a named constant, sometimes accompanied by a comment about how this number was found.
But in this case, I think it should not be a hardcoded number, but computed based on the height of the Dynamic, or perhaps some other parameter like the y-value of the visual center. It should namely also work well with different fonts, font sizes, spatium values.
@mike-spa Could you suggest a good way to compute the vertical grip positions?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@cbjeukendrup I calculated it by subtracting the pagePos of the dynamic from the position of Grip::START from HairpinSegment::gripsPositions and it turned out to be a constant value. I wasn't sure how this could be computed, so I hardcoded it.

@@ -25,6 +25,8 @@

#include "textbase.h"

#include "draw/painter.h"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of including the header here, you could include it in the cpp file, and here in the header file only write

namespace muse::draw {
class Painter;
}

}
textBox = Factory::createDynamic(dummy()->segment());
chordRest->undoAddAnnotation(textBox);
textBox->setFlag(ElementFlag::IS_PREVIEW, true);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be better to set this flag in the popup code. The popup is namely the only thing that will create a preview dynamic, so it would be good to keep the code that sets the flag close to the code that creates the preview dynamic.

(Also, when splitting this PR into smaller PRs, you'll need to do that anyway, because in the PR about the popup, this code won't exist yet 🙂)

@@ -3870,6 +3880,33 @@ void Score::addHairpinToDynamic(Hairpin* hairpin, Dynamic* dynamic)
undoAddElement(hairpin);
}

void Score::addHairpinOnGripDrag(Hairpin* hairpin, Dynamic* dynamic, const PointF& pos, Dynamic::Grip grip)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mike-spa Please check this method

Comment on lines +3318 to +3319
// Don't draw cursor if it's dynamic or if there is a selection
if (!ed.element->isDynamic() && !cursor->hasSelection()) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is questionable; for existing scores, dynamics may also contain normal text besides the dynamic symbol. So the user should also be able to edit that normal text, so a text cursor would be required.

Comment on lines +3327 to +3334
RectF r;

if (ed.element->isDynamic()) {
r = toDynamic(ed.element)->adjustedBoundingRect();
} else {
double m = spatium();
r = canvasBoundingRect().adjusted(-m, -m, m, m);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here applies what I said about checking whether we really need the adjustedBoundingRect special case

Copy link
Contributor

@cbjeukendrup cbjeukendrup left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here is everything for notation/view

@@ -138,7 +138,11 @@ void NotationViewInputController::onNotationChanged()
m_view->hideElementPopup();

if (AbstractElementPopupModel::supportsPopup(type)) {
m_view->showElementPopup(type, selectedItem->canvasBoundingRect());
if (selectedItem->isDynamic()) {
m_view->showElementPopup(type, toDynamic(selectedItem)->adjustedBoundingRect());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The same doubt again about adjustedBoundingRect. You could decide to move all changes related to the addition of this method into yet another separate PR, so that the initial popup functionality PR will be easy to merge.

@@ -877,6 +881,10 @@ void NotationViewInputController::mouseReleaseEvent(QMouseEvent* event)

if (interaction->isDragStarted()) {
interaction->endDrag();
// When dragging of hairpin ends on a note or rest, open dynamic popup
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

on a note or rest

I don't see this reflected in the actual condition in the code. Is it a requirement at all?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually the check for note or rest happens here in Score::addText which is called through addTextToItem in toggleDynamicPopup

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah okay. Still it might be better to adjust the comment, because it's a bit prone to causing confusion.

@@ -877,6 +881,10 @@ void NotationViewInputController::mouseReleaseEvent(QMouseEvent* event)

if (interaction->isDragStarted()) {
interaction->endDrag();
// When dragging of hairpin ends on a note or rest, open dynamic popup
if (interaction->selection()->element()->isHairpinSegment()) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On the one hand, this feels a bit like an ad-hoc special case. On the other hand, I don't quite see a better way for this, currently. @mike-spa do you have ideas?

@@ -546,7 +546,11 @@ void AbstractNotationPaintView::showElementPopup(const ElementType& elementType,

PopupModelType modelType = AbstractElementPopupModel::modelTypeFromElement(elementType);

emit showElementPopupRequested(modelType, fromLogical(elementRect).toQRectF());
if (elementType == ElementType::DYNAMIC) {
emit showElementPopupRequested(modelType, fromLogical(elementRect).toQRect()); // toQRect() is used in AbstractElementPopupModel::updateItemRect()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't quite understand this change. Is it necessary?

Copy link
Contributor Author

@re1san re1san Aug 19, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@cbjeukendrup Initially when we click on dynamic showElementPopupRequested is emitted which if you notice uses toQRectF() meaning that the y value will be a decimal which will be used to position the popup. But, the updateItemRect method (which is called on changing the dynamic) returns a QRect and not QRectF so the value after the decimal will be lost, meaning that it will shift the popup a little higher than initially positioned as seen in below video. (It's not very noticeable but still...)

2024-08-19.08-57-17.mp4

I was thinking if we can change updateItemRect to use QRectF (or, in general use QRect in showElementPopupRequested), so its in sync?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I see! Yes, updating updateItemRect and m_itemRect to use QRectF seems good to me.

rect = QRect();
} else {
if (m_item->isDynamic()) {
rect = fromLogical(toDynamic(m_item)->adjustedBoundingRect()).toQRect();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, same thing again...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is used to place the popup below the adjustedBoundingRectangle of dynamic like you can see in the in the below image, even when we zoom in it is placed consistently below the rectangle.

If we use the canvasBoundingRect the popup will be inside the bounding rectangle on zooming (even if I add some value to root.y of updatePosition in Qml file it was not getting placed consistently on zooming, so thats why I used adjustedBoudingRect instead of canvasBoundingRect as the elementRect)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see... @mike-spa do you have ideas about how this can be generalised? A bit like hitShape(), but different...

Also, since all TextBase elements have this grey box around them while editing their text, it might make sense to apply this to all of them. But only during actual text editing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
GSoC PRs created by GSoC participants during coding period
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants