Component Behavior Confusion

Hey folks, I’ve been playing around with Juce dsp for a few months now and I’m attempting to build my first UI (beyond some basic tutorials).

I apologize if this is an obvious or dumb problem (I hope it is so I can get past it), but I’m getting strange behavior with an XY controller I’m building and I haven’t been able to figure it out.

The dragging and updating parameters all works fine, however, I’m getting a phantom extra node(s) in the top left corner. If I explicitly call paint on the nodes from the parent, they draw correctly along with the phantom one. If I don’t call paint or call repaint(), only the phantom ones draw, and only appear if you drag the component into the top left corner. I’ve added white boxes around the component bounds and that all seems good. Images, links to video of the behavior, and the relevant code are below.

Any help would be appreciated, even if the mistake is just me being a dumb noob. :slight_smile:

Screen Shot 2021-08-18 at 7.37.47 PM
Screen Shot 2021-08-18 at 7.38.34 PM

video:

https://drive.google.com/file/d/1A94n5BhfyhvMDWkc6BodXkGaNkdo3Tal/view?usp=sharing

Parent code:

// in the constructor creating nodes...
for (auto i = 0; i < mNumNodes; ++i) {
            std::unique_ptr<XYNode> node = std::make_unique<XYNode>(i, nodeColors.at(i));
            addAndMakeVisible(node.get());
            node->setSize(XY_NODE_SIZE, XY_NODE_SIZE);
            node->addMouseListener(this, true);
            node->setWantsKeyboardFocus(true);
            mNodes.push_back(std::move(node));
        }
 
 void drawNodes(Graphics& g)
    {
        mPlotValues = getPlotValues();
        for (auto i = 0; i < mNodes.size(); ++i)
        {
            float xValue = mPlotValues[i][xOptions.at(mXControlIndex)];
            float yValue = mPlotValues[i][yOptions.at(mYControlIndex)];
            float sampleValue = mPlotValues[i]["sample"];
            drawNode(g, xValue, yValue, sampleValue, i);
        }
    }
    
    void drawNode(Graphics& g, float xValue, float yValue, float sampleValue, int index) {
        float x;
        float y;

        // if they are dragging, prevent them from being dragged outside parent
        if (!mNodes[index]->getDragging()) {
            x = jmap<float>(xValue, 0.0, 1.0, mOffset , getWidth() - mOffset );
            y = getHeight() - jmap<float>(yValue, 0.0, 1.0, mOffset, getHeight() - mOffset);
        } else {
            Position pos = mNodes[index]->getPosition();
            x = pos.x;
            y = pos.y;
        }
        
        mNodes[index]->setCentrePosition(x, y);
//        mNodes[index]->paint(g);
    }


class XYNode: public Component {
public:
    XYNode(unsigned int index, juce::Colour color): nodeIndex(index), mColor(color) {}
    ~XYNode()=default;
    
    void setColor(juce::Colour color) {
        mColor = color;
    }
    
    void setPosition(float x, float y) {
        mPosition.x = x;
        mPosition.y = y;
    }
    
    Position getPosition() {
        return mPosition;
    }
    
    void paint(Graphics& g) override
    {
        g.setColour(juce::Colours::white);
        g.drawRect(getLocalBounds());
        
        g.setColour(mColor);
        
        int x = getX();
        int y = getY();
 
        g.fillEllipse(x, y, mSize.height , mSize.width);
        g.fillEllipse(x + ((getWidth() / 2) - ((mSize.width * 0.667) / 2)), y + ((getHeight() / 2) - ((mSize.height * 0.667) / 2)), mSize.height * 0.667f , mSize.width * 0.667);
        g.fillEllipse(x + ((getWidth() / 2) - ((mSize.width / 3) / 2)), y + ((getHeight() / 2) - ((mSize.height / 3) / 2)), mSize.width / 3, mSize.height / 3);
    }
    
    
    unsigned int nodeIndex;
    
private:
    
    float mValue;
    Size mSize{XY_NODE_SIZE, XY_NODE_SIZE};
    Position mPosition;
    juce::Colour mColor;
    std::string mLabel;
    
    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (XYNode);
};

The issue is that in XYNode::paint, you use the wrong coordinates. Each component paint method paints within its local bounds, not its bounds.
That is why the bounds rectangle draws correctly, you are rightfully calling getLocalBounds and not getBounds.
If you replace x = getX() by x = 0 (and same for y), it should work.

Overall you seem to be confused with how components drawing works. Here is a quick summary:

  • Components you create should draw themselves using their paint method in local coordinates (always from 0,0 regardless of their position in the parent).
  • The parent component is in charge of positioning and resizing them in its resized method
  • The parent paint method should not do anything related to children components. Children are painted automatically

You do not need to add a size and position member variables to your component. Components already have that. See getWidth() etc

Aww… thanks so much for taking the time. I knew it was going to be something obvious. I had originally drawn the nodes from the parent just to get them on the screen, but didn’t update them correctly when I converted then to components… Thanks again!