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

improve rendering of waveform marks #12203

Merged
merged 13 commits into from Nov 5, 2023
Merged

Conversation

m0dB
Copy link
Contributor

@m0dB m0dB commented Oct 21, 2023

This PR improves the rendering of the waveform marks, by auto-scaling and better aligning the symbolic marks (such as the intro/outro triangles and the loop open circle arrow), as discussed here: https://mixxx.zulipchat.com/#narrow/stream/109171-development/topic/look.20of.20loop.20out.20marker

Additionally it:

  • removes code duplication between waveformrendermark.cpp and allshaders/waveformrendermark.cpp by moving the mark image rendering to waveformmark.cpp and doing a general cleanup of that code
  • doesn't draw a small empty rectangle for marks without a label
  • fixes drawing of marks in vertical orientation in legacy waveforms
  • fixes a small bug with hovering over marks (propagate leave event)
  • uses antialiasing to render the marks and the glsl play position mark image
Screenshot 2023-10-21 at 03 34 09

…educed code duplication, fixed a small bug with leave events not being send to the opengl widgets, use antialiasing for play pos
@m0dB m0dB requested review from ronso0 and Swiftb0y October 22, 2023 00:06
@acolombier
Copy link
Contributor

What happens when labels overlap? (e.g in the screenshot, label of hotcue 4 would be long enough to overlap label of 3)
Do you think it would be possible to add some logic to bring labels on the foreground? For example, the last activated on is always on foreground or if the user hover them on the waveform, or in the deck

@ronso0
Copy link
Member

ronso0 commented Oct 22, 2023

@acolombier I think you're referring to #11745

I suggest to keep this PR compact, then take care of the other issues (#11745 and dark/bright detection / hover effect).

I'll test this asap.

@m0dB
Copy link
Contributor Author

m0dB commented Oct 22, 2023

There is currently no such logic. But the case you describe (the previous marker being so long that it overlaps the next marker) could be very easily solved with very simple logic: draw the markers from right to left.

But what annoys me most is when you have two markers at the same position (I actually use that quite a lot, e.g. a normal and a looping hotcue). I want to try placing the second one higher than the first in that case.

In any case, all that should be in a separate PR.

@acolombier
Copy link
Contributor

@ronso0 this isn't quite the same now - my question was if instead of OK! as a label, you have Some long text(up to 23 chars + the ... suffix), how will that be interfering with the 3rd hotcue? I assume one will overlap the other, and I'm wondering if we should account for further work (no particularly in that PR) to improve the UX

@m0dB I also use that behavior and suffering from the same limitation and the change you are describing would be very appreciated :)

@m0dB
Copy link
Contributor Author

m0dB commented Oct 22, 2023

I think #11745 is more about the inconsistency between the scrolling waveform and the waveform overview.

Comment on lines 118 to 119
std::unique_ptr<QOpenGLTexture> m_pTexture; // Used by allshader::WaveformRenderMark
QImage m_image; // Used by WaveformRenderMark
Copy link
Member

Choose a reason for hiding this comment

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

so IIUC, m_pTexture is always nullptr when running with a legacy shader and QImage isNull the other way around, right? Wouldn't it be better instead then if these concepts lived in the shaders then instead? At least that sounds like thats a better separation of concern.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

In the renderer you mean? (WaveformMarkRenderer and allshaders::WaveformMarkRenderer). But that would require keeping track of which image or texture goes with which WaveformMark. No, this really needs to be part of WaveformMark. But maybe it can be done by subclassing WaveformMark... I will have a look, I agree this is not ideal.

Copy link
Member

Choose a reason for hiding this comment

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

But that would require keeping track of which image or texture goes with which WaveformMark.

The WaveformMarks in the renderer are just stored in a container, should just be sufficient to store a std::pair<std::unique_ptr<QOpenGLTexture>, WaveformMark> and then implement generateMarkImage in terms of that, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No, they are stored in a WaveformMarkSet. But I can derive a WaveformMarkImage and a WaveformMarkTexture from WaveformMark and templatize WaveformMarkSet.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I started doing this, but it adds way too much complexity. I prefer having these two data members, of which either one is used depending on the context, instead of adding all this complexity.

Copy link
Member

Choose a reason for hiding this comment

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

okay. Have you considered putting the members in a variant to highlight the fact that they are mutual exclusive? If that introduces too much complexity again, then ignore it. I'm just trying to come up with solutions to make the code more self-documenting.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yes, that makes sense! will do!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done. hope this is in line with what you had in mind.

Copy link
Member

Choose a reason for hiding this comment

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

Yes, almost. I was rather thinking of std::variant but this also works. The only thing I'm slighly concerned about is the blind static_cast from the parent to the derived type but I don't know any better solution either right now without introducing lots of boilerplate.

Copy link
Member

Choose a reason for hiding this comment

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

Thank you for going through the extra effort.

src/waveform/renderers/waveformmark.h Outdated Show resolved Hide resolved
@JoergAtGithub
Copy link
Member

On my Win11 system the green loop marker looks different than in your screenshot:
grafik

@m0dB
Copy link
Contributor Author

m0dB commented Oct 23, 2023

On my Win11 system the green loop marker looks different than in your screenshot:

My screenshot is HDPI so that will make a difference for sure, but indeed this looks worse than it should... Can you try changing, in waveformmark.cpp:

// And we scale the font size accordingly.
m_font.setPointSizeF(50.0 * ratio);

to

m_font.setPixelSize(19);

and see if that improves things? (This will make the intro/outro markers look weird, but that's expected)

@JoergAtGithub
Copy link
Member

This is how it looks with m_font.setPointSizeF(50.0 * ratio); :
grafik

and this is how it looks like with m_font.setPixelSize(19);:
grafik

@m0dB
Copy link
Contributor Author

m0dB commented Oct 23, 2023

and without this PR ?

@JoergAtGithub
Copy link
Member

Plain 2.4:
grafik

@m0dB
Copy link
Contributor Author

m0dB commented Oct 23, 2023

Thanks for testing and following up. Well, that's a relieve! That also looks bad!

What if you paste this character ↻ in a text editor with font Open Sans (bold) at different sizes?

And does it improve things if in waveformmark.cpp instead of painter.drawText(pos, label); you use painter.drawText(static_cast<int>(pos.x()), static_cast<int>(pos.y()), label); ?

@JoergAtGithub
Copy link
Member

JoergAtGithub commented Oct 23, 2023

grafik
grafik
grafik
grafik

@JoergAtGithub
Copy link
Member

This PR with painter.drawText(static_cast<int>(pos.x()), static_cast<int>(pos.y()), label);:
grafik

@m0dB
Copy link
Contributor Author

m0dB commented Oct 24, 2023

My conclusion is that Windows does a pretty bad job at rendering this symbol. Wondering what it looks like on Linux.

Maybe we should use a png instead?

@ronso0
Copy link
Member

ronso0 commented Oct 24, 2023

On Ubuntu Studio 20.04 it looks okay @150%
image

@250%
image

@JoergAtGithub
Copy link
Member

Maybe we should use a png instead?

If possible, we should use svg vector graphics, as in the skins.

@ywwg
Copy link
Member

ywwg commented Oct 25, 2023

+1 to using an SVG instead of a font

@github-actions github-actions bot added the skins label Oct 28, 2023
@m0dB
Copy link
Contributor Author

m0dB commented Oct 28, 2023

I have added the possibility to specify an svg.

This is with an svg for the loop mark.

Screenshot 2023-10-28 at 12 24 03

Does this look okay on non-HDPI, @JoergAtGithub ? Maybe the svg needs a bit of tweaking.

@JoergAtGithub
Copy link
Member

JoergAtGithub commented Oct 28, 2023

Yes, the loop marker looks now fine on Windows too!
grafik
The Intro/Outro markers look not that bad as the loop markers looked before, but also there SVG should be used. Which can be done in another PR.

@m0dB
Copy link
Contributor Author

m0dB commented Oct 28, 2023

Yes, the loop marker looks now fine on Windows too!

Great!

The Intro/Outro markers look not that bad as the loop markers looked before, but also there SVG should be used. Which can be done in another PR.

No, I will add it to this PR, it's very little effort.

Edit: Done, please confirm @JoergAtGithub

@ronso0
Copy link
Member

ronso0 commented Oct 28, 2023

No, I will add it to this PR, it's very little effort.

All skins? 😬
Reason for asking: once upon a time we decided to add the Sync Leader control to all skins (even Shade?), and just today I noticed I only managed to add it to LateNight.
It's an entirely different story (in terms of xml integration and design effort), though I wanted to add that note.

@m0dB
Copy link
Contributor Author

m0dB commented Oct 28, 2023

That's a good point, @ronso0 . I only added this to Latenight, but the text rendering is still there, so of course other skins can continue using that.

@JoergAtGithub
Copy link
Member

Now all the markers look fine on Windows11! Thank you!

Copy link
Member

@Swiftb0y Swiftb0y left a comment

Choose a reason for hiding this comment

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

LGTM. thanks a lot. before merging, did someone test this on linux as well?

@ywwg
Copy link
Member

ywwg commented Nov 2, 2023

looks good on linux

Copy link
Member

@ywwg ywwg left a comment

Choose a reason for hiding this comment

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

looks good! Please clean up stray commented-out lines before merging, and I've made some comments. I am not blocking this PR on them though since I am famously bad at getting back to my reviews :)

@@ -62,7 +62,8 @@
</MarkRange>
<Mark>
<Control>loop_start_position</Control>
<Text>&#8635;</Text>
<!--Text>&#8635;</Text-->
Copy link
Member

Choose a reason for hiding this comment

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

remove comments now that we are using the svg files

Copy link
Member

Choose a reason for hiding this comment

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

not sure about this (here). sometimes it's helpful to have the matching unicode char at hand.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, I agree, the Text should be fallback. I will revert the commenting.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done


return m_label.area().contains(point) || lineHovered;
}

// Helper class to calculate the geometry and fontsize needed by generateImage
Copy link
Member

Choose a reason for hiding this comment

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

technically this is a struct not a class, so let's call it that

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

const qreal margin{3.f};

qreal capHeight;
{
Copy link
Member

Choose a reason for hiding this comment

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

why is this scoping needed?

Copy link
Member

Choose a reason for hiding this comment

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

oh is this copied from the original code? then maybe that's fine 👍🏻

Copy link
Contributor Author

Choose a reason for hiding this comment

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

i removed it, as it allows capHeight to be const.

QString path = m_pixmapPath;
// Use devicePixelRatio to properly scale the image
QImage image = *WImageStore::getImage(path, devicePixelRatio);
// QImage image = QImage(path);
Copy link
Member

Choose a reason for hiding this comment

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

please remove commented-out lines like this throughout

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

// If loading the image didn't fail, then we're done. Otherwise fall
// through and render a label.
if (!image.isNull()) {
image =
Copy link
Member

Choose a reason for hiding this comment

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

unnecessary newline. if possible, please run the code formatter on changed lines

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I am using pre-commit , not sure why this wasn't formatted. Anyway, fixed.

Comment on lines +26 to +29
Graphics()
: m_obsolete{false} {
}
bool m_obsolete;
Copy link
Member

Choose a reason for hiding this comment

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

I think this is equivalent and simpler, though we might have a style reason not to use this format?

Suggested change
Graphics()
: m_obsolete{false} {
}
bool m_obsolete;
bool m_obsolete = false;

Copy link
Contributor Author

Choose a reason for hiding this comment

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

See my comment below on uniform initialization.


float m_linePosition;
float m_linePosition{};
Copy link
Member

Choose a reason for hiding this comment

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

I'm not sure we use this style of initialization elsewhere, probably clearer to do = 0; instead

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I would like to get developer concensus on this. @Swiftb0y (IIRC) recently told me to initialize in the initializer list...

But there are two issues at hand here: 1) using curly braces aka uniform initialization and 2) in-class initialization vs initializer lists.

As for 1, from what I understand, uniform initialization is the prefered way since c++11. The standard https://isocpp.org/wiki/faq/cpp11-language#uniform-init clearly says: "Prefer initializing using {}."

As for 2, I don't think there is such a clear preference. When you have multiple constructors, in-class initialization certainly makes things easier, otherwise I don't think it matters much.

Copy link
Member

@Swiftb0y Swiftb0y Nov 4, 2023

Choose a reason for hiding this comment

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

I would like to get developer concensus on this. @Swiftb0y (IIRC) recently told me to initialize in the initializer list...

Let me be clear, I didn't mean to tell you to use initializer lists, I just said other people will complain and I thought I'd mention it so it doesn't seem like I didn't review your PR thoroughly. I also prefer this style and if other devs do so too, then lets use it. If others complain we can settle this in a simple vote. :)

I'm not sure we use this style of initialization elsewhere, probably clearer to do = 0; instead

I also prefer = 0 but only when the type is clearly numerical. On the other hand the meaning of = 0 is already overloaded in the context of pure virtual member functions. So my preference is probably wrong and we should prefer using {} as per the cpp core guidelines.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ok, let's take this discussion to zulip

Copy link
Contributor Author

Choose a reason for hiding this comment

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

removing in-class initialization

@m0dB
Copy link
Contributor Author

m0dB commented Nov 5, 2023

All comments have been addressed, so I think this can be merged.

@JoergAtGithub
Copy link
Member

Just tested this again, and it's still working. LGTM! Thank you!

@JoergAtGithub JoergAtGithub merged commit 9129ace into mixxxdj:2.4 Nov 5, 2023
12 checks passed
@daschuer daschuer added this to the 2.4.0 milestone Nov 5, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

7 participants