ScrollBar::mouseDrag() - endless message callbacks


#1

I build my own viewport-like container-component for displaying waveforms.
Sometimes the application freezes when dragging the scrollbar, i thing this is the reason:

currentRangeStart is calculated with visibleRange.getLength().
But setCurrentRangeStart is modifying visibleRange.

Even even if deltaPixels are not changing, through rounding-errors, visibleRange.getLength() is oscillating between 0.099999999999999 and 0.010000000000002 .
The causes continuous listener callbacks.

I use the default totalRange between 0.0 and 1.0

[code]void ScrollBar::mouseDrag (const MouseEvent& e)
{
if (isDraggingThumb)
{
const int deltaPixels = ((vertical) ? e.y : e.x) - dragStartMousePos;

    setCurrentRangeStart (dragStartRange
                            + deltaPixels * (totalRange.getLength() - visibleRange.getLength())
                                / (thumbAreaSize - thumbSize));
}
else
{
    lastMousePos = (vertical) ? e.y : e.x;
}

}[/code]


#2

Not sure I understand the problem… Why would that make something freeze? It might cause a few unnecessary callbacks, but I can’t see why that’d be such a problem?


#3

EDIT: this is wrong, look post below

Good question, i know you would ask this, its tricky :slight_smile:

setCurrentRangeStart calls setCurrentRange which triggers a AsyncUpdate.
MouseDrag-Callback is also coming from the same handleAsyncUpdate(), which will trigger the AsyncUpdate which calls ScrollBar::setCurrentRange in the end.
And than we have this ping pong situation.

I seems that the mouse-up event doesn’t comes between through this message ping pong

Call stack:
[list]
ScrollBar::setCurrentRange
ScrollBar::setCurrentRangeStart
ScrollBar::mouseDrag
Component::internalMouseDrag
MouseInputSourceInternal::sendMouseDrag
MouseInputSourceInternal::setScreenPos
MouseInputSourceInternal::handleAsyncUpdate[/list]

Call stack:
[list]juce::ScrollBar::mouseDrag
juce::Component::internalMouseDrag
juce::MouseInputSourceInternal::sendMouseDrag
juce::MouseInputSourceInternal::setScreenPos
juce::MouseInputSourceInternal::handleAsyncUpdate() [/list]


#4

sorry, forget this post above, its not right, i have to investigate again :slight_smile:


#5

in my Scrollbar-Callback, i change the position of child-Components (and use the RangeStart double), and this does MouseInputSource::triggerFakeMove(), and that is generating a new Scrollbar-Callback while the mouse-up event doesn’t come through.
Without rounding-errors this would be no problem ( i think is why your viewport doesn’t have this problem because it use higher numbers), but i use a range between 0.0 - 1.0.


#6

Ok… but the fake mouse move is only sent if your component actually moves. Why would such an infinitesimal difference in the value cause your comp to move by at least 1 pixel?


#7

yes, in most i cases it won’t move.
When it scales to its internal (zoomable-virtual) position, it can be different (through rounding)

(double)0.099999999999999 * (int)1000 = 99 pixel
(double)0.100000000000002 * (int)1000 = 100 pixel


#8

Does it help if the Range class is tweaked like this:

const Range movedToStartAt (const ValueType newStart) const throw() { return Range (newStart, end + (newStart - start)); }

?


#9

mhh, I’m not sure, i could not reproduce it with this yet with tweaked Range-class, but the problems only occurs once in a while, and the changes will have no effect on the current behaviour.

The point is visibleRange.start should no be recalculated when deltaPixels not differ from the last deltaPixel in mouseDrag

These are exact values when the problem occurs :

visibleRange.start is oscillating between start=0.49714285714285722 and 0.49714285714285716

—> FIRST RANGE START CALCULATION IN void ScrollBar::mouseDrag (const MouseEvent& e)

[code]setCurrentRangeStart (dragStartRange + deltaPixels * (totalRange.getLength() - visibleRange.getLength()) / (thumbAreaSize - thumbSize)

dragStartRange = 0.90000000000000002
deltaPixels = -141
totalRange = {start=0.00000000000000000 end=1.0000000000000000 }
visibleRange = {start=0.49714285714285722 end=0.59714285714285720 }
thumbAreaSize = 355
thumbSize = 40

RESULT = newStart = 0.49714285714285716[/code]

—> SECOND RANGE START CALCULATION IN void ScrollBar::mouseDrag (const MouseEvent& e)

[code]setCurrentRangeStart (dragStartRange + deltaPixels * (totalRange.getLength() - visibleRange.getLength()) / (thumbAreaSize - thumbSize)

dragStartRange = 0.90000000000000002 (NOT CHANGED)
deltaPixels = -141 (NOT CHANGED)
totalRange = {start=0.00000000000000000 end=1.0000000000000000 } (NOT CHANGED)
visibleRange = {start=0.49714285714285716 end=0.59714285714285720 } (CHANGED!!!)
thumbAreaSize = 355 (NOT CHANGED)
thumbSize = 40 (NOT CHANGED)

RESULT = newStart = 0.49714285714285722[/code]

—> THIRD RANGE START CALCULATION IN void ScrollBar::mouseDrag (const MouseEvent& e)

(like the First)

[code]

  •   visibleRange	{	start = 0.49714285714285716
      					end = 0.59714285714285720 }	[/code]
    

—> 4th RANGE START CALCULATION IN void ScrollBar::mouseDrag (const MouseEvent& e)

[code]

  •   visibleRange	{	start=0.49714285714285722 
      					end=0.59714285714285720 }	[/code]
    

—> 4th RANGE START CALCULATION IN void ScrollBar::mouseDrag (const MouseEvent& e)

-		visibleRange	{	start = 0.49714285714285716
							end = 0.59714285714285720 }	[/code]


i think something like this would be a better solution:

[code]void ScrollBar::mouseDrag (const MouseEvent& e)
{
    if (isDraggingThumb)
    {
		
		const int deltaPixels = ((vertical) ? e.y : e.x) - dragStartMousePos;


		if (deltaPixels!=lastDeltaPixels)								//NEW
		{
			setCurrentRangeStart (dragStartRange
                                + deltaPixels * (totalRange.getLength() - visibleRange.getLength())
                                    / (thumbAreaSize - thumbSize));
		}
		lastDeltaPixels=deltaPixels;									//NEW
	}
    else
    {
        lastMousePos = (vertical) ? e.y : e.x;
    }
}

void ScrollBar::mouseUp (const MouseEvent&)
{
	lastDeltaPixels=0;													//NEW
    isDraggingThumb = false;
    stopTimer();
    repaint();
}

#10

Hmm, ok. But it all feels like it’s something that your code should deal with, really - if there’s some kind of logic in your code that can lead to it freezing when rounding errors like this occur, then fixing the scrollbar’s behaviour isn’t a guarantee that nothing else will trigger the same freeze-up.


#11

mmh - i would say, in my part of code i have no problem with rounding errors.

The only thing i do is resizing components inside ScrollBar::mouseDrag (or even with a AsyncUpdater afterwards, which behaves the same), and this should not be risky.

The combination of the kind of calculation in ScrollBar::mouseDrag() and the MouseInputSource::triggerFakeMove() is the problem which causes the loop.


#12

Yeah, I understand, but if it was my app, I’d be worried about the fact that it could freeze, no matter what caused it. If the scrollbar can make it go wrong by sending a bunch of continuous updates, then how do you know the same thing can’t be triggered by someone moving the scrollbar fast enough to generate a similar stream of events?


#13

a stream of events is not a problem. The problem is a endless continuous stream of events, caused by triggerFakeMove()

I doing nothing more than a Viewport does, something like that:

EDIT:
MYPort:ScrollBar::mouseDrag(ScrollBar* scrollBarThatHasMoved, double newRangeStart)
{
child->setBounds(newRangeStart*(int)size,0,100,100);
};

—> ScrollBar::mouseDrag -> setBounds() --> triggerFakeMove() —> ScrollBar::mouseDrag -> setBounds() --> triggerFakeMove() —> ScrollBar::mouseDrag -> setBounds() --> triggerFakeMove() —> ScrollBar::mouseDrag -> setBounds() --> triggerFakeMove() —> ScrollBar::mouseDrag -> setBounds() --> triggerFakeMove() —> ScrollBar::mouseDrag -> setBounds() --> triggerFakeMove() —> ScrollBar::mouseDrag -> setBounds() --> triggerFakeMove() —> ScrollBar::mouseDrag -> setBounds() --> triggerFakeMove() —> ScrollBar::mouseDrag -> setBounds() --> triggerFakeMove() —> ScrollBar::mouseDrag -> setBounds() --> triggerFakeMove() —> ScrollBar::mouseDrag -> setBounds() --> triggerFakeMove() etc…