Giving CaretComponent a bit of love

Hey JUCE team,

2 items related to CaretComponent:

  1. The timer was originally set to 380ms 11 years ago. It’s is a bit frantic (fast) compared to modern platform defaults and was one of the things I immediately noticed as “off” when starting to work with JUCE. Window’s default speed is 530ms. MacOS is also something around 500ms, I believe ( NSTextInsertionPointBlinkPeriodOn and NSTextInsertionPointBlinkPeriodOff makes it customizable for the platform).

  2. Caret is using setVisible to toggle the caret’s visibility…which causes a cascade of repaint calls since the parent (a text editor) is repainted which then causes its parent (such as a whole form) to repaint. All that extra repainting can lead to jank issues like I ran into.

To resolve these issues, I wanted to roll my own CaretComponent that would set isOpaque true and have a slower blink rate, etc… But Caret’s Timer inheritance was modified to be private, so one is forced to call CaretComponent::setCaretPosition from an overridden method or just not have their caret blink.

I don’t want to speak for others, but the inability to override this method and keep the blinking (and or the repainting cascade) might be why some plugins (like Vital) have caret blinking disabled altogether.

I made a PR to resolve, just shout if there’s something I can do to make it more mergeable.

5 Likes

Rather than public inheritance of Timer, maybe methods to set/get the blink rate, just to help improve communication of the intent. Could also consider setting setOpaque (true)? the only exception I can think of is if the colour is semi transparent.

I’d had a similar thought - publicly inheriting from Timer is often a red flag to me.

I was going to suggest a LaF method for the caret that returns the rate, but a getter/setter would be good too.

I’d prefer a global solution, like going through the L&F. It’d make for a consistent UI, and setting the interval manually for each caret is a bit silly.

I actually agree re: visibility. A setter for the width has also been requested before.

The only confounding problem (for me) is it means I’d still have to call CaretComponent::setCaretPosition from my overridden setCaretPosition. I don’t want to call that base method, as it toggles component visibility, triggering unwanted repaints. I guess I could pass a timer into my derived caret and handle the timing/visibility logic myself, however.

So yeah, the PR is the smallest change that increases flexibility, right now that class is pretty hard to derive from…

Yeah, good point. There are very few use cases I can think of that would require changing the rate. I mainly would like the default rate brought in line with OS behavior.

I updated my PR, removing the visibility change. All it does now is slow the timer to 500ms, bringing it in line with MacOS and Windows.

RE: painting issues, these went deeper than expected and weren’t trivially fixable.

This is the (yukky) way I’m slowing the caret until hopefully it can be fixed in JUCE.

I found a new problem with the Caret, unrelated to anything else. Haven’t nailed it down yet :sob:

On develop, there’s cursor flickering happening on text insertion, caused by an extra “frame” being painted. Frame 2 below should not exist (or it should have the caret):

This doesn’t happen when removing text, only inserting. Screen recording is the best way to confirm the extra frame. It’s also more jarring with contrasting colors.

The flicker doesn’t happen on MacOS DemoRunner 6.1.6, but does with a DemoRunner built from develop (can’t seem to reproduce at the moment).

I confirmed the issue with a fresh project. On my current project, I tried 6.1.6 and 6.0.8 and I still see the flickering on MacOS?

Windows doesn’t seem to have the problem.

I don’t see meaningful changes to TextEditor on develop, outside of this, which seem benign.

I’m cursed apparently! Trying to get a glitch-free TextEditor has become a full time endeavor :laughing:

Followup: the flicker went away on the juce7 branch. Never figured out the root cause (didn’t seem to be unnecessary repaints), but I was able to reproduce in a fresh juce 6.x project.