Missing rectangle functions for justifying rectangle inside and outside of a bounds


#1

Why don’t we have these for Juce::Rectangle? I didn’t yet write the code for setting the various points. JUCE doesn’t have those either.

SetBottom for example actually changes the size of the rectangle. I need functions that only change position based on the various 9 points top/bottom/center * left/right/mid. I need these functions to do various rectangle justification inside AND outside of a square.

juce::Justification only works based on placing a rectangle inside another rectangle. With the system of Rectangle::setSomethingSomthing(Rectangle::getSomethingSomething()) we can achieve all possible inside and outside justification.

Here are the GET functions needed

	template <typename T> static juce::Point<T> getTopLeft(juce::Rectangle<T> bounds)
	{
		return bounds.getTopLeft();
	}
	template <typename T> static juce::Point<T> getTopRight(juce::Rectangle<T> bounds)
	{
		return bounds.getTopRight();
	}
	//MISSING FROM JUCE
	template <typename T> static juce::Point<T> getTopCenter(juce::Rectangle<T> bounds)
	{
		int x = bounds.getX() + bounds.getWidth() / T(2);
		int y = bounds.getY();

		return { x,y };
	}
	//MISSING FROM JUCE
	template <typename T> static juce::Point<T> getMidLeft(juce::Rectangle<T> bounds)
	{
		int x = bounds.getX();
		int y = bounds.getY() + bounds.getHeight() / T(2);

		return { x,y };
	}
	template <typename T> static juce::Point<T> getMidCenter(juce::Rectangle<T> bounds)
	{
		return bounds.getCentre();
	}
	//MISSING FROM JUCE
	template <typename T> static juce::Point<T> getMidRight(juce::Rectangle<T> bounds)
	{
		int x = bounds.getX() + bounds.getWidth();
		int y = bounds.getY() + bounds.getHeight() / T(2);

		return { x,y };
	}
	template <typename T> static juce::Point<T> getBottomLeft(juce::Rectangle<T> bounds)
	{
		return bounds.getBottomLeft();
	}
	//MISSING FROM JUCE
	template <typename T> static juce::Point<T> getBottomCenter(juce::Rectangle<T> bounds)
	{
		int x = bounds.getX() + bounds.getWidth() / T(2);
		int y = bounds.getY() + bounds.getHeight();

		return { x,y };
	}
	template <typename T> static juce::Point<T> getBottomRight(juce::Rectangle<T> bounds)
	{
		return bounds.getBottomRight();
	}

#2

Another function I wish JUCE had was a margin function. This adjust the 4 edges of a square.

template <typename T> juce::Rectangle<T> withMargin(juce::Rectangle<T> bounds, T marginLeft, T marginTop, T marginRight, T marginBottom)
{
	int x = bounds.getX() + marginLeft;
	int y = bounds.getY() + marginTop;
	int w = bounds.getWidth() - (marginLeft + marginRight);
	int h = bounds.getHeight() - (marginTop + marginBottom);

	return { x,y,w,h };
}

#3

this is easily done, e.g. a margin of 4 pixels:

auto withMargin = getLocalBounds().reduced (4);

Or a reduced (x, y) version with x margin and y margin also exists…

Ok, the version with 4 parameters is not there, to be fair…


#4

A lot of those suggested functions look like they can be built with the already existing getCentreX/Y and getX/Y, no?


#5

How I use Margin:

image

example of setting something to CenterRight of another thing (oh, I’m using the term midRight for this example)

image

// hell no
int originalCenterY = boundsToAdjust.getY() + boundsToAdjust.getHeight() / 2;
int desiredCenterY = boundsToConsider.getY() + boundsToConsider.getHeight() / 2;
int x = boundsToConsider.getX() + boundsToConsider.getWidth();
int y = boundsToAdjust.getY() + (desiredCenterY - originalCenterY);
boundsToAdjust.setTopLeft(x,y)

OR

// ok a little better but definitely don't want to memorize 9 of these formulas
// definitely don't want 3 lines code for setting the 
// bounds of one graphical element out of hundreds
int x = boundsToConsider.getX() + boundsToConsider.getWidth();
int y = boundsToAdjust.getY() + (boundsToAdjust.getCentreY() - boundsToConsider.getCentreY());
boundsToAdjust.setTopLeft(x,y)

VS

// ahh, much better
bounds.setMidLeft(targetBounds.getMidRight())

AND

// and we also have room on this line to
// add a bit of offset so graphical elements aren't touching
bounds.setMidLeft(targetBounds.getMidRight().translated(5,0))

#6

FWIW, your missing functions simplified

	template <typename T> static juce::Point<T> getTopCenter(juce::Rectangle<T> bounds)
	{
		return { bounds.getCenterX(), bounds.getY() };
	}

	template <typename T> static juce::Point<T> getMidLeft(juce::Rectangle<T> bounds)
	{
		return { bounds.getX(), bounds.getCentreY() };
	}

	template <typename T> static juce::Point<T> getMidRight(juce::Rectangle<T> bounds)
	{
		return { bounds.getRight(), bounds.getCentreY() };
	}

	template <typename T> static juce::Point<T> getBottomCenter(juce::Rectangle<T> bounds)
	{
		return { bounds.getCentreX(), bounds.getBottom() };
	}

the margin one seems useful, but could be written this way:

template <typename T> juce::Rectangle<T> withMargin(juce::Rectangle<T> bounds, T marginLeft, T marginTop, T marginRight, T marginBottom)
{
    return bounds.withLeft(marginLeft)
        .withRight(marginRight)
        .withTop(marginTop)
        .withBottom(marginBottom);
}

#7

Ok here’s all the get/set in one go. With this we have 81 (9*9) possible rectangle justification combinations.

template <typename T> static juce::Point<T> getTopLeft(juce::Rectangle<T> bounds)
{
    return bounds.getTopLeft();
}
template <typename T> static juce::Point<T> getTopRight(juce::Rectangle<T> bounds)
{
    return bounds.getTopRight();
}
template <typename T> static juce::Point<T> getTopCenter(juce::Rectangle<T> bounds)
{
    return { bounds.getCenterX(), bounds.getY() };
}
template <typename T> static juce::Point<T> getMidLeft(juce::Rectangle<T> bounds)
{
    return { bounds.getX(), bounds.getCentreY() };
}
template <typename T> static juce::Point<T> getMidCenter(juce::Rectangle<T> bounds)
{
    return bounds.getCentre();
}
template <typename T> static juce::Point<T> getMidRight(juce::Rectangle<T> bounds)
{
    return { bounds.getRight(), bounds.getCentreY() };
}
template <typename T> static juce::Point<T> getBottomLeft(juce::Rectangle<T> bounds)
{
    return bounds.getBottomLeft();
}
template <typename T> static juce::Point<T> getBottomCenter(juce::Rectangle<T> bounds)
{
    return { bounds.getCentreX(), bounds.getBottom() };
}
template <typename T> static juce::Point<T> getBottomRight(juce::Rectangle<T> bounds)
{
    return bounds.getBottomRight();
}

template <typename T> static juce::Rectangle<T> setTopLeft(juce::Rectangle<T> bounds, juce::Point<T> target)
{
    return { target.getX(), target.getY(), bounds.getWidth(), bounds.getHeight() };
}
template <typename T> static juce::Rectangle<T> setTopCenter(juce::Rectangle<T> bounds, juce::Point<T> target)
{
    return { target.getX() - bounds.getWidth() / T(2), target.getY(), bounds.getWidth(), bounds.getHeight() };
}
template <typename T> static juce::Rectangle<T> setTopRight(juce::Rectangle<T> bounds, juce::Point<T> target)
{
    return { target.getX() - bounds.getWidth(), target.getY(), bounds.getWidth(), bounds.getHeight() };
}
template <typename T> static juce::Rectangle<T> setMidLeft(juce::Rectangle<T> bounds, juce::Point<T> target)
{
    return { target.getX(), target.getY() - bounds.getHeight() / T(2), bounds.getWidth(), bounds.getHeight() };
}
template <typename T> static juce::Rectangle<T> setMidCenter(juce::Rectangle<T> bounds, juce::Point<T> target)
{
    return { target.getX() - bounds.getWidth() / T(2), target.getY() - bounds.getHeight() / T(2), bounds.getWidth(), bounds.getHeight() };
}
template <typename T> static juce::Rectangle<T> setMidRight(juce::Rectangle<T> bounds, juce::Point<T> target)
{
    return { target.getX() - bounds.getWidth(), target.getY() - bounds.getHeight() / T(2), bounds.getWidth(), bounds.getHeight() };
}
template <typename T> static juce::Rectangle<T> setBottomLeft(juce::Rectangle<T> bounds, juce::Point<T> target)
{
    return { target.getX(), target.getY() - bounds.getHeight(), bounds.getWidth(), bounds.getHeight() };
}
template <typename T> static juce::Rectangle<T> setBottomCenter(juce::Rectangle<T> bounds, juce::Point<T> target)
{
    return { target.getX() - bounds.getWidth() / T(2), target.getY() - bounds.getHeight(), bounds.getWidth(), bounds.getHeight() }
}
template <typename T> static juce::Rectangle<T> setBottomRight(juce::Rectangle<T> bounds, juce::Point<T> target)
{
    return { target.getX() - bounds.getWidth(), target.getY() - bounds.getHeight(), bounds.getWidth(), bounds.getHeight() };
}

and nonsensically visualized:

image


#8

but why?

the Rectangle class has a bunch of member functions that you’re just duplicating or making static.

	template <typename T> static juce::Rectangle<T> setMidCenter(juce::Rectangle<T> bounds, juce::Point<T> target)
	{
		return { target.x - bounds.w / T(2), target.y - bounds.h / T(2), bounds.w, bounds.h }
	}

is just these Rectangle member functions turned into a static function

    Rectangle withPosition (Point<ValueType> newPos) const noexcept                                 { return { newPos.x, newPos.y, w, h }; }

    /** Changes the position of the rectangle's centre (leaving its size unchanged). */
    inline void setCentre (ValueType newCentreX, ValueType newCentreY) noexcept                     { pos.x = newCentreX - w / (ValueType) 2;
                                                                                                      pos.y = newCentreY - h / (ValueType) 2; }

    /** Changes the position of the rectangle's centre (leaving its size unchanged). */
    inline void setCentre (Point<ValueType> newCentre) noexcept                                     { setCentre (newCentre.x, newCentre.y); }

I mean, come on:

	template <typename T> static juce::Point<T> getTopLeft(juce::Rectangle<T> bounds)
	{
		return bounds.getTopLeft();
	}
	template <typename T> static juce::Point<T> getTopRight(juce::Rectangle<T> bounds)
	{
		return bounds.getTopRight();
	}
	template <typename T> static juce::Point<T> getMidCenter(juce::Rectangle<T> bounds)
	{
		return bounds.getCentre();
	}
	template <typename T> static juce::Point<T> getBottomLeft(juce::Rectangle<T> bounds)
	{
		return bounds.getBottomLeft();
	}
	template <typename T> static juce::Point<T> getBottomRight(juce::Rectangle<T> bounds)
	{
		return bounds.getBottomRight();
	}

you’re literally calling the member function. Congrats for writing code that does nothing but waste time spent writing it lol

	template <typename T> static juce::Rectangle<T> setBottomRight(juce::Rectangle<T> bounds, juce::Point<T> target)
	{
		return { target.x - bounds.w, target.y - bounds.h, bounds.w, bounds.h }
	}

setBottomRight is the same as

    /** Returns a rectangle with the same top-left position as this one, but a new size. */
    Rectangle withSize (ValueType newWidth, ValueType newHeight) const noexcept                     { return { pos.x, pos.y, newWidth, newHeight }; }

followed by withPosition:

    /** Returns a rectangle with the same size as this one, but a new position. */
    Rectangle withPosition (Point<ValueType> newPos) const noexcept                                 { return { newPos.x, newPos.y, w, h }; }
bounds.setSize(w, h).withPosition({x,y});

I don’t think anyone on the JUCE team will be convinced that these should be added to the Rectangle class, other than that withMargins(top, right, bottom, left) function… But maybe they’ll surprise us.


#9

I will be using this to position text at various places for labelling gui elements.

image

image


#10

Why not just use the Label class, and override the Label::LookAndFeelMethods::drawLabel()?

https://docs.juce.com/develop/structLabel_1_1LookAndFeelMethods.html

then you can position it like a regular component.


#11

Looks like it doesn’t feature all positions. But I can certainly create my own Label class that does.


#12

It’s not exactly a function, but for this you should check out BorderSize


#13

Here are some implementation ideas.

//New class that takes a rectangle to switch between move and resize
RectangleMove(bounds).setMidRight(targetBounds.getMidLeft());
RectangleResize(bounds).setMidRight(targetBounds.getMidLeft());

// class inside Rectangle for extending 9 point functions and separate move/resize
bounds::Move.setMidRight(targetBounds.getMidLeft());
bounds::Resize.setMidRight(targetBounds.getMidLeft());

// boolean to do move or resize
bounds.setMidRight(targetBounds.getMidLeft());
bounds.setMidRight(targetBounds.getMidLeft(), true);

// new class that takes in bounds and use flags for the 9 points
RectangleSnap(bounds, flags::midLeft, targetBounds, flags::midRight);
RectangleSnap(bounds, flags::midLeft, targetBounds, flags::midRight, true);

// non boolean version
RectangleMove(bounds, flags::midLeft, targetBounds, flags::midRight);
RectangleResize(bounds, flags::midLeft, targetBounds, flags::midRight);

#14

KISS and use juce::Rectangle::leftTopRightBottom:


#15

what? KISS? The margin function is not about creating a rectangle from points, it’s to reduce or grow the edges of the rectangle.


#16

#17

You’re totally misinterpreting my suggestion here!

What I’m saying is that you should do the following instead of the long-hand method you concocted above:

template<typename Type>
Rectangle<Type> withMargin (Type marginLeft, Type marginTop, Type marginRight, Type marginBottom)
{
    return Rectangle<Type>::leftTopRightBottom (marginLeft, marginTop, marginRight, marginBottom);
}

The thing is, your bounds parameter is just getting all of its contents overridden by the margins.


#18

Another usage for the 9 point system, draw a triangle (isosceles or equilateral in this case):

image

more interesting example:

/*
This creates a //\\ shape where the path is closed and the base is flat, so you must use a fill to color it.
*/
static Path pointerWithFlatBase(Graphics & g, juce::Rectangle<float> bounds, float stroke, float rotationDegrees = 0)
{
	Path p;

	p.startNewSubPath(RectPoint::getBottomLeft(bounds));
	p.lineTo(RectPoint::getTopCenter(bounds));
	p.lineTo(RectPoint::getBottomRight(bounds));
	p.lineTo(RectPoint::getBottomRight(bounds).translated(-stroke, 0));
	p.lineTo(RectPoint::getTopCenter(bounds).translated(0, +stroke * 1.5f));
	p.lineTo(RectPoint::getBottomLeft(bounds).translated(+stroke, 0));
	p.closeSubPath();

	if (rotationDegrees > 0)
		p.applyTransform(juce::AffineTransform().rotated(juce::degreesToRadians(rotationDegrees), bounds.getCentreX(), bounds.getCentreY()));

	return p;
}