Elaboration on ComponentBoundsConstrainer functions for restricting drag area

Hi all,

I’ve been reading through the forums with no luck. I am attempting to restrict a component’s draggable-area and, frankly, I’m confused by the functions within the ComponentBoundsConstrainer class.

Within my custom component class I am using a ComponentDragger and ComponentBoundsConstrainer, similar to the demo app’s draggable classes.

Thanks for any help!

Can you be more specific about what you’re trying to do? Here’s a really simple example of a Component that uses a ComponentDragger and ComponentBoundsConstrainer to ensure that it can’t be dragged offscreen:

class MainContentComponent   : public Component
{
public:
    //==============================================================================
    MainContentComponent()
    {
        addAndMakeVisible (comp);
        setSize (600, 400);
    }
    ~MainContentComponent() {}

    void paint (Graphics&) override    {}
    void resized() override            { comp.centreWithSize (200, 200); }

private:
    //==============================================================================
    struct CustomComp   : public Component
    {
        CustomComp()                                     { constrainer.setMinimumOnscreenAmounts (200, 200, 200, 200); }
        
        void paint (Graphics& g) override                { g.fillAll (Colours::hotpink); }
        
        void mouseDown (const MouseEvent& e) override    { dragger.startDraggingComponent (this, e); }
        void mouseDrag (const MouseEvent& e) override    { dragger.dragComponent (this, e, &constrainer); }
        
        ComponentDragger dragger;
        ComponentBoundsConstrainer constrainer;
    };
    
    CustomComp comp;
    
    //==============================================================================
    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainContentComponent)
};

@ed95, thanks for the response. Apologies on the delay, had to take some time off from this project.

I’m clear on what your code does - I have a similar implementation going on.

To elaborate on what I’m doing:

I have two Components on screen. One is large and takes up most of the window. The second is smaller, draggable and has within its class, a ComponentDragger and a ComponentBoundsConstrainer. I am constraining the dragging bounds of the smaller component to the bounds of the larger component, which works fine. But the larger component gets resized at some point and its bounds change. I have been unable to update those bounds on the draggable object. Does that make sense?

I guess what I’m really looking for is elaboration on the ComponentBoundsConstrainer function checkBounds - this seems like the function I’m looking for but the parameter descriptions are confusing me.

checkBounds() can be used to constrain a given Rectangle to some limits. You’ll probably want to do something like this in your smaller component that contains the constrainer (this will constrain the smaller component to its parents bounds and ensure that it can’t be dragged out of its parent):

void parentSizeChanged() override
{
    auto newBounds = getBoundsInParent();
    
    constrainer.checkBounds (newBounds, getBoundsInParent(),
                             getParentComponent()->getLocalBounds(),
                             false, false, false, false);
    setBounds (newBounds);
    
    constrainer.setMinimumOnscreenAmounts (getHeight(), getWidth(), getHeight(), getWidth());
}

The first argument of checkBounds() is a reference to a Rectangle that will be constrained, the second argument is the current bounds of the component and the third is a Rectangle to which the component should be constrained. After the call to checkBounds() the first Rectangle will have been constrained to the limits so you should set the bounds of your component to this.

@ed95
If I have a bunch of draggable components that are all supposed to restricted to dragging within their parent component, how can I limit a select few to only be draggable in a vertical direction via the ComponentBoundsConstrainer? What about Horizontal direction?
Is it better to remove the dragger entirely from those children, and put all of the logic limiting the dragging to one direction in the parent’s mouse handlers?

It’s hard to say without seeing any code, but I guess you would want to set the limits parameter of ComponentBoundsConstrainer::checkBounds() to be a rectangle that allows the component to only move horizontally or vertically.

Ok @ed95 , simplified:

struct Node : public Component
{
    Node() { setSize(20,20); }
    void paint(Graphics& g) override { g.fillEllipse(Colours::red); }
};

struct Editor : public Component
{
    Editor() 
    {
        setSize(400, 200);
        for( int i = 0; i < 10; ++i ) 
        {    
             auto* node = nodes.add( new Node() );
             addAndMakeVisible(node);
             node->setCentrePosition( 10 + i * 20, 20 );
        }
    }
    void resized() override;
    void mouseDown(const MouseEvent& e) override;
    void mouseDrag(const MouseEvent& e) override;
    OwnedArray<Node> nodes;
    ComponentBoundsConstrainer constrainer;
    ComponentDragger dragger;
    Component* draggedComponent = nullptr;
};

and then

void Editor::resized() 
{

    auto bounds = getLocalBounds().reduced(20);
    constrainer.setMinimumOnscreenAmounts(0xffffff, 0xffffff, 0xffffff, 0xffffff);
    constrainer.setSizeLimits(bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight());
}

void Editor::mouseDown(const MouseEvent& e) 
{
    draggedComponent = nullptr;
    for( auto* node : nodes )
    {
        if( node->getBounds().contains( e.getEventRelativeTo(this).getPosition() )
        {
            draggedComponent = node;
            break;
        }
    }
    if( draggedComponent )
        dragger.startDraggingComponent(draggedComponent, e);
}
void Editor::mouseDrag(const MouseEvent& e)
{
    if( draggedComponent )
    {
          if( draggedComponent == nodes.getUnchecked(0) )
          {
               //limit dragging to left edge of Editor, vertical axis
          }
          else if( draggedComponent == nodes.getUnchecked(nodes.size()-1) )
          {
               //limit dragging to right edge of editor, vertical axis
          }
          dragger.dragComponent(draggedComponent, e, &constrainer);
    }
}

what’s the correct code for limiting the area those first and last nodes can be dragged in?

I would constrain the bounds after calling dragComponent(), like this:

void mouseDrag (const MouseEvent& e) override
{
    if (draggedComponent != nullptr)
    {
        dragger.dragComponent (draggedComponent, e, nullptr);
        
        if( draggedComponent == nodes.getUnchecked(0) )
        {
            constrainer.applyBoundsToComponent (*draggedComponent, draggedComponent->getBounds().withX (edges[0].first).withRight (edges[0].second));
        }
        else if( draggedComponent == nodes.getUnchecked(nodes.size()-1) )
        {
            constrainer.applyBoundsToComponent (*draggedComponent, draggedComponent->getBounds().withX (edges[1].first).withRight (edges[1].second));
        }
    }
}

Where edges is std::pair<int, int> edges[2]; and is set in the constructor like this:

for( int i = 0; i < 10; ++i )
{
    auto* node = nodes.add( new Node() );
    addAndMakeVisible(node);
    node->setCentrePosition( 10 + i * 20, 20 );
    node->addMouseListener (this, false);
    
    if (i == 0)
        edges[0] = { node->getX(), node->getRight() };
    else if (i == 9)
        edges[1] = { node->getX(), node->getRight() };
}

I’m sure you can clean it up a bit, but that’s the general idea.

1 Like

This confuses me, tho. the dragged component is only 20x20. so, maybe it should be

this->getLocalBounds().withX().withRight()

?

edit: ok, after playing with it for a while, what I had to do was this:

        if( node == startNode )
        {
            constrainer.applyBoundsToComponent(*node, node->getBounds().withX(1 + node->NodeDiameter/2));
        }

that keeps the node’s X at the position it’s originally positioned at.

It seems like this ComponentBoundsConstrainer class could really use a limitComponentPosition(Component&, Rectangle<int> ) method as that’s how most of us want to use it, and no one has figured out how to do that with the current class member functions. Most of those member functions actually end up resizing the component that’s being constrained, not just limiting the position.

Suggested addition to the framework:
ComponentPositionConstrainer class
or, if one already exists, point us to it, and a usage example :slight_smile:

Yeah, the code that I posted is a bit verbose - you could just use withX() and it’d be fine. But the point is that it’s constraining the bounds after the ComponentDragger has moved the component, so you need to store the x position that you want to keep your component at and then apply this after it’s been moved. Adding extra methods or another class to do this seems a bit heavy handed…

I see. That worked like a charm, so thanks for the suggestion.
Any ideas for constraining the y value? is that more to do with the limits the constrainer is set up with?

Can’t you just do the same thing but use .withY() instead?