I’m working on a TabBar that has draggable buttons.
There’s a forum post here:
@jules explains how to get it basically functional, which I have done:
ExtendedTabBarButton
ownComponentDragger
instances.ExtendedTabButtonBar
is both aDragAndDropTarget
andDragAndDropContainer
I am dealing with this graphical issue when dragging the tabs (GIF):
I’m trying to get the following drag behavior (excluding the animated position change) (GIF):
Is there a better/different way to solve this graphical glitch than the one detailed below?
Here is how everything is being put together, as well why I think this bug is happening:
in my ExtendedTabButtonBar::dragItemMoved
function, i’m doing this:
/*
idx is the index of the tab being dragged in the list of tabs owned
by the TabButtonBar.
nextTab is the tab to the right of the tab being dragged.
previousTab is the tab to the left
*/
if( centerX > nextTab->getX() )
{
DBG( "switching " << tabBeingDragged->getName() << " with nextTab: " << nextTab->getName() );
moveTab(idx, nextTabIndex);
}
else if( centerX < previousTab->getRight() )
{
DBG( "switching " << tabBeingDragged->getName() << " with previousTab: " << previousTab->getName() );
moveTab(idx, previousTabIndex);
}
the tabBeingDragged
has a ComponentDragger
member controlling its position while being dragged.
the TabbedButtonBar::moveTab(currentIndex, newIndex)
call is causing the dragged tab’s position to be snapped to a new position, inside of TabbedButtonBar::updateTabPositions()
:
auto newBounds = isVertical() ? Rectangle<int> (0, pos, getWidth(), bestLength)
: Rectangle<int> (pos, 0, bestLength, getHeight());
if (animate)
{
animator.animateComponent (tb, newBounds, 1.0f, 200, false, 3.0, 0.0);
}
else
{
animator.cancelAnimation (tb, false);
tb->setBounds (newBounds);
}
I have determined that this call to setBounds()
for the tab being dragged is the culprit because it is overriding the setBounds()
being called inside of the tab’s mouseDrag
operation:
void mouseDrag (const juce::MouseEvent& e) override
{
dragger.dragComponent (this, e, constrainer.get());
}
specifically:
void ComponentDragger::dragComponent (Component* const componentToDrag, const MouseEvent& e,
ComponentBoundsConstrainer* const constrainer)
{
jassert (componentToDrag != nullptr);
jassert (e.mods.isAnyMouseButtonDown()); // The event has to be a drag event!
if (componentToDrag != nullptr)
{
auto bounds = componentToDrag->getBounds();
// If the component is a window, multiple mouse events can get queued while it's in the same position,
// so their coordinates become wrong after the first one moves the window, so in that case, we'll use
// the current mouse position instead of the one that the event contains...
if (componentToDrag->isOnDesktop())
bounds += componentToDrag->getLocalPoint (nullptr, e.source.getScreenPosition()).roundToInt() - mouseDownWithinTarget;
else
bounds += e.getEventRelativeTo (componentToDrag).getPosition() - mouseDownWithinTarget;
if (constrainer != nullptr)
constrainer->setBoundsForComponent (componentToDrag, bounds, false, false, false, false);
else
componentToDrag->setBounds (bounds);
}
}
The only solution i’ve been able to think of is to somehow block the setBounds()
call inside of TabbedButtonBar::updateTabPositions()
However, updateTabPositions()
is private, and setBounds()
cannot be overridden, so that leaves me with duplicating the entire TabbedButtonBar
class and adding the following check:
auto newBounds = isVertical() ? Rectangle<int> (0, pos, getWidth(), bestLength)
: Rectangle<int> (pos, 0, bestLength, getHeight());
if (animate)
{
animator.animateComponent (tb, newBounds, 1.0f, 200, false, 3.0, 0.0);
}
else
{
animator.cancelAnimation (tb, false);
//check if being dragged
if( auto etbb = dynamic_cast<ExtendedTabBarButton*>(tb) )
{
if( ! etbb->isBeingDragged )
{
etbb->setBounds (newBounds);
}
}
else
{
tb->setBounds (newBounds);
}
}
That is a lot of code duplication which is not guaranteed to solve the problem.
Any ideas?