mirror of https://github.com/dcoredump/dexed.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
662 lines
22 KiB
662 lines
22 KiB
/*
|
|
==============================================================================
|
|
|
|
This file is part of the JUCE library.
|
|
Copyright (c) 2015 - ROLI Ltd.
|
|
|
|
Permission is granted to use this software under the terms of either:
|
|
a) the GPL v2 (or any later version)
|
|
b) the Affero GPL v3
|
|
|
|
Details of these licenses can be found at: www.gnu.org/licenses
|
|
|
|
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
|
|
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
|
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
|
|
|
------------------------------------------------------------------------------
|
|
|
|
To release a closed-source product which uses JUCE, commercial licenses are
|
|
available: visit www.juce.com for more information.
|
|
|
|
==============================================================================
|
|
*/
|
|
|
|
#ifndef JUCE_RECTANGLELIST_H_INCLUDED
|
|
#define JUCE_RECTANGLELIST_H_INCLUDED
|
|
|
|
|
|
//==============================================================================
|
|
/**
|
|
Maintains a set of rectangles as a complex region.
|
|
|
|
This class allows a set of rectangles to be treated as a solid shape, and can
|
|
add and remove rectangular sections of it, and simplify overlapping or
|
|
adjacent rectangles.
|
|
|
|
@see Rectangle
|
|
*/
|
|
template <typename ValueType>
|
|
class RectangleList
|
|
{
|
|
public:
|
|
typedef Rectangle<ValueType> RectangleType;
|
|
|
|
//==============================================================================
|
|
/** Creates an empty RectangleList */
|
|
RectangleList() noexcept {}
|
|
|
|
/** Creates a copy of another list */
|
|
RectangleList (const RectangleList& other) : rects (other.rects)
|
|
{
|
|
}
|
|
|
|
/** Creates a list containing just one rectangle. */
|
|
RectangleList (const RectangleType& rect)
|
|
{
|
|
addWithoutMerging (rect);
|
|
}
|
|
|
|
/** Copies this list from another one. */
|
|
RectangleList& operator= (const RectangleList& other)
|
|
{
|
|
rects = other.rects;
|
|
return *this;
|
|
}
|
|
|
|
#if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS
|
|
RectangleList (RectangleList&& other) noexcept
|
|
: rects (static_cast<Array<RectangleType>&&> (other.rects))
|
|
{
|
|
}
|
|
|
|
RectangleList& operator= (RectangleList&& other) noexcept
|
|
{
|
|
rects = static_cast<Array<RectangleType>&&> (other.rects);
|
|
return *this;
|
|
}
|
|
#endif
|
|
|
|
//==============================================================================
|
|
/** Returns true if the region is empty. */
|
|
bool isEmpty() const noexcept { return rects.size() == 0; }
|
|
|
|
/** Returns the number of rectangles in the list. */
|
|
int getNumRectangles() const noexcept { return rects.size(); }
|
|
|
|
/** Returns one of the rectangles at a particular index.
|
|
@returns the rectangle at the index, or an empty rectangle if the index is out-of-range.
|
|
*/
|
|
RectangleType getRectangle (int index) const noexcept { return rects[index]; }
|
|
|
|
//==============================================================================
|
|
/** Removes all rectangles to leave an empty region. */
|
|
void clear()
|
|
{
|
|
rects.clearQuick();
|
|
}
|
|
|
|
/** Merges a new rectangle into the list.
|
|
|
|
The rectangle being added will first be clipped to remove any parts of it
|
|
that overlap existing rectangles in the list, and adjacent rectangles will be
|
|
merged into it.
|
|
|
|
The rectangle can have any size and may be empty, but if it's floating point
|
|
then it's expected to not contain any INF values.
|
|
*/
|
|
void add (const RectangleType& rect)
|
|
{
|
|
jassert (rect.isFinite()); // You must provide a valid rectangle to this method!
|
|
|
|
if (! rect.isEmpty())
|
|
{
|
|
if (rects.size() == 0)
|
|
{
|
|
rects.add (rect);
|
|
}
|
|
else
|
|
{
|
|
bool anyOverlaps = false;
|
|
|
|
for (int j = rects.size(); --j >= 0;)
|
|
{
|
|
RectangleType& ourRect = rects.getReference (j);
|
|
|
|
if (rect.intersects (ourRect))
|
|
{
|
|
if (rect.contains (ourRect))
|
|
rects.remove (j);
|
|
else if (! ourRect.reduceIfPartlyContainedIn (rect))
|
|
anyOverlaps = true;
|
|
}
|
|
}
|
|
|
|
if (anyOverlaps && rects.size() > 0)
|
|
{
|
|
RectangleList r (rect);
|
|
|
|
for (int i = rects.size(); --i >= 0;)
|
|
{
|
|
const RectangleType& ourRect = rects.getReference (i);
|
|
|
|
if (rect.intersects (ourRect))
|
|
{
|
|
r.subtract (ourRect);
|
|
|
|
if (r.rects.size() == 0)
|
|
return;
|
|
}
|
|
}
|
|
|
|
rects.addArray (r.rects);
|
|
}
|
|
else
|
|
{
|
|
rects.add (rect);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Merges a new rectangle into the list.
|
|
|
|
The rectangle being added will first be clipped to remove any parts of it
|
|
that overlap existing rectangles in the list.
|
|
*/
|
|
void add (ValueType x, ValueType y, ValueType width, ValueType height)
|
|
{
|
|
add (RectangleType (x, y, width, height));
|
|
}
|
|
|
|
/** Dumbly adds a rectangle to the list without checking for overlaps.
|
|
|
|
This simply adds the rectangle to the end, it doesn't merge it or remove
|
|
any overlapping bits.
|
|
|
|
The rectangle can have any size and may be empty, but if it's floating point
|
|
then it's expected to not contain any INF values.
|
|
*/
|
|
void addWithoutMerging (const RectangleType& rect)
|
|
{
|
|
jassert (rect.isFinite()); // You must provide a valid rectangle to this method!
|
|
|
|
if (! rect.isEmpty())
|
|
rects.add (rect);
|
|
}
|
|
|
|
/** Merges another rectangle list into this one.
|
|
|
|
Any overlaps between the two lists will be clipped, so that the result is
|
|
the union of both lists.
|
|
*/
|
|
void add (const RectangleList& other)
|
|
{
|
|
for (const RectangleType* r = other.begin(), * const e = other.end(); r != e; ++r)
|
|
add (*r);
|
|
}
|
|
|
|
/** Removes a rectangular region from the list.
|
|
|
|
Any rectangles in the list which overlap this will be clipped and subdivided
|
|
if necessary.
|
|
*/
|
|
void subtract (const RectangleType& rect)
|
|
{
|
|
const int originalNumRects = rects.size();
|
|
|
|
if (originalNumRects > 0)
|
|
{
|
|
const ValueType x1 = rect.getX();
|
|
const ValueType y1 = rect.getY();
|
|
const ValueType x2 = x1 + rect.getWidth();
|
|
const ValueType y2 = y1 + rect.getHeight();
|
|
|
|
for (int i = getNumRectangles(); --i >= 0;)
|
|
{
|
|
RectangleType& r = rects.getReference (i);
|
|
|
|
const ValueType rx1 = r.getX();
|
|
const ValueType ry1 = r.getY();
|
|
const ValueType rx2 = rx1 + r.getWidth();
|
|
const ValueType ry2 = ry1 + r.getHeight();
|
|
|
|
if (! (x2 <= rx1 || x1 >= rx2 || y2 <= ry1 || y1 >= ry2))
|
|
{
|
|
if (x1 > rx1 && x1 < rx2)
|
|
{
|
|
if (y1 <= ry1 && y2 >= ry2 && x2 >= rx2)
|
|
{
|
|
r.setWidth (x1 - rx1);
|
|
}
|
|
else
|
|
{
|
|
r.setX (x1);
|
|
r.setWidth (rx2 - x1);
|
|
|
|
rects.insert (++i, RectangleType (rx1, ry1, x1 - rx1, ry2 - ry1));
|
|
++i;
|
|
}
|
|
}
|
|
else if (x2 > rx1 && x2 < rx2)
|
|
{
|
|
r.setX (x2);
|
|
r.setWidth (rx2 - x2);
|
|
|
|
if (y1 > ry1 || y2 < ry2 || x1 > rx1)
|
|
{
|
|
rects.insert (++i, RectangleType (rx1, ry1, x2 - rx1, ry2 - ry1));
|
|
++i;
|
|
}
|
|
}
|
|
else if (y1 > ry1 && y1 < ry2)
|
|
{
|
|
if (x1 <= rx1 && x2 >= rx2 && y2 >= ry2)
|
|
{
|
|
r.setHeight (y1 - ry1);
|
|
}
|
|
else
|
|
{
|
|
r.setY (y1);
|
|
r.setHeight (ry2 - y1);
|
|
|
|
rects.insert (++i, RectangleType (rx1, ry1, rx2 - rx1, y1 - ry1));
|
|
++i;
|
|
}
|
|
}
|
|
else if (y2 > ry1 && y2 < ry2)
|
|
{
|
|
r.setY (y2);
|
|
r.setHeight (ry2 - y2);
|
|
|
|
if (x1 > rx1 || x2 < rx2 || y1 > ry1)
|
|
{
|
|
rects.insert (++i, RectangleType (rx1, ry1, rx2 - rx1, y2 - ry1));
|
|
++i;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
rects.remove (i);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Removes all areas in another RectangleList from this one.
|
|
|
|
Any rectangles in the list which overlap this will be clipped and subdivided
|
|
if necessary.
|
|
|
|
@returns true if the resulting list is non-empty.
|
|
*/
|
|
bool subtract (const RectangleList& otherList)
|
|
{
|
|
for (int i = otherList.rects.size(); --i >= 0 && rects.size() > 0;)
|
|
subtract (otherList.rects.getReference (i));
|
|
|
|
return rects.size() > 0;
|
|
}
|
|
|
|
/** Removes any areas of the region that lie outside a given rectangle.
|
|
|
|
Any rectangles in the list which overlap this will be clipped and subdivided
|
|
if necessary.
|
|
|
|
Returns true if the resulting region is not empty, false if it is empty.
|
|
|
|
@see getIntersectionWith
|
|
*/
|
|
bool clipTo (const RectangleType& rect)
|
|
{
|
|
jassert (rect.isFinite()); // You must provide a valid rectangle to this method!
|
|
|
|
bool notEmpty = false;
|
|
|
|
if (rect.isEmpty())
|
|
{
|
|
clear();
|
|
}
|
|
else
|
|
{
|
|
for (int i = rects.size(); --i >= 0;)
|
|
{
|
|
RectangleType& r = rects.getReference (i);
|
|
|
|
if (! rect.intersectRectangle (r))
|
|
rects.remove (i);
|
|
else
|
|
notEmpty = true;
|
|
}
|
|
}
|
|
|
|
return notEmpty;
|
|
}
|
|
|
|
/** Removes any areas of the region that lie outside a given rectangle list.
|
|
|
|
Any rectangles in this object which overlap the specified list will be clipped
|
|
and subdivided if necessary.
|
|
|
|
Returns true if the resulting region is not empty, false if it is empty.
|
|
|
|
@see getIntersectionWith
|
|
*/
|
|
template <typename OtherValueType>
|
|
bool clipTo (const RectangleList<OtherValueType>& other)
|
|
{
|
|
if (rects.size() == 0)
|
|
return false;
|
|
|
|
RectangleList result;
|
|
|
|
for (int j = 0; j < rects.size(); ++j)
|
|
{
|
|
const RectangleType& rect = rects.getReference (j);
|
|
|
|
for (const Rectangle<OtherValueType>* r = other.begin(), * const e = other.end(); r != e; ++r)
|
|
{
|
|
RectangleType clipped (r->template toType<ValueType>());
|
|
|
|
if (rect.intersectRectangle (clipped))
|
|
result.rects.add (clipped);
|
|
}
|
|
}
|
|
|
|
swapWith (result);
|
|
return ! isEmpty();
|
|
}
|
|
|
|
/** Creates a region which is the result of clipping this one to a given rectangle.
|
|
|
|
Unlike the other clipTo method, this one doesn't affect this object - it puts the
|
|
resulting region into the list whose reference is passed-in.
|
|
|
|
Returns true if the resulting region is not empty, false if it is empty.
|
|
|
|
@see clipTo
|
|
*/
|
|
bool getIntersectionWith (const RectangleType& rect, RectangleList& destRegion) const
|
|
{
|
|
jassert (rect.isFinite()); // You must provide a valid rectangle to this method!
|
|
|
|
destRegion.clear();
|
|
|
|
if (! rect.isEmpty())
|
|
{
|
|
for (int i = rects.size(); --i >= 0;)
|
|
{
|
|
RectangleType r (rects.getReference (i));
|
|
|
|
if (rect.intersectRectangle (r))
|
|
destRegion.rects.add (r);
|
|
}
|
|
}
|
|
|
|
return destRegion.rects.size() > 0;
|
|
}
|
|
|
|
/** Swaps the contents of this and another list.
|
|
|
|
This swaps their internal pointers, so is hugely faster than using copy-by-value
|
|
to swap them.
|
|
*/
|
|
void swapWith (RectangleList& otherList) noexcept
|
|
{
|
|
rects.swapWith (otherList.rects);
|
|
}
|
|
|
|
//==============================================================================
|
|
/** Checks whether the region contains a given point.
|
|
@returns true if the point lies within one of the rectangles in the list
|
|
*/
|
|
bool containsPoint (Point<ValueType> point) const noexcept
|
|
{
|
|
for (const RectangleType* r = rects.begin(), * const e = rects.end(); r != e; ++r)
|
|
if (r->contains (point))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
/** Checks whether the region contains a given point.
|
|
@returns true if the point lies within one of the rectangles in the list
|
|
*/
|
|
bool containsPoint (ValueType x, ValueType y) const noexcept
|
|
{
|
|
return containsPoint (Point<ValueType> (x, y));
|
|
}
|
|
|
|
/** Checks whether the region contains the whole of a given rectangle.
|
|
|
|
@returns true all parts of the rectangle passed in lie within the region
|
|
defined by this object
|
|
@see intersectsRectangle, containsPoint
|
|
*/
|
|
bool containsRectangle (const RectangleType& rectangleToCheck) const
|
|
{
|
|
if (rects.size() > 1)
|
|
{
|
|
RectangleList r (rectangleToCheck);
|
|
|
|
for (int i = rects.size(); --i >= 0;)
|
|
{
|
|
r.subtract (rects.getReference (i));
|
|
|
|
if (r.rects.size() == 0)
|
|
return true;
|
|
}
|
|
}
|
|
else if (rects.size() > 0)
|
|
{
|
|
return rects.getReference (0).contains (rectangleToCheck);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/** Checks whether the region contains any part of a given rectangle.
|
|
|
|
@returns true if any part of the rectangle passed in lies within the region
|
|
defined by this object
|
|
@see containsRectangle
|
|
*/
|
|
bool intersectsRectangle (const RectangleType& rectangleToCheck) const noexcept
|
|
{
|
|
for (const RectangleType* r = rects.begin(), * const e = rects.end(); r != e; ++r)
|
|
if (r->intersects (rectangleToCheck))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
/** Checks whether this region intersects any part of another one.
|
|
|
|
@see intersectsRectangle
|
|
*/
|
|
bool intersects (const RectangleList& other) const noexcept
|
|
{
|
|
for (const RectangleType* r = rects.begin(), * const e = rects.end(); r != e; ++r)
|
|
if (other.intersectsRectangle (*r))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
//==============================================================================
|
|
/** Returns the smallest rectangle that can enclose the whole of this region. */
|
|
RectangleType getBounds() const noexcept
|
|
{
|
|
if (rects.size() <= 1)
|
|
{
|
|
if (rects.size() == 0)
|
|
return RectangleType();
|
|
|
|
return rects.getReference (0);
|
|
}
|
|
|
|
const RectangleType& r = rects.getReference (0);
|
|
|
|
ValueType minX = r.getX();
|
|
ValueType minY = r.getY();
|
|
ValueType maxX = minX + r.getWidth();
|
|
ValueType maxY = minY + r.getHeight();
|
|
|
|
for (int i = rects.size(); --i > 0;)
|
|
{
|
|
const RectangleType& r2 = rects.getReference (i);
|
|
|
|
minX = jmin (minX, r2.getX());
|
|
minY = jmin (minY, r2.getY());
|
|
maxX = jmax (maxX, r2.getRight());
|
|
maxY = jmax (maxY, r2.getBottom());
|
|
}
|
|
|
|
return RectangleType (minX, minY, maxX - minX, maxY - minY);
|
|
}
|
|
|
|
/** Optimises the list into a minimum number of constituent rectangles.
|
|
|
|
This will try to combine any adjacent rectangles into larger ones where
|
|
possible, to simplify lists that might have been fragmented by repeated
|
|
add/subtract calls.
|
|
*/
|
|
void consolidate()
|
|
{
|
|
for (int i = 0; i < getNumRectangles() - 1; ++i)
|
|
{
|
|
RectangleType& r = rects.getReference (i);
|
|
const ValueType rx1 = r.getX();
|
|
const ValueType ry1 = r.getY();
|
|
const ValueType rx2 = rx1 + r.getWidth();
|
|
const ValueType ry2 = ry1 + r.getHeight();
|
|
|
|
for (int j = rects.size(); --j > i;)
|
|
{
|
|
RectangleType& r2 = rects.getReference (j);
|
|
const ValueType jrx1 = r2.getX();
|
|
const ValueType jry1 = r2.getY();
|
|
const ValueType jrx2 = jrx1 + r2.getWidth();
|
|
const ValueType jry2 = jry1 + r2.getHeight();
|
|
|
|
// if the vertical edges of any blocks are touching and their horizontals don't
|
|
// line up, split them horizontally..
|
|
if (jrx1 == rx2 || jrx2 == rx1)
|
|
{
|
|
if (jry1 > ry1 && jry1 < ry2)
|
|
{
|
|
r.setHeight (jry1 - ry1);
|
|
rects.add (RectangleType (rx1, jry1, rx2 - rx1, ry2 - jry1));
|
|
i = -1;
|
|
break;
|
|
}
|
|
|
|
if (jry2 > ry1 && jry2 < ry2)
|
|
{
|
|
r.setHeight (jry2 - ry1);
|
|
rects.add (RectangleType (rx1, jry2, rx2 - rx1, ry2 - jry2));
|
|
i = -1;
|
|
break;
|
|
}
|
|
else if (ry1 > jry1 && ry1 < jry2)
|
|
{
|
|
r2.setHeight (ry1 - jry1);
|
|
rects.add (RectangleType (jrx1, ry1, jrx2 - jrx1, jry2 - ry1));
|
|
i = -1;
|
|
break;
|
|
}
|
|
else if (ry2 > jry1 && ry2 < jry2)
|
|
{
|
|
r2.setHeight (ry2 - jry1);
|
|
rects.add (RectangleType (jrx1, ry2, jrx2 - jrx1, jry2 - ry2));
|
|
i = -1;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for (int i = 0; i < rects.size() - 1; ++i)
|
|
{
|
|
RectangleType& r = rects.getReference (i);
|
|
|
|
for (int j = rects.size(); --j > i;)
|
|
{
|
|
if (r.enlargeIfAdjacent (rects.getReference (j)))
|
|
{
|
|
rects.remove (j);
|
|
i = -1;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Adds an x and y value to all the coordinates. */
|
|
void offsetAll (Point<ValueType> offset) noexcept
|
|
{
|
|
for (RectangleType* r = rects.begin(), * const e = rects.end(); r != e; ++r)
|
|
*r += offset;
|
|
}
|
|
|
|
/** Adds an x and y value to all the coordinates. */
|
|
void offsetAll (ValueType dx, ValueType dy) noexcept
|
|
{
|
|
offsetAll (Point<ValueType> (dx, dy));
|
|
}
|
|
|
|
/** Scales all the coordinates. */
|
|
template <typename ScaleType>
|
|
void scaleAll (ScaleType scaleFactor) noexcept
|
|
{
|
|
for (RectangleType* r = rects.begin(), * const e = rects.end(); r != e; ++r)
|
|
*r *= scaleFactor;
|
|
}
|
|
|
|
/** Applies a transform to all the rectangles.
|
|
Obviously this will create a mess if the transform involves any
|
|
rotation or skewing.
|
|
*/
|
|
void transformAll (const AffineTransform& transform) noexcept
|
|
{
|
|
for (RectangleType* r = rects.begin(), * const e = rects.end(); r != e; ++r)
|
|
*r = r->transformedBy (transform);
|
|
}
|
|
|
|
//==============================================================================
|
|
/** Creates a Path object to represent this region. */
|
|
Path toPath() const
|
|
{
|
|
Path p;
|
|
|
|
for (int i = 0; i < rects.size(); ++i)
|
|
p.addRectangle (rects.getReference (i));
|
|
|
|
return p;
|
|
}
|
|
|
|
//==============================================================================
|
|
/** Standard method for iterating the rectangles in the list. */
|
|
const RectangleType* begin() const noexcept { return rects.begin(); }
|
|
/** Standard method for iterating the rectangles in the list. */
|
|
const RectangleType* end() const noexcept { return rects.end(); }
|
|
|
|
/** Increases the internal storage to hold a minimum number of rectangles.
|
|
Calling this before adding a large number of rectangles means that
|
|
the array won't have to keep dynamically resizing itself as the elements
|
|
are added, and it'll therefore be more efficient.
|
|
@see Array::ensureStorageAllocated
|
|
*/
|
|
void ensureStorageAllocated (int minNumRectangles)
|
|
{
|
|
rects.ensureStorageAllocated (minNumRectangles);
|
|
}
|
|
|
|
private:
|
|
//==============================================================================
|
|
Array<RectangleType> rects;
|
|
};
|
|
|
|
|
|
#endif // JUCE_RECTANGLELIST_H_INCLUDED
|
|
|