Spaces between drawable buttons?!

I’m 100% new to this, and I figure you can just answer this probably dumb question without any trouble:

I’m simply wanting to create an array of drawable buttons, and for the love of god, I cannot make them stay strictly next to each other without any padding or margen. I just want them to be completely close!


This is the code generating them:

// Stupid spaced Buttons
int totalWidth = getWidth();
int buttonHeight = 50;
int buttonWidth = totalWidth / numButtons;

for (int i = 0; i < drawableButtons.size(); ++i)
{
/int x = i * buttonWidth;/
int x = 50* i;
drawableButtons[i]->setBounds(x, 50, buttonWidth, buttonHeight);
}

// Debug:

// Draw a border around each button
g.setColour(juce::Colours::red);
for (auto* button : drawableButtons)
{
g.drawRect(button->getBounds(), 1);
}

The main problem is, that totalWidth / numButtons is an integer division, so it will get rounded down.
There are two possible solutions:

Calculate everything in float, that might make the buttons of different size by max 1 pixel.
Or calculate everything in int and have an empty space at the end.

For your button calculations I would recommend the various juce::Rectangle operations.

// With space at the end:
auto bounds = getLocalBounds();
int buttonWidth = totalWidth / numButtons;
for (int i = 0; i < drawableButtons.size(); ++i)
    drawableButtons[i]->setBounds (bounds.removeFromLeft (buttonWidth));

// with float bounds:
auto bounds = getLocalBounds();
float buttonWidth = static_cast<float>(totalWidth) / numButtons;
for (int i = 0; i < drawableButtons.size(); ++i)
    drawableButtons[i]->setBounds (bounds.withX(static_cast<int>(i * buttonWidth)
                                         .withRight(static_cast<int>((i+1) * buttonWidth));
1 Like

daniel, you are of course right, regarding floats/ints.

Thank you. My code was one of a thousand desperate attempts :slight_smile:


Anyway I still need to get them close together, and there must be something with some padding or … I dont know that Im missing…

void RarangerAudioProcessorEditor::createButtons(int numButtons)
{
    drawableButtons.clear();

    for (int i = 0; i < numButtons; ++i)
    {
        auto button = std::make_unique<juce::DrawableButton>("Button" + juce::String(i), juce::DrawableButton::ButtonStyle::ImageFitted);

        juce::Path parallelogram;
        // Define the points of the parallelogram
        juce::Point<float> startPoint(0, 50); //  Højde
        juce::Point<float> secondPoint(100, 0); // Second point
        juce::Point<float> thirdPoint(100, 100); // Third point
        juce::Point<float> endPoint(0, 100); // End point (back to start to close the shape)

        // Add line segments
        parallelogram.startNewSubPath(startPoint);
        parallelogram.lineTo(secondPoint);
        parallelogram.lineTo(thirdPoint);
        parallelogram.lineTo(endPoint);
        parallelogram.closeSubPath(); // Close the path to complete the shape

        auto drawable = std::make_unique<juce::DrawablePath>();
        drawable->setPath(parallelogram);
        drawable->setFill(juce::Colours::antiquewhite); // Set your desired fill color

       button->setImages(drawable.get());

        addAndMakeVisible(button.get());
        drawableButtons.push_back(std::move(button));
    }
}


void RarangerAudioProcessorEditor::resized()
{
    auto bounds = getLocalBounds();
    int totalWidth = bounds.getWidth();

    int rowYPosition = 100; // Adjust this value as needed to set the Y placement (height) of the entire row.
    int buttonsHeight = 100; // Adjust this value as needed to set the height of the buttons

    int buttonWidth = totalWidth / drawableButtons.size();
    int remainingPixels = totalWidth % drawableButtons.size(); // Pixels that are left after division

    for (int i = 0; i < drawableButtons.size(); ++i)
    {
        int extraPixel = (i < remainingPixels) ? 1 : 0; // Distribute the remaining pixels among the first few buttons

        // Set the Y position (height) of the entire row and keep the button's height fixed while adjusting the width
        drawableButtons[i]->setBounds(bounds.removeFromLeft(buttonWidth + extraPixel).withHeight(buttonsHeight).withY(rowYPosition));
    }
}

Check out DrawableButton::setEdgeIndent():

Gives the button an optional amount of space around the edge of the drawable.

By default there’s a gap of about 3 pixels.

Also, be aware that the DrawableButton tries to be clever about the edge around the image on the button, in some cases to the point of overriding what’s set with setEdgeIndent(), see:

Rectangle<float> DrawableButton::getImageBounds() const
{
    auto r = getLocalBounds();

    if (style != ImageStretched)
    {
        auto indentX = jmin (edgeIndent, proportionOfWidth  (0.3f));
        auto indentY = jmin (edgeIndent, proportionOfHeight (0.3f));

        if (shouldDrawButtonBackground())
        {
            indentX = jmax (getWidth()  / 4, indentX);
            indentY = jmax (getHeight() / 4, indentY);
        }
        else if (style == ImageAboveTextLabel)
        {
            r = r.withTrimmedBottom (jmin (16, proportionOfHeight (0.25f)));
        }

        r = r.reduced (indentX, indentY);
    }

    return r.toFloat();
}

To work around that, I have written a drop-in replacement in my namespace for DrawableButton, which makes it simpler (thanks to the fact that getImageBounds() is virtual)

/** A DrawableButton that strictly honours its edge indent.
 It's intended as a drop-in replacement for the DrawableButton in JUCE, which
 tries to be smart about the edge indent and may cause it to be ignored in
 certain circumstances (very big or very small buttons) */
class DrawableButton : public juce::DrawableButton
{
public:

    using juce::DrawableButton::DrawableButton; // use constructors from the base class

    juce::Rectangle <float> getImageBounds() const override
    {
        return getLocalBounds ().reduced (getEdgeIndent ()).toFloat ();
    }
};
1 Like

Thanks everyone!