migrating to the latest JUCE version
This commit is contained in:
@ -1,207 +1,207 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Models a 1-dimensional position that can be dragged around by the user, and which
|
||||
will then continue moving with a customisable physics behaviour when released.
|
||||
|
||||
This is useful for things like scrollable views or objects that can be dragged and
|
||||
thrown around with the mouse/touch, and by writing your own behaviour class, you can
|
||||
customise the trajectory that it follows when released.
|
||||
|
||||
The class uses its own Timer to continuously change its value when a drag ends, and
|
||||
Listener objects can be registered to receive callbacks whenever the value changes.
|
||||
|
||||
The value is stored as a double, and can be used to represent whatever units you need.
|
||||
|
||||
The template parameter Behaviour must be a class that implements various methods to
|
||||
return the physics of the value's movement - you can use the classes provided for this
|
||||
in the AnimatedPositionBehaviours namespace, or write your own custom behaviour.
|
||||
|
||||
@see AnimatedPositionBehaviours::ContinuousWithMomentum,
|
||||
AnimatedPositionBehaviours::SnapToPageBoundaries
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
template <typename Behaviour>
|
||||
class AnimatedPosition : private Timer
|
||||
{
|
||||
public:
|
||||
AnimatedPosition()
|
||||
: range (-std::numeric_limits<double>::max(),
|
||||
std::numeric_limits<double>::max())
|
||||
{
|
||||
}
|
||||
|
||||
/** Sets a range within which the value will be constrained. */
|
||||
void setLimits (Range<double> newRange) noexcept
|
||||
{
|
||||
range = newRange;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
/** Called to indicate that the object is now being controlled by a
|
||||
mouse-drag or similar operation.
|
||||
|
||||
After calling this method, you should make calls to the drag() method
|
||||
each time the mouse drags the position around, and always be sure to
|
||||
finish with a call to endDrag() when the mouse is released, which allows
|
||||
the position to continue moving freely according to the specified behaviour.
|
||||
*/
|
||||
void beginDrag()
|
||||
{
|
||||
grabbedPos = position;
|
||||
releaseVelocity = 0;
|
||||
stopTimer();
|
||||
}
|
||||
|
||||
/** Called during a mouse-drag operation, to indicate that the mouse has moved.
|
||||
The delta is the difference between the position when beginDrag() was called
|
||||
and the new position that's required.
|
||||
*/
|
||||
void drag (double deltaFromStartOfDrag)
|
||||
{
|
||||
moveTo (grabbedPos + deltaFromStartOfDrag);
|
||||
}
|
||||
|
||||
/** Called after beginDrag() and drag() to indicate that the drag operation has
|
||||
now finished.
|
||||
*/
|
||||
void endDrag()
|
||||
{
|
||||
startTimerHz (60);
|
||||
}
|
||||
|
||||
/** Called outside of a drag operation to cause a nudge in the specified direction.
|
||||
This is intended for use by e.g. mouse-wheel events.
|
||||
*/
|
||||
void nudge (double deltaFromCurrentPosition)
|
||||
{
|
||||
startTimerHz (10);
|
||||
moveTo (position + deltaFromCurrentPosition);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the current position. */
|
||||
double getPosition() const noexcept
|
||||
{
|
||||
return position;
|
||||
}
|
||||
|
||||
/** Explicitly sets the position and stops any further movement.
|
||||
This will cause a synchronous call to any listeners if the position actually
|
||||
changes.
|
||||
*/
|
||||
void setPosition (double newPosition)
|
||||
{
|
||||
stopTimer();
|
||||
setPositionAndSendChange (newPosition);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
/** Implement this class if you need to receive callbacks when the value of
|
||||
an AnimatedPosition changes.
|
||||
@see AnimatedPosition::addListener, AnimatedPosition::removeListener
|
||||
*/
|
||||
class Listener
|
||||
{
|
||||
public:
|
||||
virtual ~Listener() = default;
|
||||
|
||||
/** Called synchronously when an AnimatedPosition changes. */
|
||||
virtual void positionChanged (AnimatedPosition&, double newPosition) = 0;
|
||||
};
|
||||
|
||||
/** Adds a listener to be called when the value changes. */
|
||||
void addListener (Listener* listener) { listeners.add (listener); }
|
||||
|
||||
/** Removes a previously-registered listener. */
|
||||
void removeListener (Listener* listener) { listeners.remove (listener); }
|
||||
|
||||
//==============================================================================
|
||||
/** The behaviour object.
|
||||
This is public to let you tweak any parameters that it provides.
|
||||
*/
|
||||
Behaviour behaviour;
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
double position = 0.0, grabbedPos = 0.0, releaseVelocity = 0.0;
|
||||
Range<double> range;
|
||||
Time lastUpdate, lastDrag;
|
||||
ListenerList<Listener> listeners;
|
||||
|
||||
static double getSpeed (const Time last, double lastPos,
|
||||
const Time now, double newPos)
|
||||
{
|
||||
auto elapsedSecs = jmax (0.005, (now - last).inSeconds());
|
||||
auto v = (newPos - lastPos) / elapsedSecs;
|
||||
return std::abs (v) > 0.2 ? v : 0.0;
|
||||
}
|
||||
|
||||
void moveTo (double newPos)
|
||||
{
|
||||
auto now = Time::getCurrentTime();
|
||||
releaseVelocity = getSpeed (lastDrag, position, now, newPos);
|
||||
behaviour.releasedWithVelocity (newPos, releaseVelocity);
|
||||
lastDrag = now;
|
||||
|
||||
setPositionAndSendChange (newPos);
|
||||
}
|
||||
|
||||
void setPositionAndSendChange (double newPosition)
|
||||
{
|
||||
newPosition = range.clipValue (newPosition);
|
||||
|
||||
if (position != newPosition)
|
||||
{
|
||||
position = newPosition;
|
||||
listeners.call ([this, newPosition] (Listener& l) { l.positionChanged (*this, newPosition); });
|
||||
}
|
||||
}
|
||||
|
||||
void timerCallback() override
|
||||
{
|
||||
auto now = Time::getCurrentTime();
|
||||
auto elapsed = jlimit (0.001, 0.020, (now - lastUpdate).inSeconds());
|
||||
lastUpdate = now;
|
||||
auto newPos = behaviour.getNextPosition (position, elapsed);
|
||||
|
||||
if (behaviour.isStopped (newPos))
|
||||
stopTimer();
|
||||
else
|
||||
startTimerHz (60);
|
||||
|
||||
setPositionAndSendChange (newPos);
|
||||
}
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AnimatedPosition)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Models a 1-dimensional position that can be dragged around by the user, and which
|
||||
will then continue moving with a customisable physics behaviour when released.
|
||||
|
||||
This is useful for things like scrollable views or objects that can be dragged and
|
||||
thrown around with the mouse/touch, and by writing your own behaviour class, you can
|
||||
customise the trajectory that it follows when released.
|
||||
|
||||
The class uses its own Timer to continuously change its value when a drag ends, and
|
||||
Listener objects can be registered to receive callbacks whenever the value changes.
|
||||
|
||||
The value is stored as a double, and can be used to represent whatever units you need.
|
||||
|
||||
The template parameter Behaviour must be a class that implements various methods to
|
||||
return the physics of the value's movement - you can use the classes provided for this
|
||||
in the AnimatedPositionBehaviours namespace, or write your own custom behaviour.
|
||||
|
||||
@see AnimatedPositionBehaviours::ContinuousWithMomentum,
|
||||
AnimatedPositionBehaviours::SnapToPageBoundaries
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
template <typename Behaviour>
|
||||
class AnimatedPosition : private Timer
|
||||
{
|
||||
public:
|
||||
AnimatedPosition()
|
||||
: range (-std::numeric_limits<double>::max(),
|
||||
std::numeric_limits<double>::max())
|
||||
{
|
||||
}
|
||||
|
||||
/** Sets a range within which the value will be constrained. */
|
||||
void setLimits (Range<double> newRange) noexcept
|
||||
{
|
||||
range = newRange;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
/** Called to indicate that the object is now being controlled by a
|
||||
mouse-drag or similar operation.
|
||||
|
||||
After calling this method, you should make calls to the drag() method
|
||||
each time the mouse drags the position around, and always be sure to
|
||||
finish with a call to endDrag() when the mouse is released, which allows
|
||||
the position to continue moving freely according to the specified behaviour.
|
||||
*/
|
||||
void beginDrag()
|
||||
{
|
||||
grabbedPos = position;
|
||||
releaseVelocity = 0;
|
||||
stopTimer();
|
||||
}
|
||||
|
||||
/** Called during a mouse-drag operation, to indicate that the mouse has moved.
|
||||
The delta is the difference between the position when beginDrag() was called
|
||||
and the new position that's required.
|
||||
*/
|
||||
void drag (double deltaFromStartOfDrag)
|
||||
{
|
||||
moveTo (grabbedPos + deltaFromStartOfDrag);
|
||||
}
|
||||
|
||||
/** Called after beginDrag() and drag() to indicate that the drag operation has
|
||||
now finished.
|
||||
*/
|
||||
void endDrag()
|
||||
{
|
||||
startTimerHz (60);
|
||||
}
|
||||
|
||||
/** Called outside of a drag operation to cause a nudge in the specified direction.
|
||||
This is intended for use by e.g. mouse-wheel events.
|
||||
*/
|
||||
void nudge (double deltaFromCurrentPosition)
|
||||
{
|
||||
startTimerHz (10);
|
||||
moveTo (position + deltaFromCurrentPosition);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the current position. */
|
||||
double getPosition() const noexcept
|
||||
{
|
||||
return position;
|
||||
}
|
||||
|
||||
/** Explicitly sets the position and stops any further movement.
|
||||
This will cause a synchronous call to any listeners if the position actually
|
||||
changes.
|
||||
*/
|
||||
void setPosition (double newPosition)
|
||||
{
|
||||
stopTimer();
|
||||
setPositionAndSendChange (newPosition);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
/** Implement this class if you need to receive callbacks when the value of
|
||||
an AnimatedPosition changes.
|
||||
@see AnimatedPosition::addListener, AnimatedPosition::removeListener
|
||||
*/
|
||||
class Listener
|
||||
{
|
||||
public:
|
||||
virtual ~Listener() = default;
|
||||
|
||||
/** Called synchronously when an AnimatedPosition changes. */
|
||||
virtual void positionChanged (AnimatedPosition&, double newPosition) = 0;
|
||||
};
|
||||
|
||||
/** Adds a listener to be called when the value changes. */
|
||||
void addListener (Listener* listener) { listeners.add (listener); }
|
||||
|
||||
/** Removes a previously-registered listener. */
|
||||
void removeListener (Listener* listener) { listeners.remove (listener); }
|
||||
|
||||
//==============================================================================
|
||||
/** The behaviour object.
|
||||
This is public to let you tweak any parameters that it provides.
|
||||
*/
|
||||
Behaviour behaviour;
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
double position = 0.0, grabbedPos = 0.0, releaseVelocity = 0.0;
|
||||
Range<double> range;
|
||||
Time lastUpdate, lastDrag;
|
||||
ListenerList<Listener> listeners;
|
||||
|
||||
static double getSpeed (const Time last, double lastPos,
|
||||
const Time now, double newPos)
|
||||
{
|
||||
auto elapsedSecs = jmax (0.005, (now - last).inSeconds());
|
||||
auto v = (newPos - lastPos) / elapsedSecs;
|
||||
return std::abs (v) > 0.2 ? v : 0.0;
|
||||
}
|
||||
|
||||
void moveTo (double newPos)
|
||||
{
|
||||
auto now = Time::getCurrentTime();
|
||||
releaseVelocity = getSpeed (lastDrag, position, now, newPos);
|
||||
behaviour.releasedWithVelocity (newPos, releaseVelocity);
|
||||
lastDrag = now;
|
||||
|
||||
setPositionAndSendChange (newPos);
|
||||
}
|
||||
|
||||
void setPositionAndSendChange (double newPosition)
|
||||
{
|
||||
newPosition = range.clipValue (newPosition);
|
||||
|
||||
if (position != newPosition)
|
||||
{
|
||||
position = newPosition;
|
||||
listeners.call ([this, newPosition] (Listener& l) { l.positionChanged (*this, newPosition); });
|
||||
}
|
||||
}
|
||||
|
||||
void timerCallback() override
|
||||
{
|
||||
auto now = Time::getCurrentTime();
|
||||
auto elapsed = jlimit (0.001, 0.020, (now - lastUpdate).inSeconds());
|
||||
lastUpdate = now;
|
||||
auto newPos = behaviour.getNextPosition (position, elapsed);
|
||||
|
||||
if (behaviour.isStopped (newPos))
|
||||
stopTimer();
|
||||
else
|
||||
startTimerHz (60);
|
||||
|
||||
setPositionAndSendChange (newPos);
|
||||
}
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AnimatedPosition)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
|
@ -1,156 +1,156 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/** Contains classes for different types of physics behaviours - these classes
|
||||
are used as template parameters for the AnimatedPosition class.
|
||||
*/
|
||||
namespace AnimatedPositionBehaviours
|
||||
{
|
||||
/** A non-snapping behaviour that allows the content to be freely flicked in
|
||||
either direction, with momentum based on the velocity at which it was
|
||||
released, and variable friction to make it come to a halt.
|
||||
|
||||
This class is intended to be used as a template parameter to the
|
||||
AnimatedPosition class.
|
||||
|
||||
@see AnimatedPosition
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
struct ContinuousWithMomentum
|
||||
{
|
||||
ContinuousWithMomentum() = default;
|
||||
|
||||
/** Sets the friction that damps the movement of the value.
|
||||
A typical value is 0.08; higher values indicate more friction.
|
||||
*/
|
||||
void setFriction (double newFriction) noexcept
|
||||
{
|
||||
damping = 1.0 - newFriction;
|
||||
}
|
||||
|
||||
/** Sets the minimum velocity of the movement. Any velocity that's slower than
|
||||
this will stop the animation. The default is 0.05. */
|
||||
void setMinimumVelocity (double newMinimumVelocityToUse) noexcept
|
||||
{
|
||||
minimumVelocity = newMinimumVelocityToUse;
|
||||
}
|
||||
|
||||
/** Called by the AnimatedPosition class. This tells us the position and
|
||||
velocity at which the user is about to release the object.
|
||||
The velocity is measured in units/second.
|
||||
*/
|
||||
void releasedWithVelocity (double /*position*/, double releaseVelocity) noexcept
|
||||
{
|
||||
velocity = releaseVelocity;
|
||||
}
|
||||
|
||||
/** Called by the AnimatedPosition class to get the new position, after
|
||||
the given time has elapsed.
|
||||
*/
|
||||
double getNextPosition (double oldPos, double elapsedSeconds) noexcept
|
||||
{
|
||||
velocity *= damping;
|
||||
|
||||
if (std::abs (velocity) < minimumVelocity)
|
||||
velocity = 0;
|
||||
|
||||
return oldPos + velocity * elapsedSeconds;
|
||||
}
|
||||
|
||||
/** Called by the AnimatedPosition class to check whether the object
|
||||
is now stationary.
|
||||
*/
|
||||
bool isStopped (double /*position*/) const noexcept
|
||||
{
|
||||
return velocity == 0.0;
|
||||
}
|
||||
|
||||
private:
|
||||
double velocity = 0, damping = 0.92, minimumVelocity = 0.05;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** A behaviour that gravitates an AnimatedPosition object towards the nearest
|
||||
integer position when released.
|
||||
|
||||
This class is intended to be used as a template parameter to the
|
||||
AnimatedPosition class. It's handy when using an AnimatedPosition to show a
|
||||
series of pages, because it allows the pages can be scrolled smoothly, but when
|
||||
released, snaps back to show a whole page.
|
||||
|
||||
@see AnimatedPosition
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
struct SnapToPageBoundaries
|
||||
{
|
||||
SnapToPageBoundaries() = default;
|
||||
|
||||
/** Called by the AnimatedPosition class. This tells us the position and
|
||||
velocity at which the user is about to release the object.
|
||||
The velocity is measured in units/second.
|
||||
*/
|
||||
void releasedWithVelocity (double position, double releaseVelocity) noexcept
|
||||
{
|
||||
targetSnapPosition = std::floor (position + 0.5);
|
||||
|
||||
if (releaseVelocity > 1.0 && targetSnapPosition < position) ++targetSnapPosition;
|
||||
if (releaseVelocity < -1.0 && targetSnapPosition > position) --targetSnapPosition;
|
||||
}
|
||||
|
||||
/** Called by the AnimatedPosition class to get the new position, after
|
||||
the given time has elapsed.
|
||||
*/
|
||||
double getNextPosition (double oldPos, double elapsedSeconds) const noexcept
|
||||
{
|
||||
if (isStopped (oldPos))
|
||||
return targetSnapPosition;
|
||||
|
||||
const double snapSpeed = 10.0;
|
||||
const double velocity = (targetSnapPosition - oldPos) * snapSpeed;
|
||||
const double newPos = oldPos + velocity * elapsedSeconds;
|
||||
|
||||
return isStopped (newPos) ? targetSnapPosition : newPos;
|
||||
}
|
||||
|
||||
/** Called by the AnimatedPosition class to check whether the object
|
||||
is now stationary.
|
||||
*/
|
||||
bool isStopped (double position) const noexcept
|
||||
{
|
||||
return std::abs (targetSnapPosition - position) < 0.001;
|
||||
}
|
||||
|
||||
private:
|
||||
double targetSnapPosition = 0.0;
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/** Contains classes for different types of physics behaviours - these classes
|
||||
are used as template parameters for the AnimatedPosition class.
|
||||
*/
|
||||
namespace AnimatedPositionBehaviours
|
||||
{
|
||||
/** A non-snapping behaviour that allows the content to be freely flicked in
|
||||
either direction, with momentum based on the velocity at which it was
|
||||
released, and variable friction to make it come to a halt.
|
||||
|
||||
This class is intended to be used as a template parameter to the
|
||||
AnimatedPosition class.
|
||||
|
||||
@see AnimatedPosition
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
struct ContinuousWithMomentum
|
||||
{
|
||||
ContinuousWithMomentum() = default;
|
||||
|
||||
/** Sets the friction that damps the movement of the value.
|
||||
A typical value is 0.08; higher values indicate more friction.
|
||||
*/
|
||||
void setFriction (double newFriction) noexcept
|
||||
{
|
||||
damping = 1.0 - newFriction;
|
||||
}
|
||||
|
||||
/** Sets the minimum velocity of the movement. Any velocity that's slower than
|
||||
this will stop the animation. The default is 0.05. */
|
||||
void setMinimumVelocity (double newMinimumVelocityToUse) noexcept
|
||||
{
|
||||
minimumVelocity = newMinimumVelocityToUse;
|
||||
}
|
||||
|
||||
/** Called by the AnimatedPosition class. This tells us the position and
|
||||
velocity at which the user is about to release the object.
|
||||
The velocity is measured in units/second.
|
||||
*/
|
||||
void releasedWithVelocity (double /*position*/, double releaseVelocity) noexcept
|
||||
{
|
||||
velocity = releaseVelocity;
|
||||
}
|
||||
|
||||
/** Called by the AnimatedPosition class to get the new position, after
|
||||
the given time has elapsed.
|
||||
*/
|
||||
double getNextPosition (double oldPos, double elapsedSeconds) noexcept
|
||||
{
|
||||
velocity *= damping;
|
||||
|
||||
if (std::abs (velocity) < minimumVelocity)
|
||||
velocity = 0;
|
||||
|
||||
return oldPos + velocity * elapsedSeconds;
|
||||
}
|
||||
|
||||
/** Called by the AnimatedPosition class to check whether the object
|
||||
is now stationary.
|
||||
*/
|
||||
bool isStopped (double /*position*/) const noexcept
|
||||
{
|
||||
return velocity == 0.0;
|
||||
}
|
||||
|
||||
private:
|
||||
double velocity = 0, damping = 0.92, minimumVelocity = 0.05;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** A behaviour that gravitates an AnimatedPosition object towards the nearest
|
||||
integer position when released.
|
||||
|
||||
This class is intended to be used as a template parameter to the
|
||||
AnimatedPosition class. It's handy when using an AnimatedPosition to show a
|
||||
series of pages, because it allows the pages can be scrolled smoothly, but when
|
||||
released, snaps back to show a whole page.
|
||||
|
||||
@see AnimatedPosition
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
struct SnapToPageBoundaries
|
||||
{
|
||||
SnapToPageBoundaries() = default;
|
||||
|
||||
/** Called by the AnimatedPosition class. This tells us the position and
|
||||
velocity at which the user is about to release the object.
|
||||
The velocity is measured in units/second.
|
||||
*/
|
||||
void releasedWithVelocity (double position, double releaseVelocity) noexcept
|
||||
{
|
||||
targetSnapPosition = std::floor (position + 0.5);
|
||||
|
||||
if (releaseVelocity > 1.0 && targetSnapPosition < position) ++targetSnapPosition;
|
||||
if (releaseVelocity < -1.0 && targetSnapPosition > position) --targetSnapPosition;
|
||||
}
|
||||
|
||||
/** Called by the AnimatedPosition class to get the new position, after
|
||||
the given time has elapsed.
|
||||
*/
|
||||
double getNextPosition (double oldPos, double elapsedSeconds) const noexcept
|
||||
{
|
||||
if (isStopped (oldPos))
|
||||
return targetSnapPosition;
|
||||
|
||||
const double snapSpeed = 10.0;
|
||||
const double velocity = (targetSnapPosition - oldPos) * snapSpeed;
|
||||
const double newPos = oldPos + velocity * elapsedSeconds;
|
||||
|
||||
return isStopped (newPos) ? targetSnapPosition : newPos;
|
||||
}
|
||||
|
||||
/** Called by the AnimatedPosition class to check whether the object
|
||||
is now stationary.
|
||||
*/
|
||||
bool isStopped (double position) const noexcept
|
||||
{
|
||||
return std::abs (targetSnapPosition - position) < 0.001;
|
||||
}
|
||||
|
||||
private:
|
||||
double targetSnapPosition = 0.0;
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
|
@ -1,354 +1,354 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
class ComponentAnimator::AnimationTask
|
||||
{
|
||||
public:
|
||||
AnimationTask (Component* c) noexcept : component (c) {}
|
||||
|
||||
~AnimationTask()
|
||||
{
|
||||
proxy.deleteAndZero();
|
||||
}
|
||||
|
||||
void reset (const Rectangle<int>& finalBounds,
|
||||
float finalAlpha,
|
||||
int millisecondsToSpendMoving,
|
||||
bool useProxyComponent,
|
||||
double startSpd, double endSpd)
|
||||
{
|
||||
msElapsed = 0;
|
||||
msTotal = jmax (1, millisecondsToSpendMoving);
|
||||
lastProgress = 0;
|
||||
destination = finalBounds;
|
||||
destAlpha = finalAlpha;
|
||||
|
||||
isMoving = (finalBounds != component->getBounds());
|
||||
isChangingAlpha = (finalAlpha != component->getAlpha());
|
||||
|
||||
left = component->getX();
|
||||
top = component->getY();
|
||||
right = component->getRight();
|
||||
bottom = component->getBottom();
|
||||
alpha = component->getAlpha();
|
||||
|
||||
const double invTotalDistance = 4.0 / (startSpd + endSpd + 2.0);
|
||||
startSpeed = jmax (0.0, startSpd * invTotalDistance);
|
||||
midSpeed = invTotalDistance;
|
||||
endSpeed = jmax (0.0, endSpd * invTotalDistance);
|
||||
|
||||
proxy.deleteAndZero();
|
||||
|
||||
if (useProxyComponent)
|
||||
proxy = new ProxyComponent (*component);
|
||||
|
||||
component->setVisible (! useProxyComponent);
|
||||
}
|
||||
|
||||
bool useTimeslice (const int elapsed)
|
||||
{
|
||||
if (auto* c = proxy != nullptr ? proxy.getComponent()
|
||||
: component.get())
|
||||
{
|
||||
msElapsed += elapsed;
|
||||
double newProgress = msElapsed / (double) msTotal;
|
||||
|
||||
if (newProgress >= 0 && newProgress < 1.0)
|
||||
{
|
||||
const WeakReference<AnimationTask> weakRef (this);
|
||||
newProgress = timeToDistance (newProgress);
|
||||
const double delta = (newProgress - lastProgress) / (1.0 - lastProgress);
|
||||
jassert (newProgress >= lastProgress);
|
||||
lastProgress = newProgress;
|
||||
|
||||
if (delta < 1.0)
|
||||
{
|
||||
bool stillBusy = false;
|
||||
|
||||
if (isMoving)
|
||||
{
|
||||
left += (destination.getX() - left) * delta;
|
||||
top += (destination.getY() - top) * delta;
|
||||
right += (destination.getRight() - right) * delta;
|
||||
bottom += (destination.getBottom() - bottom) * delta;
|
||||
|
||||
const Rectangle<int> newBounds (roundToInt (left),
|
||||
roundToInt (top),
|
||||
roundToInt (right - left),
|
||||
roundToInt (bottom - top));
|
||||
|
||||
if (newBounds != destination)
|
||||
{
|
||||
c->setBounds (newBounds);
|
||||
stillBusy = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Check whether the animation was cancelled/deleted during
|
||||
// a callback during the setBounds method
|
||||
if (weakRef.wasObjectDeleted())
|
||||
return false;
|
||||
|
||||
if (isChangingAlpha)
|
||||
{
|
||||
alpha += (destAlpha - alpha) * delta;
|
||||
c->setAlpha ((float) alpha);
|
||||
stillBusy = true;
|
||||
}
|
||||
|
||||
if (stillBusy)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
moveToFinalDestination();
|
||||
return false;
|
||||
}
|
||||
|
||||
void moveToFinalDestination()
|
||||
{
|
||||
if (component != nullptr)
|
||||
{
|
||||
const WeakReference<AnimationTask> weakRef (this);
|
||||
component->setAlpha ((float) destAlpha);
|
||||
component->setBounds (destination);
|
||||
|
||||
if (! weakRef.wasObjectDeleted())
|
||||
if (proxy != nullptr)
|
||||
component->setVisible (destAlpha > 0);
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
struct ProxyComponent : public Component
|
||||
{
|
||||
ProxyComponent (Component& c)
|
||||
{
|
||||
setWantsKeyboardFocus (false);
|
||||
setBounds (c.getBounds());
|
||||
setTransform (c.getTransform());
|
||||
setAlpha (c.getAlpha());
|
||||
setInterceptsMouseClicks (false, false);
|
||||
|
||||
if (auto* parent = c.getParentComponent())
|
||||
parent->addAndMakeVisible (this);
|
||||
else if (c.isOnDesktop() && c.getPeer() != nullptr)
|
||||
addToDesktop (c.getPeer()->getStyleFlags() | ComponentPeer::windowIgnoresKeyPresses);
|
||||
else
|
||||
jassertfalse; // seem to be trying to animate a component that's not visible..
|
||||
|
||||
auto scale = (float) Desktop::getInstance().getDisplays().getDisplayForRect (getScreenBounds())->scale
|
||||
* Component::getApproximateScaleFactorForComponent (&c);
|
||||
|
||||
image = c.createComponentSnapshot (c.getLocalBounds(), false, scale);
|
||||
|
||||
setVisible (true);
|
||||
toBehind (&c);
|
||||
}
|
||||
|
||||
void paint (Graphics& g) override
|
||||
{
|
||||
g.setOpacity (1.0f);
|
||||
g.drawImageTransformed (image, AffineTransform::scale ((float) getWidth() / (float) jmax (1, image.getWidth()),
|
||||
(float) getHeight() / (float) jmax (1, image.getHeight())), false);
|
||||
}
|
||||
|
||||
private:
|
||||
std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override
|
||||
{
|
||||
return createIgnoredAccessibilityHandler (*this);
|
||||
}
|
||||
|
||||
Image image;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ProxyComponent)
|
||||
};
|
||||
|
||||
WeakReference<Component> component;
|
||||
Component::SafePointer<Component> proxy;
|
||||
|
||||
Rectangle<int> destination;
|
||||
double destAlpha;
|
||||
|
||||
int msElapsed, msTotal;
|
||||
double startSpeed, midSpeed, endSpeed, lastProgress;
|
||||
double left, top, right, bottom, alpha;
|
||||
bool isMoving, isChangingAlpha;
|
||||
|
||||
private:
|
||||
double timeToDistance (const double time) const noexcept
|
||||
{
|
||||
return (time < 0.5) ? time * (startSpeed + time * (midSpeed - startSpeed))
|
||||
: 0.5 * (startSpeed + 0.5 * (midSpeed - startSpeed))
|
||||
+ (time - 0.5) * (midSpeed + (time - 0.5) * (endSpeed - midSpeed));
|
||||
}
|
||||
|
||||
JUCE_DECLARE_WEAK_REFERENCEABLE (AnimationTask)
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AnimationTask)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
ComponentAnimator::ComponentAnimator() : lastTime (0) {}
|
||||
ComponentAnimator::~ComponentAnimator() {}
|
||||
|
||||
//==============================================================================
|
||||
ComponentAnimator::AnimationTask* ComponentAnimator::findTaskFor (Component* const component) const noexcept
|
||||
{
|
||||
for (int i = tasks.size(); --i >= 0;)
|
||||
if (component == tasks.getUnchecked(i)->component.get())
|
||||
return tasks.getUnchecked(i);
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void ComponentAnimator::animateComponent (Component* const component,
|
||||
const Rectangle<int>& finalBounds,
|
||||
const float finalAlpha,
|
||||
const int millisecondsToSpendMoving,
|
||||
const bool useProxyComponent,
|
||||
const double startSpeed,
|
||||
const double endSpeed)
|
||||
{
|
||||
// the speeds must be 0 or greater!
|
||||
jassert (startSpeed >= 0 && endSpeed >= 0);
|
||||
|
||||
if (component != nullptr)
|
||||
{
|
||||
auto* at = findTaskFor (component);
|
||||
|
||||
if (at == nullptr)
|
||||
{
|
||||
at = new AnimationTask (component);
|
||||
tasks.add (at);
|
||||
sendChangeMessage();
|
||||
}
|
||||
|
||||
at->reset (finalBounds, finalAlpha, millisecondsToSpendMoving,
|
||||
useProxyComponent, startSpeed, endSpeed);
|
||||
|
||||
if (! isTimerRunning())
|
||||
{
|
||||
lastTime = Time::getMillisecondCounter();
|
||||
startTimerHz (50);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ComponentAnimator::fadeOut (Component* component, int millisecondsToTake)
|
||||
{
|
||||
if (component != nullptr)
|
||||
{
|
||||
if (component->isShowing() && millisecondsToTake > 0)
|
||||
animateComponent (component, component->getBounds(), 0.0f, millisecondsToTake, true, 1.0, 1.0);
|
||||
|
||||
component->setVisible (false);
|
||||
}
|
||||
}
|
||||
|
||||
void ComponentAnimator::fadeIn (Component* component, int millisecondsToTake)
|
||||
{
|
||||
if (component != nullptr && ! (component->isVisible() && component->getAlpha() == 1.0f))
|
||||
{
|
||||
component->setAlpha (0.0f);
|
||||
component->setVisible (true);
|
||||
animateComponent (component, component->getBounds(), 1.0f, millisecondsToTake, false, 1.0, 1.0);
|
||||
}
|
||||
}
|
||||
|
||||
void ComponentAnimator::cancelAllAnimations (const bool moveComponentsToTheirFinalPositions)
|
||||
{
|
||||
if (tasks.size() > 0)
|
||||
{
|
||||
if (moveComponentsToTheirFinalPositions)
|
||||
for (int i = tasks.size(); --i >= 0;)
|
||||
tasks.getUnchecked(i)->moveToFinalDestination();
|
||||
|
||||
tasks.clear();
|
||||
sendChangeMessage();
|
||||
}
|
||||
}
|
||||
|
||||
void ComponentAnimator::cancelAnimation (Component* const component,
|
||||
const bool moveComponentToItsFinalPosition)
|
||||
{
|
||||
if (auto* at = findTaskFor (component))
|
||||
{
|
||||
if (moveComponentToItsFinalPosition)
|
||||
at->moveToFinalDestination();
|
||||
|
||||
tasks.removeObject (at);
|
||||
sendChangeMessage();
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle<int> ComponentAnimator::getComponentDestination (Component* const component)
|
||||
{
|
||||
jassert (component != nullptr);
|
||||
|
||||
if (auto* at = findTaskFor (component))
|
||||
return at->destination;
|
||||
|
||||
return component->getBounds();
|
||||
}
|
||||
|
||||
bool ComponentAnimator::isAnimating (Component* component) const noexcept
|
||||
{
|
||||
return findTaskFor (component) != nullptr;
|
||||
}
|
||||
|
||||
bool ComponentAnimator::isAnimating() const noexcept
|
||||
{
|
||||
return tasks.size() != 0;
|
||||
}
|
||||
|
||||
void ComponentAnimator::timerCallback()
|
||||
{
|
||||
auto timeNow = Time::getMillisecondCounter();
|
||||
|
||||
if (lastTime == 0)
|
||||
lastTime = timeNow;
|
||||
|
||||
auto elapsed = (int) (timeNow - lastTime);
|
||||
|
||||
for (auto* task : Array<AnimationTask*> (tasks.begin(), tasks.size()))
|
||||
{
|
||||
if (tasks.contains (task) && ! task->useTimeslice (elapsed))
|
||||
{
|
||||
tasks.removeObject (task);
|
||||
sendChangeMessage();
|
||||
}
|
||||
}
|
||||
|
||||
lastTime = timeNow;
|
||||
|
||||
if (tasks.size() == 0)
|
||||
stopTimer();
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
class ComponentAnimator::AnimationTask
|
||||
{
|
||||
public:
|
||||
AnimationTask (Component* c) noexcept : component (c) {}
|
||||
|
||||
~AnimationTask()
|
||||
{
|
||||
proxy.deleteAndZero();
|
||||
}
|
||||
|
||||
void reset (const Rectangle<int>& finalBounds,
|
||||
float finalAlpha,
|
||||
int millisecondsToSpendMoving,
|
||||
bool useProxyComponent,
|
||||
double startSpd, double endSpd)
|
||||
{
|
||||
msElapsed = 0;
|
||||
msTotal = jmax (1, millisecondsToSpendMoving);
|
||||
lastProgress = 0;
|
||||
destination = finalBounds;
|
||||
destAlpha = finalAlpha;
|
||||
|
||||
isMoving = (finalBounds != component->getBounds());
|
||||
isChangingAlpha = (finalAlpha != component->getAlpha());
|
||||
|
||||
left = component->getX();
|
||||
top = component->getY();
|
||||
right = component->getRight();
|
||||
bottom = component->getBottom();
|
||||
alpha = component->getAlpha();
|
||||
|
||||
const double invTotalDistance = 4.0 / (startSpd + endSpd + 2.0);
|
||||
startSpeed = jmax (0.0, startSpd * invTotalDistance);
|
||||
midSpeed = invTotalDistance;
|
||||
endSpeed = jmax (0.0, endSpd * invTotalDistance);
|
||||
|
||||
proxy.deleteAndZero();
|
||||
|
||||
if (useProxyComponent)
|
||||
proxy = new ProxyComponent (*component);
|
||||
|
||||
component->setVisible (! useProxyComponent);
|
||||
}
|
||||
|
||||
bool useTimeslice (const int elapsed)
|
||||
{
|
||||
if (auto* c = proxy != nullptr ? proxy.getComponent()
|
||||
: component.get())
|
||||
{
|
||||
msElapsed += elapsed;
|
||||
double newProgress = msElapsed / (double) msTotal;
|
||||
|
||||
if (newProgress >= 0 && newProgress < 1.0)
|
||||
{
|
||||
const WeakReference<AnimationTask> weakRef (this);
|
||||
newProgress = timeToDistance (newProgress);
|
||||
const double delta = (newProgress - lastProgress) / (1.0 - lastProgress);
|
||||
jassert (newProgress >= lastProgress);
|
||||
lastProgress = newProgress;
|
||||
|
||||
if (delta < 1.0)
|
||||
{
|
||||
bool stillBusy = false;
|
||||
|
||||
if (isMoving)
|
||||
{
|
||||
left += (destination.getX() - left) * delta;
|
||||
top += (destination.getY() - top) * delta;
|
||||
right += (destination.getRight() - right) * delta;
|
||||
bottom += (destination.getBottom() - bottom) * delta;
|
||||
|
||||
const Rectangle<int> newBounds (roundToInt (left),
|
||||
roundToInt (top),
|
||||
roundToInt (right - left),
|
||||
roundToInt (bottom - top));
|
||||
|
||||
if (newBounds != destination)
|
||||
{
|
||||
c->setBounds (newBounds);
|
||||
stillBusy = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Check whether the animation was cancelled/deleted during
|
||||
// a callback during the setBounds method
|
||||
if (weakRef.wasObjectDeleted())
|
||||
return false;
|
||||
|
||||
if (isChangingAlpha)
|
||||
{
|
||||
alpha += (destAlpha - alpha) * delta;
|
||||
c->setAlpha ((float) alpha);
|
||||
stillBusy = true;
|
||||
}
|
||||
|
||||
if (stillBusy)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
moveToFinalDestination();
|
||||
return false;
|
||||
}
|
||||
|
||||
void moveToFinalDestination()
|
||||
{
|
||||
if (component != nullptr)
|
||||
{
|
||||
const WeakReference<AnimationTask> weakRef (this);
|
||||
component->setAlpha ((float) destAlpha);
|
||||
component->setBounds (destination);
|
||||
|
||||
if (! weakRef.wasObjectDeleted())
|
||||
if (proxy != nullptr)
|
||||
component->setVisible (destAlpha > 0);
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
struct ProxyComponent : public Component
|
||||
{
|
||||
ProxyComponent (Component& c)
|
||||
{
|
||||
setWantsKeyboardFocus (false);
|
||||
setBounds (c.getBounds());
|
||||
setTransform (c.getTransform());
|
||||
setAlpha (c.getAlpha());
|
||||
setInterceptsMouseClicks (false, false);
|
||||
|
||||
if (auto* parent = c.getParentComponent())
|
||||
parent->addAndMakeVisible (this);
|
||||
else if (c.isOnDesktop() && c.getPeer() != nullptr)
|
||||
addToDesktop (c.getPeer()->getStyleFlags() | ComponentPeer::windowIgnoresKeyPresses);
|
||||
else
|
||||
jassertfalse; // seem to be trying to animate a component that's not visible..
|
||||
|
||||
auto scale = (float) Desktop::getInstance().getDisplays().getDisplayForRect (getScreenBounds())->scale
|
||||
* Component::getApproximateScaleFactorForComponent (&c);
|
||||
|
||||
image = c.createComponentSnapshot (c.getLocalBounds(), false, scale);
|
||||
|
||||
setVisible (true);
|
||||
toBehind (&c);
|
||||
}
|
||||
|
||||
void paint (Graphics& g) override
|
||||
{
|
||||
g.setOpacity (1.0f);
|
||||
g.drawImageTransformed (image, AffineTransform::scale ((float) getWidth() / (float) jmax (1, image.getWidth()),
|
||||
(float) getHeight() / (float) jmax (1, image.getHeight())), false);
|
||||
}
|
||||
|
||||
private:
|
||||
std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override
|
||||
{
|
||||
return createIgnoredAccessibilityHandler (*this);
|
||||
}
|
||||
|
||||
Image image;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ProxyComponent)
|
||||
};
|
||||
|
||||
WeakReference<Component> component;
|
||||
Component::SafePointer<Component> proxy;
|
||||
|
||||
Rectangle<int> destination;
|
||||
double destAlpha;
|
||||
|
||||
int msElapsed, msTotal;
|
||||
double startSpeed, midSpeed, endSpeed, lastProgress;
|
||||
double left, top, right, bottom, alpha;
|
||||
bool isMoving, isChangingAlpha;
|
||||
|
||||
private:
|
||||
double timeToDistance (const double time) const noexcept
|
||||
{
|
||||
return (time < 0.5) ? time * (startSpeed + time * (midSpeed - startSpeed))
|
||||
: 0.5 * (startSpeed + 0.5 * (midSpeed - startSpeed))
|
||||
+ (time - 0.5) * (midSpeed + (time - 0.5) * (endSpeed - midSpeed));
|
||||
}
|
||||
|
||||
JUCE_DECLARE_WEAK_REFERENCEABLE (AnimationTask)
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AnimationTask)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
ComponentAnimator::ComponentAnimator() : lastTime (0) {}
|
||||
ComponentAnimator::~ComponentAnimator() {}
|
||||
|
||||
//==============================================================================
|
||||
ComponentAnimator::AnimationTask* ComponentAnimator::findTaskFor (Component* const component) const noexcept
|
||||
{
|
||||
for (int i = tasks.size(); --i >= 0;)
|
||||
if (component == tasks.getUnchecked(i)->component.get())
|
||||
return tasks.getUnchecked(i);
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void ComponentAnimator::animateComponent (Component* const component,
|
||||
const Rectangle<int>& finalBounds,
|
||||
const float finalAlpha,
|
||||
const int millisecondsToSpendMoving,
|
||||
const bool useProxyComponent,
|
||||
const double startSpeed,
|
||||
const double endSpeed)
|
||||
{
|
||||
// the speeds must be 0 or greater!
|
||||
jassert (startSpeed >= 0 && endSpeed >= 0);
|
||||
|
||||
if (component != nullptr)
|
||||
{
|
||||
auto* at = findTaskFor (component);
|
||||
|
||||
if (at == nullptr)
|
||||
{
|
||||
at = new AnimationTask (component);
|
||||
tasks.add (at);
|
||||
sendChangeMessage();
|
||||
}
|
||||
|
||||
at->reset (finalBounds, finalAlpha, millisecondsToSpendMoving,
|
||||
useProxyComponent, startSpeed, endSpeed);
|
||||
|
||||
if (! isTimerRunning())
|
||||
{
|
||||
lastTime = Time::getMillisecondCounter();
|
||||
startTimerHz (50);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ComponentAnimator::fadeOut (Component* component, int millisecondsToTake)
|
||||
{
|
||||
if (component != nullptr)
|
||||
{
|
||||
if (component->isShowing() && millisecondsToTake > 0)
|
||||
animateComponent (component, component->getBounds(), 0.0f, millisecondsToTake, true, 1.0, 1.0);
|
||||
|
||||
component->setVisible (false);
|
||||
}
|
||||
}
|
||||
|
||||
void ComponentAnimator::fadeIn (Component* component, int millisecondsToTake)
|
||||
{
|
||||
if (component != nullptr && ! (component->isVisible() && component->getAlpha() == 1.0f))
|
||||
{
|
||||
component->setAlpha (0.0f);
|
||||
component->setVisible (true);
|
||||
animateComponent (component, component->getBounds(), 1.0f, millisecondsToTake, false, 1.0, 1.0);
|
||||
}
|
||||
}
|
||||
|
||||
void ComponentAnimator::cancelAllAnimations (const bool moveComponentsToTheirFinalPositions)
|
||||
{
|
||||
if (tasks.size() > 0)
|
||||
{
|
||||
if (moveComponentsToTheirFinalPositions)
|
||||
for (int i = tasks.size(); --i >= 0;)
|
||||
tasks.getUnchecked(i)->moveToFinalDestination();
|
||||
|
||||
tasks.clear();
|
||||
sendChangeMessage();
|
||||
}
|
||||
}
|
||||
|
||||
void ComponentAnimator::cancelAnimation (Component* const component,
|
||||
const bool moveComponentToItsFinalPosition)
|
||||
{
|
||||
if (auto* at = findTaskFor (component))
|
||||
{
|
||||
if (moveComponentToItsFinalPosition)
|
||||
at->moveToFinalDestination();
|
||||
|
||||
tasks.removeObject (at);
|
||||
sendChangeMessage();
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle<int> ComponentAnimator::getComponentDestination (Component* const component)
|
||||
{
|
||||
jassert (component != nullptr);
|
||||
|
||||
if (auto* at = findTaskFor (component))
|
||||
return at->destination;
|
||||
|
||||
return component->getBounds();
|
||||
}
|
||||
|
||||
bool ComponentAnimator::isAnimating (Component* component) const noexcept
|
||||
{
|
||||
return findTaskFor (component) != nullptr;
|
||||
}
|
||||
|
||||
bool ComponentAnimator::isAnimating() const noexcept
|
||||
{
|
||||
return tasks.size() != 0;
|
||||
}
|
||||
|
||||
void ComponentAnimator::timerCallback()
|
||||
{
|
||||
auto timeNow = Time::getMillisecondCounter();
|
||||
|
||||
if (lastTime == 0)
|
||||
lastTime = timeNow;
|
||||
|
||||
auto elapsed = (int) (timeNow - lastTime);
|
||||
|
||||
for (auto* task : Array<AnimationTask*> (tasks.begin(), tasks.size()))
|
||||
{
|
||||
if (tasks.contains (task) && ! task->useTimeslice (elapsed))
|
||||
{
|
||||
tasks.removeObject (task);
|
||||
sendChangeMessage();
|
||||
}
|
||||
}
|
||||
|
||||
lastTime = timeNow;
|
||||
|
||||
if (tasks.size() == 0)
|
||||
stopTimer();
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
|
@ -1,162 +1,162 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Animates a set of components, moving them to a new position and/or fading their
|
||||
alpha levels.
|
||||
|
||||
To animate a component, create a ComponentAnimator instance or (preferably) use the
|
||||
global animator object provided by Desktop::getAnimator(), and call its animateComponent()
|
||||
method to commence the movement.
|
||||
|
||||
If you're using your own ComponentAnimator instance, you'll need to make sure it isn't
|
||||
deleted before it finishes moving the components, or they'll be abandoned before reaching their
|
||||
destinations.
|
||||
|
||||
It's ok to delete components while they're being animated - the animator will detect this
|
||||
and safely stop using them.
|
||||
|
||||
The class is a ChangeBroadcaster and sends a notification when any components
|
||||
start or finish being animated.
|
||||
|
||||
@see Desktop::getAnimator
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API ComponentAnimator : public ChangeBroadcaster,
|
||||
private Timer
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates a ComponentAnimator. */
|
||||
ComponentAnimator();
|
||||
|
||||
/** Destructor. */
|
||||
~ComponentAnimator() override;
|
||||
|
||||
//==============================================================================
|
||||
/** Starts a component moving from its current position to a specified position.
|
||||
|
||||
If the component is already in the middle of an animation, that will be abandoned,
|
||||
and a new animation will begin, moving the component from its current location.
|
||||
|
||||
The start and end speed parameters let you apply some acceleration to the component's
|
||||
movement.
|
||||
|
||||
@param component the component to move
|
||||
@param finalBounds the destination bounds to which the component should move. To leave the
|
||||
component in the same place, just pass component->getBounds() for this value
|
||||
@param finalAlpha the alpha value that the component should have at the end of the animation
|
||||
@param animationDurationMilliseconds how long the animation should last, in milliseconds
|
||||
@param useProxyComponent if true, this means the component should be replaced by an internally
|
||||
managed temporary component which is a snapshot of the original component.
|
||||
This avoids the component having to paint itself as it moves, so may
|
||||
be more efficient. This option also allows you to delete the original
|
||||
component immediately after starting the animation, because the animation
|
||||
can proceed without it. If you use a proxy, the original component will be
|
||||
made invisible by this call, and then will become visible again at the end
|
||||
of the animation. It'll also mean that the proxy component will be temporarily
|
||||
added to the component's parent, so avoid it if this might confuse the parent
|
||||
component, or if there's a chance the parent might decide to delete its children.
|
||||
@param startSpeed a value to indicate the relative start speed of the animation. If this is 0,
|
||||
the component will start by accelerating from rest; higher values mean that it
|
||||
will have an initial speed greater than zero. If the value is greater than 1, it
|
||||
will decelerate towards the middle of its journey. To move the component at a
|
||||
constant rate for its entire animation, set both the start and end speeds to 1.0
|
||||
@param endSpeed a relative speed at which the component should be moving when the animation finishes.
|
||||
If this is 0, the component will decelerate to a standstill at its final position;
|
||||
higher values mean the component will still be moving when it stops. To move the component
|
||||
at a constant rate for its entire animation, set both the start and end speeds to 1.0
|
||||
*/
|
||||
void animateComponent (Component* component,
|
||||
const Rectangle<int>& finalBounds,
|
||||
float finalAlpha,
|
||||
int animationDurationMilliseconds,
|
||||
bool useProxyComponent,
|
||||
double startSpeed,
|
||||
double endSpeed);
|
||||
|
||||
/** Begins a fade-out of this components alpha level.
|
||||
This is a quick way of invoking animateComponent() with a target alpha value of 0.0f, using
|
||||
a proxy. You're safe to delete the component after calling this method, and this won't
|
||||
interfere with the animation's progress.
|
||||
*/
|
||||
void fadeOut (Component* component, int millisecondsToTake);
|
||||
|
||||
/** Begins a fade-in of a component.
|
||||
This is a quick way of invoking animateComponent() with a target alpha value of 1.0f.
|
||||
*/
|
||||
void fadeIn (Component* component, int millisecondsToTake);
|
||||
|
||||
/** Stops a component if it's currently being animated.
|
||||
|
||||
If moveComponentToItsFinalPosition is true, then the component will
|
||||
be immediately moved to its destination position and size. If false, it will be
|
||||
left in whatever location it currently occupies.
|
||||
*/
|
||||
void cancelAnimation (Component* component,
|
||||
bool moveComponentToItsFinalPosition);
|
||||
|
||||
/** Clears all of the active animations.
|
||||
|
||||
If moveComponentsToTheirFinalPositions is true, all the components will
|
||||
be immediately set to their final positions. If false, they will be
|
||||
left in whatever locations they currently occupy.
|
||||
*/
|
||||
void cancelAllAnimations (bool moveComponentsToTheirFinalPositions);
|
||||
|
||||
/** Returns the destination position for a component.
|
||||
|
||||
If the component is being animated, this will return the target position that
|
||||
was specified when animateComponent() was called.
|
||||
|
||||
If the specified component isn't currently being animated, this method will just
|
||||
return its current position.
|
||||
*/
|
||||
Rectangle<int> getComponentDestination (Component* component);
|
||||
|
||||
/** Returns true if the specified component is currently being animated. */
|
||||
bool isAnimating (Component* component) const noexcept;
|
||||
|
||||
/** Returns true if any components are currently being animated. */
|
||||
bool isAnimating() const noexcept;
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
class AnimationTask;
|
||||
OwnedArray<AnimationTask> tasks;
|
||||
uint32 lastTime;
|
||||
|
||||
AnimationTask* findTaskFor (Component*) const noexcept;
|
||||
void timerCallback() override;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ComponentAnimator)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Animates a set of components, moving them to a new position and/or fading their
|
||||
alpha levels.
|
||||
|
||||
To animate a component, create a ComponentAnimator instance or (preferably) use the
|
||||
global animator object provided by Desktop::getAnimator(), and call its animateComponent()
|
||||
method to commence the movement.
|
||||
|
||||
If you're using your own ComponentAnimator instance, you'll need to make sure it isn't
|
||||
deleted before it finishes moving the components, or they'll be abandoned before reaching their
|
||||
destinations.
|
||||
|
||||
It's ok to delete components while they're being animated - the animator will detect this
|
||||
and safely stop using them.
|
||||
|
||||
The class is a ChangeBroadcaster and sends a notification when any components
|
||||
start or finish being animated.
|
||||
|
||||
@see Desktop::getAnimator
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API ComponentAnimator : public ChangeBroadcaster,
|
||||
private Timer
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates a ComponentAnimator. */
|
||||
ComponentAnimator();
|
||||
|
||||
/** Destructor. */
|
||||
~ComponentAnimator() override;
|
||||
|
||||
//==============================================================================
|
||||
/** Starts a component moving from its current position to a specified position.
|
||||
|
||||
If the component is already in the middle of an animation, that will be abandoned,
|
||||
and a new animation will begin, moving the component from its current location.
|
||||
|
||||
The start and end speed parameters let you apply some acceleration to the component's
|
||||
movement.
|
||||
|
||||
@param component the component to move
|
||||
@param finalBounds the destination bounds to which the component should move. To leave the
|
||||
component in the same place, just pass component->getBounds() for this value
|
||||
@param finalAlpha the alpha value that the component should have at the end of the animation
|
||||
@param animationDurationMilliseconds how long the animation should last, in milliseconds
|
||||
@param useProxyComponent if true, this means the component should be replaced by an internally
|
||||
managed temporary component which is a snapshot of the original component.
|
||||
This avoids the component having to paint itself as it moves, so may
|
||||
be more efficient. This option also allows you to delete the original
|
||||
component immediately after starting the animation, because the animation
|
||||
can proceed without it. If you use a proxy, the original component will be
|
||||
made invisible by this call, and then will become visible again at the end
|
||||
of the animation. It'll also mean that the proxy component will be temporarily
|
||||
added to the component's parent, so avoid it if this might confuse the parent
|
||||
component, or if there's a chance the parent might decide to delete its children.
|
||||
@param startSpeed a value to indicate the relative start speed of the animation. If this is 0,
|
||||
the component will start by accelerating from rest; higher values mean that it
|
||||
will have an initial speed greater than zero. If the value is greater than 1, it
|
||||
will decelerate towards the middle of its journey. To move the component at a
|
||||
constant rate for its entire animation, set both the start and end speeds to 1.0
|
||||
@param endSpeed a relative speed at which the component should be moving when the animation finishes.
|
||||
If this is 0, the component will decelerate to a standstill at its final position;
|
||||
higher values mean the component will still be moving when it stops. To move the component
|
||||
at a constant rate for its entire animation, set both the start and end speeds to 1.0
|
||||
*/
|
||||
void animateComponent (Component* component,
|
||||
const Rectangle<int>& finalBounds,
|
||||
float finalAlpha,
|
||||
int animationDurationMilliseconds,
|
||||
bool useProxyComponent,
|
||||
double startSpeed,
|
||||
double endSpeed);
|
||||
|
||||
/** Begins a fade-out of this components alpha level.
|
||||
This is a quick way of invoking animateComponent() with a target alpha value of 0.0f, using
|
||||
a proxy. You're safe to delete the component after calling this method, and this won't
|
||||
interfere with the animation's progress.
|
||||
*/
|
||||
void fadeOut (Component* component, int millisecondsToTake);
|
||||
|
||||
/** Begins a fade-in of a component.
|
||||
This is a quick way of invoking animateComponent() with a target alpha value of 1.0f.
|
||||
*/
|
||||
void fadeIn (Component* component, int millisecondsToTake);
|
||||
|
||||
/** Stops a component if it's currently being animated.
|
||||
|
||||
If moveComponentToItsFinalPosition is true, then the component will
|
||||
be immediately moved to its destination position and size. If false, it will be
|
||||
left in whatever location it currently occupies.
|
||||
*/
|
||||
void cancelAnimation (Component* component,
|
||||
bool moveComponentToItsFinalPosition);
|
||||
|
||||
/** Clears all of the active animations.
|
||||
|
||||
If moveComponentsToTheirFinalPositions is true, all the components will
|
||||
be immediately set to their final positions. If false, they will be
|
||||
left in whatever locations they currently occupy.
|
||||
*/
|
||||
void cancelAllAnimations (bool moveComponentsToTheirFinalPositions);
|
||||
|
||||
/** Returns the destination position for a component.
|
||||
|
||||
If the component is being animated, this will return the target position that
|
||||
was specified when animateComponent() was called.
|
||||
|
||||
If the specified component isn't currently being animated, this method will just
|
||||
return its current position.
|
||||
*/
|
||||
Rectangle<int> getComponentDestination (Component* component);
|
||||
|
||||
/** Returns true if the specified component is currently being animated. */
|
||||
bool isAnimating (Component* component) const noexcept;
|
||||
|
||||
/** Returns true if any components are currently being animated. */
|
||||
bool isAnimating() const noexcept;
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
class AnimationTask;
|
||||
OwnedArray<AnimationTask> tasks;
|
||||
uint32 lastTime;
|
||||
|
||||
AnimationTask* findTaskFor (Component*) const noexcept;
|
||||
void timerCallback() override;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ComponentAnimator)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
|
@ -1,296 +1,305 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
ComponentBoundsConstrainer::ComponentBoundsConstrainer() noexcept {}
|
||||
ComponentBoundsConstrainer::~ComponentBoundsConstrainer() {}
|
||||
|
||||
//==============================================================================
|
||||
void ComponentBoundsConstrainer::setMinimumWidth (int minimumWidth) noexcept { minW = minimumWidth; }
|
||||
void ComponentBoundsConstrainer::setMaximumWidth (int maximumWidth) noexcept { maxW = maximumWidth; }
|
||||
void ComponentBoundsConstrainer::setMinimumHeight (int minimumHeight) noexcept { minH = minimumHeight; }
|
||||
void ComponentBoundsConstrainer::setMaximumHeight (int maximumHeight) noexcept { maxH = maximumHeight; }
|
||||
|
||||
void ComponentBoundsConstrainer::setMinimumSize (int minimumWidth, int minimumHeight) noexcept
|
||||
{
|
||||
jassert (maxW >= minimumWidth);
|
||||
jassert (maxH >= minimumHeight);
|
||||
jassert (minimumWidth > 0 && minimumHeight > 0);
|
||||
|
||||
minW = minimumWidth;
|
||||
minH = minimumHeight;
|
||||
|
||||
if (minW > maxW) maxW = minW;
|
||||
if (minH > maxH) maxH = minH;
|
||||
}
|
||||
|
||||
void ComponentBoundsConstrainer::setMaximumSize (int maximumWidth, int maximumHeight) noexcept
|
||||
{
|
||||
jassert (maximumWidth >= minW);
|
||||
jassert (maximumHeight >= minH);
|
||||
jassert (maximumWidth > 0 && maximumHeight > 0);
|
||||
|
||||
maxW = jmax (minW, maximumWidth);
|
||||
maxH = jmax (minH, maximumHeight);
|
||||
}
|
||||
|
||||
void ComponentBoundsConstrainer::setSizeLimits (int minimumWidth,
|
||||
int minimumHeight,
|
||||
int maximumWidth,
|
||||
int maximumHeight) noexcept
|
||||
{
|
||||
jassert (maximumWidth >= minimumWidth);
|
||||
jassert (maximumHeight >= minimumHeight);
|
||||
jassert (maximumWidth > 0 && maximumHeight > 0);
|
||||
jassert (minimumWidth > 0 && minimumHeight > 0);
|
||||
|
||||
minW = jmax (0, minimumWidth);
|
||||
minH = jmax (0, minimumHeight);
|
||||
maxW = jmax (minW, maximumWidth);
|
||||
maxH = jmax (minH, maximumHeight);
|
||||
}
|
||||
|
||||
void ComponentBoundsConstrainer::setMinimumOnscreenAmounts (int minimumWhenOffTheTop,
|
||||
int minimumWhenOffTheLeft,
|
||||
int minimumWhenOffTheBottom,
|
||||
int minimumWhenOffTheRight) noexcept
|
||||
{
|
||||
minOffTop = minimumWhenOffTheTop;
|
||||
minOffLeft = minimumWhenOffTheLeft;
|
||||
minOffBottom = minimumWhenOffTheBottom;
|
||||
minOffRight = minimumWhenOffTheRight;
|
||||
}
|
||||
|
||||
void ComponentBoundsConstrainer::setFixedAspectRatio (double widthOverHeight) noexcept
|
||||
{
|
||||
aspectRatio = jmax (0.0, widthOverHeight);
|
||||
}
|
||||
|
||||
double ComponentBoundsConstrainer::getFixedAspectRatio() const noexcept
|
||||
{
|
||||
return aspectRatio;
|
||||
}
|
||||
|
||||
void ComponentBoundsConstrainer::setBoundsForComponent (Component* component,
|
||||
Rectangle<int> targetBounds,
|
||||
bool isStretchingTop,
|
||||
bool isStretchingLeft,
|
||||
bool isStretchingBottom,
|
||||
bool isStretchingRight)
|
||||
{
|
||||
jassert (component != nullptr);
|
||||
|
||||
Rectangle<int> limits, bounds (targetBounds);
|
||||
BorderSize<int> border;
|
||||
|
||||
if (auto* parent = component->getParentComponent())
|
||||
{
|
||||
limits.setSize (parent->getWidth(), parent->getHeight());
|
||||
}
|
||||
else
|
||||
{
|
||||
if (auto* peer = component->getPeer())
|
||||
border = peer->getFrameSize();
|
||||
|
||||
auto screenBounds = Desktop::getInstance().getDisplays().getDisplayForPoint (targetBounds.getCentre())->userArea;
|
||||
|
||||
limits = component->getLocalArea (nullptr, screenBounds) + component->getPosition();
|
||||
}
|
||||
|
||||
border.addTo (bounds);
|
||||
|
||||
checkBounds (bounds,
|
||||
border.addedTo (component->getBounds()), limits,
|
||||
isStretchingTop, isStretchingLeft,
|
||||
isStretchingBottom, isStretchingRight);
|
||||
|
||||
border.subtractFrom (bounds);
|
||||
|
||||
applyBoundsToComponent (*component, bounds);
|
||||
}
|
||||
|
||||
void ComponentBoundsConstrainer::checkComponentBounds (Component* component)
|
||||
{
|
||||
setBoundsForComponent (component, component->getBounds(),
|
||||
false, false, false, false);
|
||||
}
|
||||
|
||||
void ComponentBoundsConstrainer::applyBoundsToComponent (Component& component, Rectangle<int> bounds)
|
||||
{
|
||||
if (auto* positioner = component.getPositioner())
|
||||
positioner->applyNewBounds (bounds);
|
||||
else
|
||||
component.setBounds (bounds);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void ComponentBoundsConstrainer::resizeStart()
|
||||
{
|
||||
}
|
||||
|
||||
void ComponentBoundsConstrainer::resizeEnd()
|
||||
{
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void ComponentBoundsConstrainer::checkBounds (Rectangle<int>& bounds,
|
||||
const Rectangle<int>& old,
|
||||
const Rectangle<int>& limits,
|
||||
bool isStretchingTop,
|
||||
bool isStretchingLeft,
|
||||
bool isStretchingBottom,
|
||||
bool isStretchingRight)
|
||||
{
|
||||
if (isStretchingLeft)
|
||||
bounds.setLeft (jlimit (old.getRight() - maxW, old.getRight() - minW, bounds.getX()));
|
||||
else
|
||||
bounds.setWidth (jlimit (minW, maxW, bounds.getWidth()));
|
||||
|
||||
if (isStretchingTop)
|
||||
bounds.setTop (jlimit (old.getBottom() - maxH, old.getBottom() - minH, bounds.getY()));
|
||||
else
|
||||
bounds.setHeight (jlimit (minH, maxH, bounds.getHeight()));
|
||||
|
||||
if (bounds.isEmpty())
|
||||
return;
|
||||
|
||||
if (minOffTop > 0)
|
||||
{
|
||||
const int limit = limits.getY() + jmin (minOffTop - bounds.getHeight(), 0);
|
||||
|
||||
if (bounds.getY() < limit)
|
||||
{
|
||||
if (isStretchingTop)
|
||||
bounds.setTop (limits.getY());
|
||||
else
|
||||
bounds.setY (limit);
|
||||
}
|
||||
}
|
||||
|
||||
if (minOffLeft > 0)
|
||||
{
|
||||
const int limit = limits.getX() + jmin (minOffLeft - bounds.getWidth(), 0);
|
||||
|
||||
if (bounds.getX() < limit)
|
||||
{
|
||||
if (isStretchingLeft)
|
||||
bounds.setLeft (limits.getX());
|
||||
else
|
||||
bounds.setX (limit);
|
||||
}
|
||||
}
|
||||
|
||||
if (minOffBottom > 0)
|
||||
{
|
||||
const int limit = limits.getBottom() - jmin (minOffBottom, bounds.getHeight());
|
||||
|
||||
if (bounds.getY() > limit)
|
||||
{
|
||||
if (isStretchingBottom)
|
||||
bounds.setBottom (limits.getBottom());
|
||||
else
|
||||
bounds.setY (limit);
|
||||
}
|
||||
}
|
||||
|
||||
if (minOffRight > 0)
|
||||
{
|
||||
const int limit = limits.getRight() - jmin (minOffRight, bounds.getWidth());
|
||||
|
||||
if (bounds.getX() > limit)
|
||||
{
|
||||
if (isStretchingRight)
|
||||
bounds.setRight (limits.getRight());
|
||||
else
|
||||
bounds.setX (limit);
|
||||
}
|
||||
}
|
||||
|
||||
// constrain the aspect ratio if one has been specified..
|
||||
if (aspectRatio > 0.0)
|
||||
{
|
||||
bool adjustWidth;
|
||||
|
||||
if ((isStretchingTop || isStretchingBottom) && ! (isStretchingLeft || isStretchingRight))
|
||||
{
|
||||
adjustWidth = true;
|
||||
}
|
||||
else if ((isStretchingLeft || isStretchingRight) && ! (isStretchingTop || isStretchingBottom))
|
||||
{
|
||||
adjustWidth = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
const double oldRatio = (old.getHeight() > 0) ? std::abs (old.getWidth() / (double) old.getHeight()) : 0.0;
|
||||
const double newRatio = std::abs (bounds.getWidth() / (double) bounds.getHeight());
|
||||
|
||||
adjustWidth = (oldRatio > newRatio);
|
||||
}
|
||||
|
||||
if (adjustWidth)
|
||||
{
|
||||
bounds.setWidth (roundToInt (bounds.getHeight() * aspectRatio));
|
||||
|
||||
if (bounds.getWidth() > maxW || bounds.getWidth() < minW)
|
||||
{
|
||||
bounds.setWidth (jlimit (minW, maxW, bounds.getWidth()));
|
||||
bounds.setHeight (roundToInt (bounds.getWidth() / aspectRatio));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
bounds.setHeight (roundToInt (bounds.getWidth() / aspectRatio));
|
||||
|
||||
if (bounds.getHeight() > maxH || bounds.getHeight() < minH)
|
||||
{
|
||||
bounds.setHeight (jlimit (minH, maxH, bounds.getHeight()));
|
||||
bounds.setWidth (roundToInt (bounds.getHeight() * aspectRatio));
|
||||
}
|
||||
}
|
||||
|
||||
if ((isStretchingTop || isStretchingBottom) && ! (isStretchingLeft || isStretchingRight))
|
||||
{
|
||||
bounds.setX (old.getX() + (old.getWidth() - bounds.getWidth()) / 2);
|
||||
}
|
||||
else if ((isStretchingLeft || isStretchingRight) && ! (isStretchingTop || isStretchingBottom))
|
||||
{
|
||||
bounds.setY (old.getY() + (old.getHeight() - bounds.getHeight()) / 2);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (isStretchingLeft)
|
||||
bounds.setX (old.getRight() - bounds.getWidth());
|
||||
|
||||
if (isStretchingTop)
|
||||
bounds.setY (old.getBottom() - bounds.getHeight());
|
||||
}
|
||||
}
|
||||
|
||||
jassert (! bounds.isEmpty());
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
ComponentBoundsConstrainer::ComponentBoundsConstrainer() noexcept {}
|
||||
ComponentBoundsConstrainer::~ComponentBoundsConstrainer() {}
|
||||
|
||||
//==============================================================================
|
||||
void ComponentBoundsConstrainer::setMinimumWidth (int minimumWidth) noexcept { minW = minimumWidth; }
|
||||
void ComponentBoundsConstrainer::setMaximumWidth (int maximumWidth) noexcept { maxW = maximumWidth; }
|
||||
void ComponentBoundsConstrainer::setMinimumHeight (int minimumHeight) noexcept { minH = minimumHeight; }
|
||||
void ComponentBoundsConstrainer::setMaximumHeight (int maximumHeight) noexcept { maxH = maximumHeight; }
|
||||
|
||||
void ComponentBoundsConstrainer::setMinimumSize (int minimumWidth, int minimumHeight) noexcept
|
||||
{
|
||||
jassert (maxW >= minimumWidth);
|
||||
jassert (maxH >= minimumHeight);
|
||||
jassert (minimumWidth > 0 && minimumHeight > 0);
|
||||
|
||||
minW = minimumWidth;
|
||||
minH = minimumHeight;
|
||||
|
||||
if (minW > maxW) maxW = minW;
|
||||
if (minH > maxH) maxH = minH;
|
||||
}
|
||||
|
||||
void ComponentBoundsConstrainer::setMaximumSize (int maximumWidth, int maximumHeight) noexcept
|
||||
{
|
||||
jassert (maximumWidth >= minW);
|
||||
jassert (maximumHeight >= minH);
|
||||
jassert (maximumWidth > 0 && maximumHeight > 0);
|
||||
|
||||
maxW = jmax (minW, maximumWidth);
|
||||
maxH = jmax (minH, maximumHeight);
|
||||
}
|
||||
|
||||
void ComponentBoundsConstrainer::setSizeLimits (int minimumWidth,
|
||||
int minimumHeight,
|
||||
int maximumWidth,
|
||||
int maximumHeight) noexcept
|
||||
{
|
||||
jassert (maximumWidth >= minimumWidth);
|
||||
jassert (maximumHeight >= minimumHeight);
|
||||
jassert (maximumWidth > 0 && maximumHeight > 0);
|
||||
jassert (minimumWidth > 0 && minimumHeight > 0);
|
||||
|
||||
minW = jmax (0, minimumWidth);
|
||||
minH = jmax (0, minimumHeight);
|
||||
maxW = jmax (minW, maximumWidth);
|
||||
maxH = jmax (minH, maximumHeight);
|
||||
}
|
||||
|
||||
void ComponentBoundsConstrainer::setMinimumOnscreenAmounts (int minimumWhenOffTheTop,
|
||||
int minimumWhenOffTheLeft,
|
||||
int minimumWhenOffTheBottom,
|
||||
int minimumWhenOffTheRight) noexcept
|
||||
{
|
||||
minOffTop = minimumWhenOffTheTop;
|
||||
minOffLeft = minimumWhenOffTheLeft;
|
||||
minOffBottom = minimumWhenOffTheBottom;
|
||||
minOffRight = minimumWhenOffTheRight;
|
||||
}
|
||||
|
||||
void ComponentBoundsConstrainer::setFixedAspectRatio (double widthOverHeight) noexcept
|
||||
{
|
||||
aspectRatio = jmax (0.0, widthOverHeight);
|
||||
}
|
||||
|
||||
double ComponentBoundsConstrainer::getFixedAspectRatio() const noexcept
|
||||
{
|
||||
return aspectRatio;
|
||||
}
|
||||
|
||||
void ComponentBoundsConstrainer::setBoundsForComponent (Component* component,
|
||||
Rectangle<int> targetBounds,
|
||||
bool isStretchingTop,
|
||||
bool isStretchingLeft,
|
||||
bool isStretchingBottom,
|
||||
bool isStretchingRight)
|
||||
{
|
||||
jassert (component != nullptr);
|
||||
|
||||
auto bounds = targetBounds;
|
||||
|
||||
auto limits = [&]() -> Rectangle<int>
|
||||
{
|
||||
if (auto* parent = component->getParentComponent())
|
||||
return { parent->getWidth(), parent->getHeight() };
|
||||
|
||||
const auto globalBounds = component->localAreaToGlobal (targetBounds - component->getPosition());
|
||||
|
||||
if (auto* display = Desktop::getInstance().getDisplays().getDisplayForPoint (globalBounds.getCentre()))
|
||||
return component->getLocalArea (nullptr, display->userArea) + component->getPosition();
|
||||
|
||||
const auto max = std::numeric_limits<int>::max();
|
||||
return { max, max };
|
||||
}();
|
||||
|
||||
auto border = [&]() -> BorderSize<int>
|
||||
{
|
||||
if (component->getParentComponent() == nullptr)
|
||||
if (auto* peer = component->getPeer())
|
||||
if (const auto frameSize = peer->getFrameSizeIfPresent())
|
||||
return *frameSize;
|
||||
|
||||
return {};
|
||||
}();
|
||||
|
||||
border.addTo (bounds);
|
||||
|
||||
checkBounds (bounds,
|
||||
border.addedTo (component->getBounds()), limits,
|
||||
isStretchingTop, isStretchingLeft,
|
||||
isStretchingBottom, isStretchingRight);
|
||||
|
||||
border.subtractFrom (bounds);
|
||||
|
||||
applyBoundsToComponent (*component, bounds);
|
||||
}
|
||||
|
||||
void ComponentBoundsConstrainer::checkComponentBounds (Component* component)
|
||||
{
|
||||
setBoundsForComponent (component, component->getBounds(),
|
||||
false, false, false, false);
|
||||
}
|
||||
|
||||
void ComponentBoundsConstrainer::applyBoundsToComponent (Component& component, Rectangle<int> bounds)
|
||||
{
|
||||
if (auto* positioner = component.getPositioner())
|
||||
positioner->applyNewBounds (bounds);
|
||||
else
|
||||
component.setBounds (bounds);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void ComponentBoundsConstrainer::resizeStart()
|
||||
{
|
||||
}
|
||||
|
||||
void ComponentBoundsConstrainer::resizeEnd()
|
||||
{
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void ComponentBoundsConstrainer::checkBounds (Rectangle<int>& bounds,
|
||||
const Rectangle<int>& old,
|
||||
const Rectangle<int>& limits,
|
||||
bool isStretchingTop,
|
||||
bool isStretchingLeft,
|
||||
bool isStretchingBottom,
|
||||
bool isStretchingRight)
|
||||
{
|
||||
if (isStretchingLeft)
|
||||
bounds.setLeft (jlimit (old.getRight() - maxW, old.getRight() - minW, bounds.getX()));
|
||||
else
|
||||
bounds.setWidth (jlimit (minW, maxW, bounds.getWidth()));
|
||||
|
||||
if (isStretchingTop)
|
||||
bounds.setTop (jlimit (old.getBottom() - maxH, old.getBottom() - minH, bounds.getY()));
|
||||
else
|
||||
bounds.setHeight (jlimit (minH, maxH, bounds.getHeight()));
|
||||
|
||||
if (bounds.isEmpty())
|
||||
return;
|
||||
|
||||
if (minOffTop > 0)
|
||||
{
|
||||
const int limit = limits.getY() + jmin (minOffTop - bounds.getHeight(), 0);
|
||||
|
||||
if (bounds.getY() < limit)
|
||||
{
|
||||
if (isStretchingTop)
|
||||
bounds.setTop (limits.getY());
|
||||
else
|
||||
bounds.setY (limit);
|
||||
}
|
||||
}
|
||||
|
||||
if (minOffLeft > 0)
|
||||
{
|
||||
const int limit = limits.getX() + jmin (minOffLeft - bounds.getWidth(), 0);
|
||||
|
||||
if (bounds.getX() < limit)
|
||||
{
|
||||
if (isStretchingLeft)
|
||||
bounds.setLeft (limits.getX());
|
||||
else
|
||||
bounds.setX (limit);
|
||||
}
|
||||
}
|
||||
|
||||
if (minOffBottom > 0)
|
||||
{
|
||||
const int limit = limits.getBottom() - jmin (minOffBottom, bounds.getHeight());
|
||||
|
||||
if (bounds.getY() > limit)
|
||||
{
|
||||
if (isStretchingBottom)
|
||||
bounds.setBottom (limits.getBottom());
|
||||
else
|
||||
bounds.setY (limit);
|
||||
}
|
||||
}
|
||||
|
||||
if (minOffRight > 0)
|
||||
{
|
||||
const int limit = limits.getRight() - jmin (minOffRight, bounds.getWidth());
|
||||
|
||||
if (bounds.getX() > limit)
|
||||
{
|
||||
if (isStretchingRight)
|
||||
bounds.setRight (limits.getRight());
|
||||
else
|
||||
bounds.setX (limit);
|
||||
}
|
||||
}
|
||||
|
||||
// constrain the aspect ratio if one has been specified..
|
||||
if (aspectRatio > 0.0)
|
||||
{
|
||||
bool adjustWidth;
|
||||
|
||||
if ((isStretchingTop || isStretchingBottom) && ! (isStretchingLeft || isStretchingRight))
|
||||
{
|
||||
adjustWidth = true;
|
||||
}
|
||||
else if ((isStretchingLeft || isStretchingRight) && ! (isStretchingTop || isStretchingBottom))
|
||||
{
|
||||
adjustWidth = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
const double oldRatio = (old.getHeight() > 0) ? std::abs (old.getWidth() / (double) old.getHeight()) : 0.0;
|
||||
const double newRatio = std::abs (bounds.getWidth() / (double) bounds.getHeight());
|
||||
|
||||
adjustWidth = (oldRatio > newRatio);
|
||||
}
|
||||
|
||||
if (adjustWidth)
|
||||
{
|
||||
bounds.setWidth (roundToInt (bounds.getHeight() * aspectRatio));
|
||||
|
||||
if (bounds.getWidth() > maxW || bounds.getWidth() < minW)
|
||||
{
|
||||
bounds.setWidth (jlimit (minW, maxW, bounds.getWidth()));
|
||||
bounds.setHeight (roundToInt (bounds.getWidth() / aspectRatio));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
bounds.setHeight (roundToInt (bounds.getWidth() / aspectRatio));
|
||||
|
||||
if (bounds.getHeight() > maxH || bounds.getHeight() < minH)
|
||||
{
|
||||
bounds.setHeight (jlimit (minH, maxH, bounds.getHeight()));
|
||||
bounds.setWidth (roundToInt (bounds.getHeight() * aspectRatio));
|
||||
}
|
||||
}
|
||||
|
||||
if ((isStretchingTop || isStretchingBottom) && ! (isStretchingLeft || isStretchingRight))
|
||||
{
|
||||
bounds.setX (old.getX() + (old.getWidth() - bounds.getWidth()) / 2);
|
||||
}
|
||||
else if ((isStretchingLeft || isStretchingRight) && ! (isStretchingTop || isStretchingBottom))
|
||||
{
|
||||
bounds.setY (old.getY() + (old.getHeight() - bounds.getHeight()) / 2);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (isStretchingLeft)
|
||||
bounds.setX (old.getRight() - bounds.getWidth());
|
||||
|
||||
if (isStretchingTop)
|
||||
bounds.setY (old.getBottom() - bounds.getHeight());
|
||||
}
|
||||
}
|
||||
|
||||
jassert (! bounds.isEmpty());
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
|
@ -1,197 +1,197 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A class that imposes restrictions on a Component's size or position.
|
||||
|
||||
This is used by classes such as ResizableCornerComponent,
|
||||
ResizableBorderComponent and ResizableWindow.
|
||||
|
||||
The base class can impose some basic size and position limits, but you can
|
||||
also subclass this for custom uses.
|
||||
|
||||
@see ResizableCornerComponent, ResizableBorderComponent, ResizableWindow
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API ComponentBoundsConstrainer
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** When first created, the object will not impose any restrictions on the components. */
|
||||
ComponentBoundsConstrainer() noexcept;
|
||||
|
||||
/** Destructor. */
|
||||
virtual ~ComponentBoundsConstrainer();
|
||||
|
||||
//==============================================================================
|
||||
/** Imposes a minimum width limit. */
|
||||
void setMinimumWidth (int minimumWidth) noexcept;
|
||||
|
||||
/** Returns the current minimum width. */
|
||||
int getMinimumWidth() const noexcept { return minW; }
|
||||
|
||||
/** Imposes a maximum width limit. */
|
||||
void setMaximumWidth (int maximumWidth) noexcept;
|
||||
|
||||
/** Returns the current maximum width. */
|
||||
int getMaximumWidth() const noexcept { return maxW; }
|
||||
|
||||
/** Imposes a minimum height limit. */
|
||||
void setMinimumHeight (int minimumHeight) noexcept;
|
||||
|
||||
/** Returns the current minimum height. */
|
||||
int getMinimumHeight() const noexcept { return minH; }
|
||||
|
||||
/** Imposes a maximum height limit. */
|
||||
void setMaximumHeight (int maximumHeight) noexcept;
|
||||
|
||||
/** Returns the current maximum height. */
|
||||
int getMaximumHeight() const noexcept { return maxH; }
|
||||
|
||||
/** Imposes a minimum width and height limit. */
|
||||
void setMinimumSize (int minimumWidth,
|
||||
int minimumHeight) noexcept;
|
||||
|
||||
/** Imposes a maximum width and height limit. */
|
||||
void setMaximumSize (int maximumWidth,
|
||||
int maximumHeight) noexcept;
|
||||
|
||||
/** Set all the maximum and minimum dimensions. */
|
||||
void setSizeLimits (int minimumWidth,
|
||||
int minimumHeight,
|
||||
int maximumWidth,
|
||||
int maximumHeight) noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** Sets the amount by which the component is allowed to go off-screen.
|
||||
|
||||
The values indicate how many pixels must remain on-screen when dragged off
|
||||
one of its parent's edges, so e.g. if minimumWhenOffTheTop is set to 10, then
|
||||
when the component goes off the top of the screen, its y-position will be
|
||||
clipped so that there are always at least 10 pixels on-screen. In other words,
|
||||
the lowest y-position it can take would be (10 - the component's height).
|
||||
|
||||
If you pass 0 or less for one of these amounts, the component is allowed
|
||||
to move beyond that edge completely, with no restrictions at all.
|
||||
|
||||
If you pass a very large number (i.e. larger that the dimensions of the
|
||||
component itself), then the component won't be allowed to overlap that
|
||||
edge at all. So e.g. setting minimumWhenOffTheLeft to 0xffffff will mean that
|
||||
the component will bump into the left side of the screen and go no further.
|
||||
*/
|
||||
void setMinimumOnscreenAmounts (int minimumWhenOffTheTop,
|
||||
int minimumWhenOffTheLeft,
|
||||
int minimumWhenOffTheBottom,
|
||||
int minimumWhenOffTheRight) noexcept;
|
||||
|
||||
|
||||
/** Returns the minimum distance the bounds can be off-screen. @see setMinimumOnscreenAmounts */
|
||||
int getMinimumWhenOffTheTop() const noexcept { return minOffTop; }
|
||||
/** Returns the minimum distance the bounds can be off-screen. @see setMinimumOnscreenAmounts */
|
||||
int getMinimumWhenOffTheLeft() const noexcept { return minOffLeft; }
|
||||
/** Returns the minimum distance the bounds can be off-screen. @see setMinimumOnscreenAmounts */
|
||||
int getMinimumWhenOffTheBottom() const noexcept { return minOffBottom; }
|
||||
/** Returns the minimum distance the bounds can be off-screen. @see setMinimumOnscreenAmounts */
|
||||
int getMinimumWhenOffTheRight() const noexcept { return minOffRight; }
|
||||
|
||||
//==============================================================================
|
||||
/** Specifies a width-to-height ratio that the resizer should always maintain.
|
||||
|
||||
If the value is 0, no aspect ratio is enforced. If it's non-zero, the width
|
||||
will always be maintained as this multiple of the height.
|
||||
|
||||
@see setResizeLimits
|
||||
*/
|
||||
void setFixedAspectRatio (double widthOverHeight) noexcept;
|
||||
|
||||
/** Returns the aspect ratio that was set with setFixedAspectRatio().
|
||||
|
||||
If no aspect ratio is being enforced, this will return 0.
|
||||
*/
|
||||
double getFixedAspectRatio() const noexcept;
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/** This callback changes the given coordinates to impose whatever the current
|
||||
constraints are set to be.
|
||||
|
||||
@param bounds the target position that should be examined and adjusted
|
||||
@param previousBounds the component's current size
|
||||
@param limits the region in which the component can be positioned
|
||||
@param isStretchingTop whether the top edge of the component is being resized
|
||||
@param isStretchingLeft whether the left edge of the component is being resized
|
||||
@param isStretchingBottom whether the bottom edge of the component is being resized
|
||||
@param isStretchingRight whether the right edge of the component is being resized
|
||||
*/
|
||||
virtual void checkBounds (Rectangle<int>& bounds,
|
||||
const Rectangle<int>& previousBounds,
|
||||
const Rectangle<int>& limits,
|
||||
bool isStretchingTop,
|
||||
bool isStretchingLeft,
|
||||
bool isStretchingBottom,
|
||||
bool isStretchingRight);
|
||||
|
||||
/** This callback happens when the resizer is about to start dragging. */
|
||||
virtual void resizeStart();
|
||||
|
||||
/** This callback happens when the resizer has finished dragging. */
|
||||
virtual void resizeEnd();
|
||||
|
||||
/** Checks the given bounds, and then sets the component to the corrected size. */
|
||||
void setBoundsForComponent (Component* component,
|
||||
Rectangle<int> bounds,
|
||||
bool isStretchingTop,
|
||||
bool isStretchingLeft,
|
||||
bool isStretchingBottom,
|
||||
bool isStretchingRight);
|
||||
|
||||
/** Performs a check on the current size of a component, and moves or resizes
|
||||
it if it fails the constraints.
|
||||
*/
|
||||
void checkComponentBounds (Component* component);
|
||||
|
||||
/** Called by setBoundsForComponent() to apply a new constrained size to a
|
||||
component.
|
||||
|
||||
By default this just calls setBounds(), but is virtual in case it's needed for
|
||||
extremely cunning purposes.
|
||||
*/
|
||||
virtual void applyBoundsToComponent (Component&, Rectangle<int> bounds);
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
int minW = 0, maxW = 0x3fffffff, minH = 0, maxH = 0x3fffffff;
|
||||
int minOffTop = 0, minOffLeft = 0, minOffBottom = 0, minOffRight = 0;
|
||||
double aspectRatio = 0;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ComponentBoundsConstrainer)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A class that imposes restrictions on a Component's size or position.
|
||||
|
||||
This is used by classes such as ResizableCornerComponent,
|
||||
ResizableBorderComponent and ResizableWindow.
|
||||
|
||||
The base class can impose some basic size and position limits, but you can
|
||||
also subclass this for custom uses.
|
||||
|
||||
@see ResizableCornerComponent, ResizableBorderComponent, ResizableWindow
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API ComponentBoundsConstrainer
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** When first created, the object will not impose any restrictions on the components. */
|
||||
ComponentBoundsConstrainer() noexcept;
|
||||
|
||||
/** Destructor. */
|
||||
virtual ~ComponentBoundsConstrainer();
|
||||
|
||||
//==============================================================================
|
||||
/** Imposes a minimum width limit. */
|
||||
void setMinimumWidth (int minimumWidth) noexcept;
|
||||
|
||||
/** Returns the current minimum width. */
|
||||
int getMinimumWidth() const noexcept { return minW; }
|
||||
|
||||
/** Imposes a maximum width limit. */
|
||||
void setMaximumWidth (int maximumWidth) noexcept;
|
||||
|
||||
/** Returns the current maximum width. */
|
||||
int getMaximumWidth() const noexcept { return maxW; }
|
||||
|
||||
/** Imposes a minimum height limit. */
|
||||
void setMinimumHeight (int minimumHeight) noexcept;
|
||||
|
||||
/** Returns the current minimum height. */
|
||||
int getMinimumHeight() const noexcept { return minH; }
|
||||
|
||||
/** Imposes a maximum height limit. */
|
||||
void setMaximumHeight (int maximumHeight) noexcept;
|
||||
|
||||
/** Returns the current maximum height. */
|
||||
int getMaximumHeight() const noexcept { return maxH; }
|
||||
|
||||
/** Imposes a minimum width and height limit. */
|
||||
void setMinimumSize (int minimumWidth,
|
||||
int minimumHeight) noexcept;
|
||||
|
||||
/** Imposes a maximum width and height limit. */
|
||||
void setMaximumSize (int maximumWidth,
|
||||
int maximumHeight) noexcept;
|
||||
|
||||
/** Set all the maximum and minimum dimensions. */
|
||||
void setSizeLimits (int minimumWidth,
|
||||
int minimumHeight,
|
||||
int maximumWidth,
|
||||
int maximumHeight) noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** Sets the amount by which the component is allowed to go off-screen.
|
||||
|
||||
The values indicate how many pixels must remain on-screen when dragged off
|
||||
one of its parent's edges, so e.g. if minimumWhenOffTheTop is set to 10, then
|
||||
when the component goes off the top of the screen, its y-position will be
|
||||
clipped so that there are always at least 10 pixels on-screen. In other words,
|
||||
the lowest y-position it can take would be (10 - the component's height).
|
||||
|
||||
If you pass 0 or less for one of these amounts, the component is allowed
|
||||
to move beyond that edge completely, with no restrictions at all.
|
||||
|
||||
If you pass a very large number (i.e. larger that the dimensions of the
|
||||
component itself), then the component won't be allowed to overlap that
|
||||
edge at all. So e.g. setting minimumWhenOffTheLeft to 0xffffff will mean that
|
||||
the component will bump into the left side of the screen and go no further.
|
||||
*/
|
||||
void setMinimumOnscreenAmounts (int minimumWhenOffTheTop,
|
||||
int minimumWhenOffTheLeft,
|
||||
int minimumWhenOffTheBottom,
|
||||
int minimumWhenOffTheRight) noexcept;
|
||||
|
||||
|
||||
/** Returns the minimum distance the bounds can be off-screen. @see setMinimumOnscreenAmounts */
|
||||
int getMinimumWhenOffTheTop() const noexcept { return minOffTop; }
|
||||
/** Returns the minimum distance the bounds can be off-screen. @see setMinimumOnscreenAmounts */
|
||||
int getMinimumWhenOffTheLeft() const noexcept { return minOffLeft; }
|
||||
/** Returns the minimum distance the bounds can be off-screen. @see setMinimumOnscreenAmounts */
|
||||
int getMinimumWhenOffTheBottom() const noexcept { return minOffBottom; }
|
||||
/** Returns the minimum distance the bounds can be off-screen. @see setMinimumOnscreenAmounts */
|
||||
int getMinimumWhenOffTheRight() const noexcept { return minOffRight; }
|
||||
|
||||
//==============================================================================
|
||||
/** Specifies a width-to-height ratio that the resizer should always maintain.
|
||||
|
||||
If the value is 0, no aspect ratio is enforced. If it's non-zero, the width
|
||||
will always be maintained as this multiple of the height.
|
||||
|
||||
@see setResizeLimits
|
||||
*/
|
||||
void setFixedAspectRatio (double widthOverHeight) noexcept;
|
||||
|
||||
/** Returns the aspect ratio that was set with setFixedAspectRatio().
|
||||
|
||||
If no aspect ratio is being enforced, this will return 0.
|
||||
*/
|
||||
double getFixedAspectRatio() const noexcept;
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/** This callback changes the given coordinates to impose whatever the current
|
||||
constraints are set to be.
|
||||
|
||||
@param bounds the target position that should be examined and adjusted
|
||||
@param previousBounds the component's current size
|
||||
@param limits the region in which the component can be positioned
|
||||
@param isStretchingTop whether the top edge of the component is being resized
|
||||
@param isStretchingLeft whether the left edge of the component is being resized
|
||||
@param isStretchingBottom whether the bottom edge of the component is being resized
|
||||
@param isStretchingRight whether the right edge of the component is being resized
|
||||
*/
|
||||
virtual void checkBounds (Rectangle<int>& bounds,
|
||||
const Rectangle<int>& previousBounds,
|
||||
const Rectangle<int>& limits,
|
||||
bool isStretchingTop,
|
||||
bool isStretchingLeft,
|
||||
bool isStretchingBottom,
|
||||
bool isStretchingRight);
|
||||
|
||||
/** This callback happens when the resizer is about to start dragging. */
|
||||
virtual void resizeStart();
|
||||
|
||||
/** This callback happens when the resizer has finished dragging. */
|
||||
virtual void resizeEnd();
|
||||
|
||||
/** Checks the given bounds, and then sets the component to the corrected size. */
|
||||
void setBoundsForComponent (Component* component,
|
||||
Rectangle<int> bounds,
|
||||
bool isStretchingTop,
|
||||
bool isStretchingLeft,
|
||||
bool isStretchingBottom,
|
||||
bool isStretchingRight);
|
||||
|
||||
/** Performs a check on the current size of a component, and moves or resizes
|
||||
it if it fails the constraints.
|
||||
*/
|
||||
void checkComponentBounds (Component* component);
|
||||
|
||||
/** Called by setBoundsForComponent() to apply a new constrained size to a
|
||||
component.
|
||||
|
||||
By default this just calls setBounds(), but is virtual in case it's needed for
|
||||
extremely cunning purposes.
|
||||
*/
|
||||
virtual void applyBoundsToComponent (Component&, Rectangle<int> bounds);
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
int minW = 0, maxW = 0x3fffffff, minH = 0, maxH = 0x3fffffff;
|
||||
int minOffTop = 0, minOffLeft = 0, minOffBottom = 0, minOffRight = 0;
|
||||
double aspectRatio = 0;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ComponentBoundsConstrainer)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
|
@ -1,286 +1,286 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
namespace ComponentBuilderHelpers
|
||||
{
|
||||
static String getStateId (const ValueTree& state)
|
||||
{
|
||||
return state [ComponentBuilder::idProperty].toString();
|
||||
}
|
||||
|
||||
static Component* removeComponentWithID (OwnedArray<Component>& components, const String& compId)
|
||||
{
|
||||
jassert (compId.isNotEmpty());
|
||||
|
||||
for (int i = components.size(); --i >= 0;)
|
||||
{
|
||||
Component* const c = components.getUnchecked (i);
|
||||
|
||||
if (c->getComponentID() == compId)
|
||||
return components.removeAndReturn (i);
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static Component* findComponentWithID (Component& c, const String& compId)
|
||||
{
|
||||
jassert (compId.isNotEmpty());
|
||||
if (c.getComponentID() == compId)
|
||||
return &c;
|
||||
|
||||
for (auto* child : c.getChildren())
|
||||
if (auto* found = findComponentWithID (*child, compId))
|
||||
return found;
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static Component* createNewComponent (ComponentBuilder::TypeHandler& type,
|
||||
const ValueTree& state, Component* parent)
|
||||
{
|
||||
Component* const c = type.addNewComponentFromState (state, parent);
|
||||
jassert (c != nullptr && c->getParentComponent() == parent);
|
||||
c->setComponentID (getStateId (state));
|
||||
return c;
|
||||
}
|
||||
|
||||
static void updateComponent (ComponentBuilder& builder, const ValueTree& state)
|
||||
{
|
||||
if (Component* topLevelComp = builder.getManagedComponent())
|
||||
{
|
||||
ComponentBuilder::TypeHandler* const type = builder.getHandlerForState (state);
|
||||
const String uid (getStateId (state));
|
||||
|
||||
if (type == nullptr || uid.isEmpty())
|
||||
{
|
||||
// ..handle the case where a child of the actual state node has changed.
|
||||
if (state.getParent().isValid())
|
||||
updateComponent (builder, state.getParent());
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Component* const changedComp = findComponentWithID (*topLevelComp, uid))
|
||||
type->updateComponentFromState (changedComp, state);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
const Identifier ComponentBuilder::idProperty ("id");
|
||||
|
||||
ComponentBuilder::ComponentBuilder()
|
||||
: imageProvider (nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
ComponentBuilder::ComponentBuilder (const ValueTree& state_)
|
||||
: state (state_), imageProvider (nullptr)
|
||||
{
|
||||
state.addListener (this);
|
||||
}
|
||||
|
||||
ComponentBuilder::~ComponentBuilder()
|
||||
{
|
||||
state.removeListener (this);
|
||||
|
||||
#if JUCE_DEBUG
|
||||
// Don't delete the managed component!! The builder owns that component, and will delete
|
||||
// it automatically when it gets deleted.
|
||||
jassert (componentRef.get() == component.get());
|
||||
#endif
|
||||
}
|
||||
|
||||
Component* ComponentBuilder::getManagedComponent()
|
||||
{
|
||||
if (component == nullptr)
|
||||
{
|
||||
component.reset (createComponent());
|
||||
|
||||
#if JUCE_DEBUG
|
||||
componentRef = component.get();
|
||||
#endif
|
||||
}
|
||||
|
||||
return component.get();
|
||||
}
|
||||
|
||||
Component* ComponentBuilder::createComponent()
|
||||
{
|
||||
jassert (types.size() > 0); // You need to register all the necessary types before you can load a component!
|
||||
|
||||
if (TypeHandler* const type = getHandlerForState (state))
|
||||
return ComponentBuilderHelpers::createNewComponent (*type, state, nullptr);
|
||||
|
||||
jassertfalse; // trying to create a component from an unknown type of ValueTree
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void ComponentBuilder::registerTypeHandler (ComponentBuilder::TypeHandler* const type)
|
||||
{
|
||||
jassert (type != nullptr);
|
||||
|
||||
// Don't try to move your types around! Once a type has been added to a builder, the
|
||||
// builder owns it, and you should leave it alone!
|
||||
jassert (type->builder == nullptr);
|
||||
|
||||
types.add (type);
|
||||
type->builder = this;
|
||||
}
|
||||
|
||||
ComponentBuilder::TypeHandler* ComponentBuilder::getHandlerForState (const ValueTree& s) const
|
||||
{
|
||||
const Identifier targetType (s.getType());
|
||||
|
||||
for (int i = 0; i < types.size(); ++i)
|
||||
{
|
||||
TypeHandler* const t = types.getUnchecked(i);
|
||||
|
||||
if (t->type == targetType)
|
||||
return t;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int ComponentBuilder::getNumHandlers() const noexcept
|
||||
{
|
||||
return types.size();
|
||||
}
|
||||
|
||||
ComponentBuilder::TypeHandler* ComponentBuilder::getHandler (const int index) const noexcept
|
||||
{
|
||||
return types [index];
|
||||
}
|
||||
|
||||
void ComponentBuilder::registerStandardComponentTypes()
|
||||
{
|
||||
}
|
||||
|
||||
void ComponentBuilder::setImageProvider (ImageProvider* newImageProvider) noexcept
|
||||
{
|
||||
imageProvider = newImageProvider;
|
||||
}
|
||||
|
||||
ComponentBuilder::ImageProvider* ComponentBuilder::getImageProvider() const noexcept
|
||||
{
|
||||
return imageProvider;
|
||||
}
|
||||
|
||||
void ComponentBuilder::valueTreePropertyChanged (ValueTree& tree, const Identifier&)
|
||||
{
|
||||
ComponentBuilderHelpers::updateComponent (*this, tree);
|
||||
}
|
||||
|
||||
void ComponentBuilder::valueTreeChildAdded (ValueTree& tree, ValueTree&)
|
||||
{
|
||||
ComponentBuilderHelpers::updateComponent (*this, tree);
|
||||
}
|
||||
|
||||
void ComponentBuilder::valueTreeChildRemoved (ValueTree& tree, ValueTree&, int)
|
||||
{
|
||||
ComponentBuilderHelpers::updateComponent (*this, tree);
|
||||
}
|
||||
|
||||
void ComponentBuilder::valueTreeChildOrderChanged (ValueTree& tree, int, int)
|
||||
{
|
||||
ComponentBuilderHelpers::updateComponent (*this, tree);
|
||||
}
|
||||
|
||||
void ComponentBuilder::valueTreeParentChanged (ValueTree& tree)
|
||||
{
|
||||
ComponentBuilderHelpers::updateComponent (*this, tree);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
ComponentBuilder::TypeHandler::TypeHandler (const Identifier& valueTreeType)
|
||||
: type (valueTreeType), builder (nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
ComponentBuilder::TypeHandler::~TypeHandler()
|
||||
{
|
||||
}
|
||||
|
||||
ComponentBuilder* ComponentBuilder::TypeHandler::getBuilder() const noexcept
|
||||
{
|
||||
// A type handler needs to be registered with a ComponentBuilder before using it!
|
||||
jassert (builder != nullptr);
|
||||
return builder;
|
||||
}
|
||||
|
||||
void ComponentBuilder::updateChildComponents (Component& parent, const ValueTree& children)
|
||||
{
|
||||
using namespace ComponentBuilderHelpers;
|
||||
|
||||
auto numExistingChildComps = parent.getNumChildComponents();
|
||||
|
||||
Array<Component*> componentsInOrder;
|
||||
componentsInOrder.ensureStorageAllocated (numExistingChildComps);
|
||||
|
||||
{
|
||||
OwnedArray<Component> existingComponents;
|
||||
existingComponents.ensureStorageAllocated (numExistingChildComps);
|
||||
|
||||
for (int i = 0; i < numExistingChildComps; ++i)
|
||||
existingComponents.add (parent.getChildComponent (i));
|
||||
|
||||
auto newNumChildren = children.getNumChildren();
|
||||
|
||||
for (int i = 0; i < newNumChildren; ++i)
|
||||
{
|
||||
auto childState = children.getChild (i);
|
||||
auto* c = removeComponentWithID (existingComponents, getStateId (childState));
|
||||
|
||||
if (c == nullptr)
|
||||
{
|
||||
if (auto* type = getHandlerForState (childState))
|
||||
c = ComponentBuilderHelpers::createNewComponent (*type, childState, &parent);
|
||||
else
|
||||
jassertfalse;
|
||||
}
|
||||
|
||||
if (c != nullptr)
|
||||
componentsInOrder.add (c);
|
||||
}
|
||||
|
||||
// (remaining unused items in existingComponents get deleted here as it goes out of scope)
|
||||
}
|
||||
|
||||
// Make sure the z-order is correct..
|
||||
if (componentsInOrder.size() > 0)
|
||||
{
|
||||
componentsInOrder.getLast()->toFront (false);
|
||||
|
||||
for (int i = componentsInOrder.size() - 1; --i >= 0;)
|
||||
componentsInOrder.getUnchecked(i)->toBehind (componentsInOrder.getUnchecked (i + 1));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
namespace ComponentBuilderHelpers
|
||||
{
|
||||
static String getStateId (const ValueTree& state)
|
||||
{
|
||||
return state [ComponentBuilder::idProperty].toString();
|
||||
}
|
||||
|
||||
static Component* removeComponentWithID (OwnedArray<Component>& components, const String& compId)
|
||||
{
|
||||
jassert (compId.isNotEmpty());
|
||||
|
||||
for (int i = components.size(); --i >= 0;)
|
||||
{
|
||||
Component* const c = components.getUnchecked (i);
|
||||
|
||||
if (c->getComponentID() == compId)
|
||||
return components.removeAndReturn (i);
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static Component* findComponentWithID (Component& c, const String& compId)
|
||||
{
|
||||
jassert (compId.isNotEmpty());
|
||||
if (c.getComponentID() == compId)
|
||||
return &c;
|
||||
|
||||
for (auto* child : c.getChildren())
|
||||
if (auto* found = findComponentWithID (*child, compId))
|
||||
return found;
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static Component* createNewComponent (ComponentBuilder::TypeHandler& type,
|
||||
const ValueTree& state, Component* parent)
|
||||
{
|
||||
Component* const c = type.addNewComponentFromState (state, parent);
|
||||
jassert (c != nullptr && c->getParentComponent() == parent);
|
||||
c->setComponentID (getStateId (state));
|
||||
return c;
|
||||
}
|
||||
|
||||
static void updateComponent (ComponentBuilder& builder, const ValueTree& state)
|
||||
{
|
||||
if (Component* topLevelComp = builder.getManagedComponent())
|
||||
{
|
||||
ComponentBuilder::TypeHandler* const type = builder.getHandlerForState (state);
|
||||
const String uid (getStateId (state));
|
||||
|
||||
if (type == nullptr || uid.isEmpty())
|
||||
{
|
||||
// ..handle the case where a child of the actual state node has changed.
|
||||
if (state.getParent().isValid())
|
||||
updateComponent (builder, state.getParent());
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Component* const changedComp = findComponentWithID (*topLevelComp, uid))
|
||||
type->updateComponentFromState (changedComp, state);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
const Identifier ComponentBuilder::idProperty ("id");
|
||||
|
||||
ComponentBuilder::ComponentBuilder()
|
||||
: imageProvider (nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
ComponentBuilder::ComponentBuilder (const ValueTree& state_)
|
||||
: state (state_), imageProvider (nullptr)
|
||||
{
|
||||
state.addListener (this);
|
||||
}
|
||||
|
||||
ComponentBuilder::~ComponentBuilder()
|
||||
{
|
||||
state.removeListener (this);
|
||||
|
||||
#if JUCE_DEBUG
|
||||
// Don't delete the managed component!! The builder owns that component, and will delete
|
||||
// it automatically when it gets deleted.
|
||||
jassert (componentRef.get() == component.get());
|
||||
#endif
|
||||
}
|
||||
|
||||
Component* ComponentBuilder::getManagedComponent()
|
||||
{
|
||||
if (component == nullptr)
|
||||
{
|
||||
component.reset (createComponent());
|
||||
|
||||
#if JUCE_DEBUG
|
||||
componentRef = component.get();
|
||||
#endif
|
||||
}
|
||||
|
||||
return component.get();
|
||||
}
|
||||
|
||||
Component* ComponentBuilder::createComponent()
|
||||
{
|
||||
jassert (types.size() > 0); // You need to register all the necessary types before you can load a component!
|
||||
|
||||
if (TypeHandler* const type = getHandlerForState (state))
|
||||
return ComponentBuilderHelpers::createNewComponent (*type, state, nullptr);
|
||||
|
||||
jassertfalse; // trying to create a component from an unknown type of ValueTree
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void ComponentBuilder::registerTypeHandler (ComponentBuilder::TypeHandler* const type)
|
||||
{
|
||||
jassert (type != nullptr);
|
||||
|
||||
// Don't try to move your types around! Once a type has been added to a builder, the
|
||||
// builder owns it, and you should leave it alone!
|
||||
jassert (type->builder == nullptr);
|
||||
|
||||
types.add (type);
|
||||
type->builder = this;
|
||||
}
|
||||
|
||||
ComponentBuilder::TypeHandler* ComponentBuilder::getHandlerForState (const ValueTree& s) const
|
||||
{
|
||||
const Identifier targetType (s.getType());
|
||||
|
||||
for (int i = 0; i < types.size(); ++i)
|
||||
{
|
||||
TypeHandler* const t = types.getUnchecked(i);
|
||||
|
||||
if (t->type == targetType)
|
||||
return t;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int ComponentBuilder::getNumHandlers() const noexcept
|
||||
{
|
||||
return types.size();
|
||||
}
|
||||
|
||||
ComponentBuilder::TypeHandler* ComponentBuilder::getHandler (const int index) const noexcept
|
||||
{
|
||||
return types [index];
|
||||
}
|
||||
|
||||
void ComponentBuilder::registerStandardComponentTypes()
|
||||
{
|
||||
}
|
||||
|
||||
void ComponentBuilder::setImageProvider (ImageProvider* newImageProvider) noexcept
|
||||
{
|
||||
imageProvider = newImageProvider;
|
||||
}
|
||||
|
||||
ComponentBuilder::ImageProvider* ComponentBuilder::getImageProvider() const noexcept
|
||||
{
|
||||
return imageProvider;
|
||||
}
|
||||
|
||||
void ComponentBuilder::valueTreePropertyChanged (ValueTree& tree, const Identifier&)
|
||||
{
|
||||
ComponentBuilderHelpers::updateComponent (*this, tree);
|
||||
}
|
||||
|
||||
void ComponentBuilder::valueTreeChildAdded (ValueTree& tree, ValueTree&)
|
||||
{
|
||||
ComponentBuilderHelpers::updateComponent (*this, tree);
|
||||
}
|
||||
|
||||
void ComponentBuilder::valueTreeChildRemoved (ValueTree& tree, ValueTree&, int)
|
||||
{
|
||||
ComponentBuilderHelpers::updateComponent (*this, tree);
|
||||
}
|
||||
|
||||
void ComponentBuilder::valueTreeChildOrderChanged (ValueTree& tree, int, int)
|
||||
{
|
||||
ComponentBuilderHelpers::updateComponent (*this, tree);
|
||||
}
|
||||
|
||||
void ComponentBuilder::valueTreeParentChanged (ValueTree& tree)
|
||||
{
|
||||
ComponentBuilderHelpers::updateComponent (*this, tree);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
ComponentBuilder::TypeHandler::TypeHandler (const Identifier& valueTreeType)
|
||||
: type (valueTreeType), builder (nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
ComponentBuilder::TypeHandler::~TypeHandler()
|
||||
{
|
||||
}
|
||||
|
||||
ComponentBuilder* ComponentBuilder::TypeHandler::getBuilder() const noexcept
|
||||
{
|
||||
// A type handler needs to be registered with a ComponentBuilder before using it!
|
||||
jassert (builder != nullptr);
|
||||
return builder;
|
||||
}
|
||||
|
||||
void ComponentBuilder::updateChildComponents (Component& parent, const ValueTree& children)
|
||||
{
|
||||
using namespace ComponentBuilderHelpers;
|
||||
|
||||
auto numExistingChildComps = parent.getNumChildComponents();
|
||||
|
||||
Array<Component*> componentsInOrder;
|
||||
componentsInOrder.ensureStorageAllocated (numExistingChildComps);
|
||||
|
||||
{
|
||||
OwnedArray<Component> existingComponents;
|
||||
existingComponents.ensureStorageAllocated (numExistingChildComps);
|
||||
|
||||
for (int i = 0; i < numExistingChildComps; ++i)
|
||||
existingComponents.add (parent.getChildComponent (i));
|
||||
|
||||
auto newNumChildren = children.getNumChildren();
|
||||
|
||||
for (int i = 0; i < newNumChildren; ++i)
|
||||
{
|
||||
auto childState = children.getChild (i);
|
||||
auto* c = removeComponentWithID (existingComponents, getStateId (childState));
|
||||
|
||||
if (c == nullptr)
|
||||
{
|
||||
if (auto* type = getHandlerForState (childState))
|
||||
c = ComponentBuilderHelpers::createNewComponent (*type, childState, &parent);
|
||||
else
|
||||
jassertfalse;
|
||||
}
|
||||
|
||||
if (c != nullptr)
|
||||
componentsInOrder.add (c);
|
||||
}
|
||||
|
||||
// (remaining unused items in existingComponents get deleted here as it goes out of scope)
|
||||
}
|
||||
|
||||
// Make sure the z-order is correct..
|
||||
if (componentsInOrder.size() > 0)
|
||||
{
|
||||
componentsInOrder.getLast()->toFront (false);
|
||||
|
||||
for (int i = componentsInOrder.size() - 1; --i >= 0;)
|
||||
componentsInOrder.getUnchecked(i)->toBehind (componentsInOrder.getUnchecked (i + 1));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
|
@ -1,247 +1,247 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Loads and maintains a tree of Components from a ValueTree that represents them.
|
||||
|
||||
To allow the state of a tree of components to be saved as a ValueTree and re-loaded,
|
||||
this class lets you register a set of type-handlers for the different components that
|
||||
are involved, and then uses these types to re-create a set of components from its
|
||||
stored state.
|
||||
|
||||
Essentially, to use this, you need to create a ComponentBuilder with your ValueTree,
|
||||
then use registerTypeHandler() to give it a set of type handlers that can cope with
|
||||
all the items in your tree. Then you can call getComponent() to build the component.
|
||||
Once you've got the component you can either take it and delete the ComponentBuilder
|
||||
object, or if you keep the ComponentBuilder around, it'll monitor any changes in the
|
||||
ValueTree and automatically update the component to reflect these changes.
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API ComponentBuilder : private ValueTree::Listener
|
||||
{
|
||||
public:
|
||||
/** Creates a ComponentBuilder that will use the given state.
|
||||
Once you've created your builder, you should use registerTypeHandler() to register some
|
||||
type handlers for it, and then you can call createComponent() or getManagedComponent()
|
||||
to get the actual component.
|
||||
*/
|
||||
explicit ComponentBuilder (const ValueTree& state);
|
||||
|
||||
/** Creates a builder that doesn't have a state object. */
|
||||
ComponentBuilder();
|
||||
|
||||
/** Destructor. */
|
||||
~ComponentBuilder() override;
|
||||
|
||||
/** This is the ValueTree data object that the builder is working with. */
|
||||
ValueTree state;
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the builder's component (creating it if necessary).
|
||||
|
||||
The first time that this method is called, the builder will attempt to create a component
|
||||
from the ValueTree, so you must have registered some suitable type handlers before calling
|
||||
this. If there's a problem and the component can't be created, this method returns nullptr.
|
||||
|
||||
The component that is returned is owned by this ComponentBuilder, so you can put it inside
|
||||
your own parent components, but don't delete it! The ComponentBuilder will delete it automatically
|
||||
when the builder is destroyed. If you want to get a component that you can delete yourself,
|
||||
call createComponent() instead.
|
||||
|
||||
The ComponentBuilder will update this component if any changes are made to the ValueTree, so if
|
||||
there's a chance that the tree might change, be careful not to keep any pointers to sub-components,
|
||||
as they may be changed or removed.
|
||||
*/
|
||||
Component* getManagedComponent();
|
||||
|
||||
/** Creates and returns a new instance of the component that the ValueTree represents.
|
||||
The caller is responsible for using and deleting the object that is returned. Unlike
|
||||
getManagedComponent(), the component that is returned will not be updated by the builder.
|
||||
*/
|
||||
Component* createComponent();
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
The class is a base class for objects that manage the loading of a type of component
|
||||
from a ValueTree.
|
||||
|
||||
To store and re-load a tree of components as a ValueTree, each component type must have
|
||||
a TypeHandler to represent it.
|
||||
|
||||
@see ComponentBuilder::registerTypeHandler(), Drawable::registerDrawableTypeHandlers()
|
||||
*/
|
||||
class JUCE_API TypeHandler
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates a TypeHandler.
|
||||
The valueTreeType must be the type name of the ValueTrees that this handler can parse.
|
||||
*/
|
||||
explicit TypeHandler (const Identifier& valueTreeType);
|
||||
|
||||
/** Destructor. */
|
||||
virtual ~TypeHandler();
|
||||
|
||||
/** Returns the type of the ValueTrees that this handler can parse. */
|
||||
const Identifier type;
|
||||
|
||||
/** Returns the builder that this type is registered with. */
|
||||
ComponentBuilder* getBuilder() const noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** This method must create a new component from the given state, add it to the specified
|
||||
parent component (which may be null), and return it.
|
||||
|
||||
The ValueTree will have been pre-checked to make sure that its type matches the type
|
||||
that this handler supports.
|
||||
|
||||
There's no need to set the new Component's ID to match that of the state - the builder
|
||||
will take care of that itself.
|
||||
*/
|
||||
virtual Component* addNewComponentFromState (const ValueTree& state, Component* parent) = 0;
|
||||
|
||||
/** This method must update an existing component from a new ValueTree state.
|
||||
|
||||
A component that has been created with addNewComponentFromState() may need to be updated
|
||||
if the ValueTree changes, so this method is used to do that. Your implementation must do
|
||||
whatever's necessary to update the component from the new state provided.
|
||||
|
||||
The ValueTree will have been pre-checked to make sure that its type matches the type
|
||||
that this handler supports, and the component will have been created by this type's
|
||||
addNewComponentFromState() method.
|
||||
*/
|
||||
virtual void updateComponentFromState (Component* component, const ValueTree& state) = 0;
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
friend class ComponentBuilder;
|
||||
ComponentBuilder* builder;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TypeHandler)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** Adds a type handler that the builder can use when trying to load components.
|
||||
@see Drawable::registerDrawableTypeHandlers()
|
||||
*/
|
||||
void registerTypeHandler (TypeHandler* type);
|
||||
|
||||
/** Tries to find a registered type handler that can load a component from the given ValueTree. */
|
||||
TypeHandler* getHandlerForState (const ValueTree& state) const;
|
||||
|
||||
/** Returns the number of registered type handlers.
|
||||
@see getHandler, registerTypeHandler
|
||||
*/
|
||||
int getNumHandlers() const noexcept;
|
||||
|
||||
/** Returns one of the registered type handlers.
|
||||
@see getNumHandlers, registerTypeHandler
|
||||
*/
|
||||
TypeHandler* getHandler (int index) const noexcept;
|
||||
|
||||
/** Registers handlers for various standard juce components. */
|
||||
void registerStandardComponentTypes();
|
||||
|
||||
//==============================================================================
|
||||
/** This class is used when references to images need to be stored in ValueTrees.
|
||||
|
||||
An instance of an ImageProvider provides a mechanism for converting an Image to/from
|
||||
a reference, which may be a file, URL, ID string, or whatever system is appropriate in
|
||||
your app.
|
||||
|
||||
When you're loading components from a ValueTree that may need a way of loading images, you
|
||||
should call ComponentBuilder::setImageProvider() to supply a suitable provider before
|
||||
trying to load the component.
|
||||
|
||||
@see ComponentBuilder::setImageProvider()
|
||||
*/
|
||||
class JUCE_API ImageProvider
|
||||
{
|
||||
public:
|
||||
ImageProvider() = default;
|
||||
virtual ~ImageProvider() = default;
|
||||
|
||||
/** Retrieves the image associated with this identifier, which could be any
|
||||
kind of string, number, filename, etc.
|
||||
|
||||
The image that is returned will be owned by the caller, but it may come
|
||||
from the ImageCache.
|
||||
*/
|
||||
virtual Image getImageForIdentifier (const var& imageIdentifier) = 0;
|
||||
|
||||
/** Returns an identifier to be used to refer to a given image.
|
||||
This is used when a reference to an image is stored in a ValueTree.
|
||||
*/
|
||||
virtual var getIdentifierForImage (const Image& image) = 0;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** Gives the builder an ImageProvider object that the type handlers can use when
|
||||
loading images from stored references.
|
||||
|
||||
The object that is passed in is not owned by the builder, so the caller must delete
|
||||
it when it is no longer needed, but not while the builder may still be using it. To
|
||||
clear the image provider, just call setImageProvider (nullptr).
|
||||
*/
|
||||
void setImageProvider (ImageProvider* newImageProvider) noexcept;
|
||||
|
||||
/** Returns the current image provider that this builder is using, or nullptr if none has been set. */
|
||||
ImageProvider* getImageProvider() const noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** Updates the children of a parent component by updating them from the children of
|
||||
a given ValueTree.
|
||||
*/
|
||||
void updateChildComponents (Component& parent, const ValueTree& children);
|
||||
|
||||
/** An identifier for the property of the ValueTrees that is used to store a unique ID
|
||||
for that component.
|
||||
*/
|
||||
static const Identifier idProperty;
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
OwnedArray<TypeHandler> types;
|
||||
std::unique_ptr<Component> component;
|
||||
ImageProvider* imageProvider;
|
||||
#if JUCE_DEBUG
|
||||
WeakReference<Component> componentRef;
|
||||
#endif
|
||||
|
||||
void valueTreePropertyChanged (ValueTree&, const Identifier&) override;
|
||||
void valueTreeChildAdded (ValueTree&, ValueTree&) override;
|
||||
void valueTreeChildRemoved (ValueTree&, ValueTree&, int) override;
|
||||
void valueTreeChildOrderChanged (ValueTree&, int, int) override;
|
||||
void valueTreeParentChanged (ValueTree&) override;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ComponentBuilder)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Loads and maintains a tree of Components from a ValueTree that represents them.
|
||||
|
||||
To allow the state of a tree of components to be saved as a ValueTree and re-loaded,
|
||||
this class lets you register a set of type-handlers for the different components that
|
||||
are involved, and then uses these types to re-create a set of components from its
|
||||
stored state.
|
||||
|
||||
Essentially, to use this, you need to create a ComponentBuilder with your ValueTree,
|
||||
then use registerTypeHandler() to give it a set of type handlers that can cope with
|
||||
all the items in your tree. Then you can call getComponent() to build the component.
|
||||
Once you've got the component you can either take it and delete the ComponentBuilder
|
||||
object, or if you keep the ComponentBuilder around, it'll monitor any changes in the
|
||||
ValueTree and automatically update the component to reflect these changes.
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API ComponentBuilder : private ValueTree::Listener
|
||||
{
|
||||
public:
|
||||
/** Creates a ComponentBuilder that will use the given state.
|
||||
Once you've created your builder, you should use registerTypeHandler() to register some
|
||||
type handlers for it, and then you can call createComponent() or getManagedComponent()
|
||||
to get the actual component.
|
||||
*/
|
||||
explicit ComponentBuilder (const ValueTree& state);
|
||||
|
||||
/** Creates a builder that doesn't have a state object. */
|
||||
ComponentBuilder();
|
||||
|
||||
/** Destructor. */
|
||||
~ComponentBuilder() override;
|
||||
|
||||
/** This is the ValueTree data object that the builder is working with. */
|
||||
ValueTree state;
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the builder's component (creating it if necessary).
|
||||
|
||||
The first time that this method is called, the builder will attempt to create a component
|
||||
from the ValueTree, so you must have registered some suitable type handlers before calling
|
||||
this. If there's a problem and the component can't be created, this method returns nullptr.
|
||||
|
||||
The component that is returned is owned by this ComponentBuilder, so you can put it inside
|
||||
your own parent components, but don't delete it! The ComponentBuilder will delete it automatically
|
||||
when the builder is destroyed. If you want to get a component that you can delete yourself,
|
||||
call createComponent() instead.
|
||||
|
||||
The ComponentBuilder will update this component if any changes are made to the ValueTree, so if
|
||||
there's a chance that the tree might change, be careful not to keep any pointers to sub-components,
|
||||
as they may be changed or removed.
|
||||
*/
|
||||
Component* getManagedComponent();
|
||||
|
||||
/** Creates and returns a new instance of the component that the ValueTree represents.
|
||||
The caller is responsible for using and deleting the object that is returned. Unlike
|
||||
getManagedComponent(), the component that is returned will not be updated by the builder.
|
||||
*/
|
||||
Component* createComponent();
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
The class is a base class for objects that manage the loading of a type of component
|
||||
from a ValueTree.
|
||||
|
||||
To store and re-load a tree of components as a ValueTree, each component type must have
|
||||
a TypeHandler to represent it.
|
||||
|
||||
@see ComponentBuilder::registerTypeHandler(), Drawable::registerDrawableTypeHandlers()
|
||||
*/
|
||||
class JUCE_API TypeHandler
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates a TypeHandler.
|
||||
The valueTreeType must be the type name of the ValueTrees that this handler can parse.
|
||||
*/
|
||||
explicit TypeHandler (const Identifier& valueTreeType);
|
||||
|
||||
/** Destructor. */
|
||||
virtual ~TypeHandler();
|
||||
|
||||
/** Returns the type of the ValueTrees that this handler can parse. */
|
||||
const Identifier type;
|
||||
|
||||
/** Returns the builder that this type is registered with. */
|
||||
ComponentBuilder* getBuilder() const noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** This method must create a new component from the given state, add it to the specified
|
||||
parent component (which may be null), and return it.
|
||||
|
||||
The ValueTree will have been pre-checked to make sure that its type matches the type
|
||||
that this handler supports.
|
||||
|
||||
There's no need to set the new Component's ID to match that of the state - the builder
|
||||
will take care of that itself.
|
||||
*/
|
||||
virtual Component* addNewComponentFromState (const ValueTree& state, Component* parent) = 0;
|
||||
|
||||
/** This method must update an existing component from a new ValueTree state.
|
||||
|
||||
A component that has been created with addNewComponentFromState() may need to be updated
|
||||
if the ValueTree changes, so this method is used to do that. Your implementation must do
|
||||
whatever's necessary to update the component from the new state provided.
|
||||
|
||||
The ValueTree will have been pre-checked to make sure that its type matches the type
|
||||
that this handler supports, and the component will have been created by this type's
|
||||
addNewComponentFromState() method.
|
||||
*/
|
||||
virtual void updateComponentFromState (Component* component, const ValueTree& state) = 0;
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
friend class ComponentBuilder;
|
||||
ComponentBuilder* builder;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TypeHandler)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** Adds a type handler that the builder can use when trying to load components.
|
||||
@see Drawable::registerDrawableTypeHandlers()
|
||||
*/
|
||||
void registerTypeHandler (TypeHandler* type);
|
||||
|
||||
/** Tries to find a registered type handler that can load a component from the given ValueTree. */
|
||||
TypeHandler* getHandlerForState (const ValueTree& state) const;
|
||||
|
||||
/** Returns the number of registered type handlers.
|
||||
@see getHandler, registerTypeHandler
|
||||
*/
|
||||
int getNumHandlers() const noexcept;
|
||||
|
||||
/** Returns one of the registered type handlers.
|
||||
@see getNumHandlers, registerTypeHandler
|
||||
*/
|
||||
TypeHandler* getHandler (int index) const noexcept;
|
||||
|
||||
/** Registers handlers for various standard juce components. */
|
||||
void registerStandardComponentTypes();
|
||||
|
||||
//==============================================================================
|
||||
/** This class is used when references to images need to be stored in ValueTrees.
|
||||
|
||||
An instance of an ImageProvider provides a mechanism for converting an Image to/from
|
||||
a reference, which may be a file, URL, ID string, or whatever system is appropriate in
|
||||
your app.
|
||||
|
||||
When you're loading components from a ValueTree that may need a way of loading images, you
|
||||
should call ComponentBuilder::setImageProvider() to supply a suitable provider before
|
||||
trying to load the component.
|
||||
|
||||
@see ComponentBuilder::setImageProvider()
|
||||
*/
|
||||
class JUCE_API ImageProvider
|
||||
{
|
||||
public:
|
||||
ImageProvider() = default;
|
||||
virtual ~ImageProvider() = default;
|
||||
|
||||
/** Retrieves the image associated with this identifier, which could be any
|
||||
kind of string, number, filename, etc.
|
||||
|
||||
The image that is returned will be owned by the caller, but it may come
|
||||
from the ImageCache.
|
||||
*/
|
||||
virtual Image getImageForIdentifier (const var& imageIdentifier) = 0;
|
||||
|
||||
/** Returns an identifier to be used to refer to a given image.
|
||||
This is used when a reference to an image is stored in a ValueTree.
|
||||
*/
|
||||
virtual var getIdentifierForImage (const Image& image) = 0;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** Gives the builder an ImageProvider object that the type handlers can use when
|
||||
loading images from stored references.
|
||||
|
||||
The object that is passed in is not owned by the builder, so the caller must delete
|
||||
it when it is no longer needed, but not while the builder may still be using it. To
|
||||
clear the image provider, just call setImageProvider (nullptr).
|
||||
*/
|
||||
void setImageProvider (ImageProvider* newImageProvider) noexcept;
|
||||
|
||||
/** Returns the current image provider that this builder is using, or nullptr if none has been set. */
|
||||
ImageProvider* getImageProvider() const noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** Updates the children of a parent component by updating them from the children of
|
||||
a given ValueTree.
|
||||
*/
|
||||
void updateChildComponents (Component& parent, const ValueTree& children);
|
||||
|
||||
/** An identifier for the property of the ValueTrees that is used to store a unique ID
|
||||
for that component.
|
||||
*/
|
||||
static const Identifier idProperty;
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
OwnedArray<TypeHandler> types;
|
||||
std::unique_ptr<Component> component;
|
||||
ImageProvider* imageProvider;
|
||||
#if JUCE_DEBUG
|
||||
WeakReference<Component> componentRef;
|
||||
#endif
|
||||
|
||||
void valueTreePropertyChanged (ValueTree&, const Identifier&) override;
|
||||
void valueTreeChildAdded (ValueTree&, ValueTree&) override;
|
||||
void valueTreeChildRemoved (ValueTree&, ValueTree&, int) override;
|
||||
void valueTreeChildOrderChanged (ValueTree&, int, int) override;
|
||||
void valueTreeParentChanged (ValueTree&) override;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ComponentBuilder)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
|
@ -1,142 +1,142 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
ComponentMovementWatcher::ComponentMovementWatcher (Component* const comp)
|
||||
: component (comp),
|
||||
wasShowing (comp->isShowing())
|
||||
{
|
||||
jassert (component != nullptr); // can't use this with a null pointer..
|
||||
|
||||
component->addComponentListener (this);
|
||||
registerWithParentComps();
|
||||
}
|
||||
|
||||
ComponentMovementWatcher::~ComponentMovementWatcher()
|
||||
{
|
||||
if (component != nullptr)
|
||||
component->removeComponentListener (this);
|
||||
|
||||
unregister();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void ComponentMovementWatcher::componentParentHierarchyChanged (Component&)
|
||||
{
|
||||
if (component != nullptr && ! reentrant)
|
||||
{
|
||||
const ScopedValueSetter<bool> setter (reentrant, true);
|
||||
|
||||
auto* peer = component->getPeer();
|
||||
auto peerID = peer != nullptr ? peer->getUniqueID() : 0;
|
||||
|
||||
if (peerID != lastPeerID)
|
||||
{
|
||||
componentPeerChanged();
|
||||
|
||||
if (component == nullptr)
|
||||
return;
|
||||
|
||||
lastPeerID = peerID;
|
||||
}
|
||||
|
||||
unregister();
|
||||
registerWithParentComps();
|
||||
|
||||
componentMovedOrResized (*component, true, true);
|
||||
|
||||
if (component != nullptr)
|
||||
componentVisibilityChanged (*component);
|
||||
}
|
||||
}
|
||||
|
||||
void ComponentMovementWatcher::componentMovedOrResized (Component&, bool wasMoved, bool wasResized)
|
||||
{
|
||||
if (component != nullptr)
|
||||
{
|
||||
if (wasMoved)
|
||||
{
|
||||
Point<int> newPos;
|
||||
auto* top = component->getTopLevelComponent();
|
||||
|
||||
if (top != component)
|
||||
newPos = top->getLocalPoint (component, Point<int>());
|
||||
else
|
||||
newPos = top->getPosition();
|
||||
|
||||
wasMoved = lastBounds.getPosition() != newPos;
|
||||
lastBounds.setPosition (newPos);
|
||||
}
|
||||
|
||||
wasResized = (lastBounds.getWidth() != component->getWidth() || lastBounds.getHeight() != component->getHeight());
|
||||
lastBounds.setSize (component->getWidth(), component->getHeight());
|
||||
|
||||
if (wasMoved || wasResized)
|
||||
componentMovedOrResized (wasMoved, wasResized);
|
||||
}
|
||||
}
|
||||
|
||||
void ComponentMovementWatcher::componentBeingDeleted (Component& comp)
|
||||
{
|
||||
registeredParentComps.removeFirstMatchingValue (&comp);
|
||||
|
||||
if (component == &comp)
|
||||
unregister();
|
||||
}
|
||||
|
||||
void ComponentMovementWatcher::componentVisibilityChanged (Component&)
|
||||
{
|
||||
if (component != nullptr)
|
||||
{
|
||||
const bool isShowingNow = component->isShowing();
|
||||
|
||||
if (wasShowing != isShowingNow)
|
||||
{
|
||||
wasShowing = isShowingNow;
|
||||
componentVisibilityChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ComponentMovementWatcher::registerWithParentComps()
|
||||
{
|
||||
for (auto* p = component->getParentComponent(); p != nullptr; p = p->getParentComponent())
|
||||
{
|
||||
p->addComponentListener (this);
|
||||
registeredParentComps.add (p);
|
||||
}
|
||||
}
|
||||
|
||||
void ComponentMovementWatcher::unregister()
|
||||
{
|
||||
for (auto* c : registeredParentComps)
|
||||
c->removeComponentListener (this);
|
||||
|
||||
registeredParentComps.clear();
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
ComponentMovementWatcher::ComponentMovementWatcher (Component* const comp)
|
||||
: component (comp),
|
||||
wasShowing (comp->isShowing())
|
||||
{
|
||||
jassert (component != nullptr); // can't use this with a null pointer..
|
||||
|
||||
component->addComponentListener (this);
|
||||
registerWithParentComps();
|
||||
}
|
||||
|
||||
ComponentMovementWatcher::~ComponentMovementWatcher()
|
||||
{
|
||||
if (component != nullptr)
|
||||
component->removeComponentListener (this);
|
||||
|
||||
unregister();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void ComponentMovementWatcher::componentParentHierarchyChanged (Component&)
|
||||
{
|
||||
if (component != nullptr && ! reentrant)
|
||||
{
|
||||
const ScopedValueSetter<bool> setter (reentrant, true);
|
||||
|
||||
auto* peer = component->getPeer();
|
||||
auto peerID = peer != nullptr ? peer->getUniqueID() : 0;
|
||||
|
||||
if (peerID != lastPeerID)
|
||||
{
|
||||
componentPeerChanged();
|
||||
|
||||
if (component == nullptr)
|
||||
return;
|
||||
|
||||
lastPeerID = peerID;
|
||||
}
|
||||
|
||||
unregister();
|
||||
registerWithParentComps();
|
||||
|
||||
componentMovedOrResized (*component, true, true);
|
||||
|
||||
if (component != nullptr)
|
||||
componentVisibilityChanged (*component);
|
||||
}
|
||||
}
|
||||
|
||||
void ComponentMovementWatcher::componentMovedOrResized (Component&, bool wasMoved, bool wasResized)
|
||||
{
|
||||
if (component != nullptr)
|
||||
{
|
||||
if (wasMoved)
|
||||
{
|
||||
Point<int> newPos;
|
||||
auto* top = component->getTopLevelComponent();
|
||||
|
||||
if (top != component)
|
||||
newPos = top->getLocalPoint (component, Point<int>());
|
||||
else
|
||||
newPos = top->getPosition();
|
||||
|
||||
wasMoved = lastBounds.getPosition() != newPos;
|
||||
lastBounds.setPosition (newPos);
|
||||
}
|
||||
|
||||
wasResized = (lastBounds.getWidth() != component->getWidth() || lastBounds.getHeight() != component->getHeight());
|
||||
lastBounds.setSize (component->getWidth(), component->getHeight());
|
||||
|
||||
if (wasMoved || wasResized)
|
||||
componentMovedOrResized (wasMoved, wasResized);
|
||||
}
|
||||
}
|
||||
|
||||
void ComponentMovementWatcher::componentBeingDeleted (Component& comp)
|
||||
{
|
||||
registeredParentComps.removeFirstMatchingValue (&comp);
|
||||
|
||||
if (component == &comp)
|
||||
unregister();
|
||||
}
|
||||
|
||||
void ComponentMovementWatcher::componentVisibilityChanged (Component&)
|
||||
{
|
||||
if (component != nullptr)
|
||||
{
|
||||
const bool isShowingNow = component->isShowing();
|
||||
|
||||
if (wasShowing != isShowingNow)
|
||||
{
|
||||
wasShowing = isShowingNow;
|
||||
componentVisibilityChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ComponentMovementWatcher::registerWithParentComps()
|
||||
{
|
||||
for (auto* p = component->getParentComponent(); p != nullptr; p = p->getParentComponent())
|
||||
{
|
||||
p->addComponentListener (this);
|
||||
registeredParentComps.add (p);
|
||||
}
|
||||
}
|
||||
|
||||
void ComponentMovementWatcher::unregister()
|
||||
{
|
||||
for (auto* c : registeredParentComps)
|
||||
c->removeComponentListener (this);
|
||||
|
||||
registeredParentComps.clear();
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
|
@ -1,96 +1,96 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/** An object that watches for any movement of a component or any of its parent components.
|
||||
|
||||
This makes it easy to check when a component is moved relative to its top-level
|
||||
peer window. The normal Component::moved() method is only called when a component
|
||||
moves relative to its immediate parent, and sometimes you want to know if any of
|
||||
components higher up the tree have moved (which of course will affect the overall
|
||||
position of all their sub-components).
|
||||
|
||||
It also includes a callback that lets you know when the top-level peer is changed.
|
||||
|
||||
This class is used by specialised components like WebBrowserComponent
|
||||
because they need to keep their custom windows in the right place and respond to
|
||||
changes in the peer.
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API ComponentMovementWatcher : public ComponentListener
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates a ComponentMovementWatcher to watch a given target component. */
|
||||
ComponentMovementWatcher (Component* componentToWatch);
|
||||
|
||||
/** Destructor. */
|
||||
~ComponentMovementWatcher() override;
|
||||
|
||||
//==============================================================================
|
||||
/** This callback happens when the component that is being watched is moved
|
||||
relative to its top-level peer window, or when it is resized. */
|
||||
virtual void componentMovedOrResized (bool wasMoved, bool wasResized) = 0;
|
||||
|
||||
/** This callback happens when the component's top-level peer is changed. */
|
||||
virtual void componentPeerChanged() = 0;
|
||||
|
||||
/** This callback happens when the component's visibility state changes, possibly due to
|
||||
one of its parents being made visible or invisible.
|
||||
*/
|
||||
virtual void componentVisibilityChanged() = 0;
|
||||
|
||||
/** Returns the component that's being watched. */
|
||||
Component* getComponent() const noexcept { return component.get(); }
|
||||
|
||||
//==============================================================================
|
||||
/** @internal */
|
||||
void componentParentHierarchyChanged (Component&) override;
|
||||
/** @internal */
|
||||
void componentMovedOrResized (Component&, bool wasMoved, bool wasResized) override;
|
||||
/** @internal */
|
||||
void componentBeingDeleted (Component&) override;
|
||||
/** @internal */
|
||||
void componentVisibilityChanged (Component&) override;
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
WeakReference<Component> component;
|
||||
uint32 lastPeerID = 0;
|
||||
Array<Component*> registeredParentComps;
|
||||
bool reentrant = false, wasShowing;
|
||||
Rectangle<int> lastBounds;
|
||||
|
||||
void unregister();
|
||||
void registerWithParentComps();
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ComponentMovementWatcher)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/** An object that watches for any movement of a component or any of its parent components.
|
||||
|
||||
This makes it easy to check when a component is moved relative to its top-level
|
||||
peer window. The normal Component::moved() method is only called when a component
|
||||
moves relative to its immediate parent, and sometimes you want to know if any of
|
||||
components higher up the tree have moved (which of course will affect the overall
|
||||
position of all their sub-components).
|
||||
|
||||
It also includes a callback that lets you know when the top-level peer is changed.
|
||||
|
||||
This class is used by specialised components like WebBrowserComponent
|
||||
because they need to keep their custom windows in the right place and respond to
|
||||
changes in the peer.
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API ComponentMovementWatcher : public ComponentListener
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates a ComponentMovementWatcher to watch a given target component. */
|
||||
ComponentMovementWatcher (Component* componentToWatch);
|
||||
|
||||
/** Destructor. */
|
||||
~ComponentMovementWatcher() override;
|
||||
|
||||
//==============================================================================
|
||||
/** This callback happens when the component that is being watched is moved
|
||||
relative to its top-level peer window, or when it is resized. */
|
||||
virtual void componentMovedOrResized (bool wasMoved, bool wasResized) = 0;
|
||||
|
||||
/** This callback happens when the component's top-level peer is changed. */
|
||||
virtual void componentPeerChanged() = 0;
|
||||
|
||||
/** This callback happens when the component's visibility state changes, possibly due to
|
||||
one of its parents being made visible or invisible.
|
||||
*/
|
||||
virtual void componentVisibilityChanged() = 0;
|
||||
|
||||
/** Returns the component that's being watched. */
|
||||
Component* getComponent() const noexcept { return component.get(); }
|
||||
|
||||
//==============================================================================
|
||||
/** @internal */
|
||||
void componentParentHierarchyChanged (Component&) override;
|
||||
/** @internal */
|
||||
void componentMovedOrResized (Component&, bool wasMoved, bool wasResized) override;
|
||||
/** @internal */
|
||||
void componentBeingDeleted (Component&) override;
|
||||
/** @internal */
|
||||
void componentVisibilityChanged (Component&) override;
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
WeakReference<Component> component;
|
||||
uint32 lastPeerID = 0;
|
||||
Array<Component*> registeredParentComps;
|
||||
bool reentrant = false, wasShowing;
|
||||
Rectangle<int> lastBounds;
|
||||
|
||||
void unregister();
|
||||
void registerWithParentComps();
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ComponentMovementWatcher)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
|
@ -1,468 +1,468 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
struct ConcertinaPanel::PanelSizes
|
||||
{
|
||||
struct Panel
|
||||
{
|
||||
Panel() = default;
|
||||
|
||||
Panel (int sz, int mn, int mx) noexcept
|
||||
: size (sz), minSize (mn), maxSize (mx) {}
|
||||
|
||||
int setSize (int newSize) noexcept
|
||||
{
|
||||
jassert (minSize <= maxSize);
|
||||
auto oldSize = size;
|
||||
size = jlimit (minSize, maxSize, newSize);
|
||||
return size - oldSize;
|
||||
}
|
||||
|
||||
int expand (int amount) noexcept
|
||||
{
|
||||
amount = jmin (amount, maxSize - size);
|
||||
size += amount;
|
||||
return amount;
|
||||
}
|
||||
|
||||
int reduce (int amount) noexcept
|
||||
{
|
||||
amount = jmin (amount, size - minSize);
|
||||
size -= amount;
|
||||
return amount;
|
||||
}
|
||||
|
||||
bool canExpand() const noexcept { return size < maxSize; }
|
||||
bool isMinimised() const noexcept { return size <= minSize; }
|
||||
|
||||
int size, minSize, maxSize;
|
||||
};
|
||||
|
||||
Array<Panel> sizes;
|
||||
|
||||
Panel& get (int index) noexcept { return sizes.getReference (index); }
|
||||
const Panel& get (int index) const noexcept { return sizes.getReference (index); }
|
||||
|
||||
PanelSizes withMovedPanel (int index, int targetPosition, int totalSpace) const
|
||||
{
|
||||
auto num = sizes.size();
|
||||
totalSpace = jmax (totalSpace, getMinimumSize (0, num));
|
||||
targetPosition = jmax (targetPosition, totalSpace - getMaximumSize (index, num));
|
||||
|
||||
PanelSizes newSizes (*this);
|
||||
newSizes.stretchRange (0, index, targetPosition - newSizes.getTotalSize (0, index), stretchLast);
|
||||
newSizes.stretchRange (index, num, totalSpace - newSizes.getTotalSize (0, index) - newSizes.getTotalSize (index, num), stretchFirst);
|
||||
return newSizes;
|
||||
}
|
||||
|
||||
PanelSizes fittedInto (int totalSpace) const
|
||||
{
|
||||
auto newSizes (*this);
|
||||
auto num = newSizes.sizes.size();
|
||||
totalSpace = jmax (totalSpace, getMinimumSize (0, num));
|
||||
newSizes.stretchRange (0, num, totalSpace - newSizes.getTotalSize (0, num), stretchAll);
|
||||
return newSizes;
|
||||
}
|
||||
|
||||
PanelSizes withResizedPanel (int index, int panelHeight, int totalSpace) const
|
||||
{
|
||||
PanelSizes newSizes (*this);
|
||||
|
||||
if (totalSpace <= 0)
|
||||
{
|
||||
newSizes.get(index).size = panelHeight;
|
||||
}
|
||||
else
|
||||
{
|
||||
auto num = sizes.size();
|
||||
auto minSize = getMinimumSize (0, num);
|
||||
totalSpace = jmax (totalSpace, minSize);
|
||||
|
||||
newSizes.get(index).setSize (panelHeight);
|
||||
newSizes.stretchRange (0, index, totalSpace - newSizes.getTotalSize (0, num), stretchLast);
|
||||
newSizes.stretchRange (index, num, totalSpace - newSizes.getTotalSize (0, num), stretchLast);
|
||||
newSizes = newSizes.fittedInto (totalSpace);
|
||||
}
|
||||
|
||||
return newSizes;
|
||||
}
|
||||
|
||||
private:
|
||||
enum ExpandMode
|
||||
{
|
||||
stretchAll,
|
||||
stretchFirst,
|
||||
stretchLast
|
||||
};
|
||||
|
||||
void growRangeFirst (int start, int end, int spaceDiff) noexcept
|
||||
{
|
||||
for (int attempts = 4; --attempts >= 0 && spaceDiff > 0;)
|
||||
for (int i = start; i < end && spaceDiff > 0; ++i)
|
||||
spaceDiff -= get (i).expand (spaceDiff);
|
||||
}
|
||||
|
||||
void growRangeLast (int start, int end, int spaceDiff) noexcept
|
||||
{
|
||||
for (int attempts = 4; --attempts >= 0 && spaceDiff > 0;)
|
||||
for (int i = end; --i >= start && spaceDiff > 0;)
|
||||
spaceDiff -= get (i).expand (spaceDiff);
|
||||
}
|
||||
|
||||
void growRangeAll (int start, int end, int spaceDiff) noexcept
|
||||
{
|
||||
Array<Panel*> expandableItems;
|
||||
|
||||
for (int i = start; i < end; ++i)
|
||||
if (get(i).canExpand() && ! get(i).isMinimised())
|
||||
expandableItems.add (& get(i));
|
||||
|
||||
for (int attempts = 4; --attempts >= 0 && spaceDiff > 0;)
|
||||
for (int i = expandableItems.size(); --i >= 0 && spaceDiff > 0;)
|
||||
spaceDiff -= expandableItems.getUnchecked(i)->expand (spaceDiff / (i + 1));
|
||||
|
||||
growRangeLast (start, end, spaceDiff);
|
||||
}
|
||||
|
||||
void shrinkRangeFirst (int start, int end, int spaceDiff) noexcept
|
||||
{
|
||||
for (int i = start; i < end && spaceDiff > 0; ++i)
|
||||
spaceDiff -= get(i).reduce (spaceDiff);
|
||||
}
|
||||
|
||||
void shrinkRangeLast (int start, int end, int spaceDiff) noexcept
|
||||
{
|
||||
for (int i = end; --i >= start && spaceDiff > 0;)
|
||||
spaceDiff -= get(i).reduce (spaceDiff);
|
||||
}
|
||||
|
||||
void stretchRange (int start, int end, int amountToAdd, ExpandMode expandMode) noexcept
|
||||
{
|
||||
if (end > start)
|
||||
{
|
||||
if (amountToAdd > 0)
|
||||
{
|
||||
if (expandMode == stretchAll) growRangeAll (start, end, amountToAdd);
|
||||
else if (expandMode == stretchFirst) growRangeFirst (start, end, amountToAdd);
|
||||
else if (expandMode == stretchLast) growRangeLast (start, end, amountToAdd);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (expandMode == stretchFirst) shrinkRangeFirst (start, end, -amountToAdd);
|
||||
else shrinkRangeLast (start, end, -amountToAdd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int getTotalSize (int start, int end) const noexcept
|
||||
{
|
||||
int tot = 0;
|
||||
while (start < end) tot += get (start++).size;
|
||||
return tot;
|
||||
}
|
||||
|
||||
int getMinimumSize (int start, int end) const noexcept
|
||||
{
|
||||
int tot = 0;
|
||||
while (start < end) tot += get (start++).minSize;
|
||||
return tot;
|
||||
}
|
||||
|
||||
int getMaximumSize (int start, int end) const noexcept
|
||||
{
|
||||
int tot = 0;
|
||||
|
||||
while (start < end)
|
||||
{
|
||||
auto mx = get (start++).maxSize;
|
||||
|
||||
if (mx > 0x100000)
|
||||
return mx;
|
||||
|
||||
tot += mx;
|
||||
}
|
||||
|
||||
return tot;
|
||||
}
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
class ConcertinaPanel::PanelHolder : public Component
|
||||
{
|
||||
public:
|
||||
PanelHolder (Component* comp, bool takeOwnership)
|
||||
: component (comp, takeOwnership)
|
||||
{
|
||||
setRepaintsOnMouseActivity (true);
|
||||
setWantsKeyboardFocus (false);
|
||||
addAndMakeVisible (comp);
|
||||
}
|
||||
|
||||
void paint (Graphics& g) override
|
||||
{
|
||||
if (customHeaderComponent == nullptr)
|
||||
{
|
||||
const Rectangle<int> area (getWidth(), getHeaderSize());
|
||||
g.reduceClipRegion (area);
|
||||
|
||||
getLookAndFeel().drawConcertinaPanelHeader (g, area, isMouseOver(), isMouseButtonDown(),
|
||||
getPanel(), *component);
|
||||
}
|
||||
}
|
||||
|
||||
void resized() override
|
||||
{
|
||||
auto bounds = getLocalBounds();
|
||||
auto headerBounds = bounds.removeFromTop (getHeaderSize());
|
||||
|
||||
if (customHeaderComponent != nullptr)
|
||||
customHeaderComponent->setBounds (headerBounds);
|
||||
|
||||
component->setBounds (bounds);
|
||||
}
|
||||
|
||||
void mouseDown (const MouseEvent&) override
|
||||
{
|
||||
mouseDownY = getY();
|
||||
dragStartSizes = getPanel().getFittedSizes();
|
||||
}
|
||||
|
||||
void mouseDrag (const MouseEvent& e) override
|
||||
{
|
||||
if (e.mouseWasDraggedSinceMouseDown())
|
||||
{
|
||||
auto& panel = getPanel();
|
||||
panel.setLayout (dragStartSizes.withMovedPanel (panel.holders.indexOf (this),
|
||||
mouseDownY + e.getDistanceFromDragStartY(),
|
||||
panel.getHeight()), false);
|
||||
}
|
||||
}
|
||||
|
||||
void mouseDoubleClick (const MouseEvent&) override
|
||||
{
|
||||
getPanel().panelHeaderDoubleClicked (component);
|
||||
}
|
||||
|
||||
void setCustomHeaderComponent (Component* headerComponent, bool shouldTakeOwnership)
|
||||
{
|
||||
customHeaderComponent.set (headerComponent, shouldTakeOwnership);
|
||||
|
||||
if (headerComponent != nullptr)
|
||||
{
|
||||
addAndMakeVisible (customHeaderComponent);
|
||||
customHeaderComponent->addMouseListener (this, false);
|
||||
}
|
||||
}
|
||||
|
||||
OptionalScopedPointer<Component> component;
|
||||
|
||||
private:
|
||||
PanelSizes dragStartSizes;
|
||||
int mouseDownY;
|
||||
OptionalScopedPointer<Component> customHeaderComponent;
|
||||
|
||||
int getHeaderSize() const noexcept
|
||||
{
|
||||
ConcertinaPanel& panel = getPanel();
|
||||
auto ourIndex = panel.holders.indexOf (this);
|
||||
return panel.currentSizes->get(ourIndex).minSize;
|
||||
}
|
||||
|
||||
ConcertinaPanel& getPanel() const
|
||||
{
|
||||
auto panel = dynamic_cast<ConcertinaPanel*> (getParentComponent());
|
||||
jassert (panel != nullptr);
|
||||
return *panel;
|
||||
}
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (PanelHolder)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
ConcertinaPanel::ConcertinaPanel()
|
||||
: currentSizes (new PanelSizes()),
|
||||
headerHeight (20)
|
||||
{
|
||||
}
|
||||
|
||||
ConcertinaPanel::~ConcertinaPanel() {}
|
||||
|
||||
int ConcertinaPanel::getNumPanels() const noexcept
|
||||
{
|
||||
return holders.size();
|
||||
}
|
||||
|
||||
Component* ConcertinaPanel::getPanel (int index) const noexcept
|
||||
{
|
||||
if (PanelHolder* h = holders[index])
|
||||
return h->component;
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void ConcertinaPanel::addPanel (int insertIndex, Component* component, bool takeOwnership)
|
||||
{
|
||||
jassert (component != nullptr); // can't use a null pointer here!
|
||||
jassert (indexOfComp (component) < 0); // You can't add the same component more than once!
|
||||
|
||||
auto holder = new PanelHolder (component, takeOwnership);
|
||||
holders.insert (insertIndex, holder);
|
||||
currentSizes->sizes.insert (insertIndex, PanelSizes::Panel (headerHeight, headerHeight, std::numeric_limits<int>::max()));
|
||||
addAndMakeVisible (holder);
|
||||
resized();
|
||||
}
|
||||
|
||||
void ConcertinaPanel::removePanel (Component* component)
|
||||
{
|
||||
auto index = indexOfComp (component);
|
||||
|
||||
if (index >= 0)
|
||||
{
|
||||
currentSizes->sizes.remove (index);
|
||||
holders.remove (index);
|
||||
resized();
|
||||
}
|
||||
}
|
||||
|
||||
bool ConcertinaPanel::setPanelSize (Component* panelComponent, int height, bool animate)
|
||||
{
|
||||
auto index = indexOfComp (panelComponent);
|
||||
jassert (index >= 0); // The specified component doesn't seem to have been added!
|
||||
|
||||
height += currentSizes->get(index).minSize;
|
||||
auto oldSize = currentSizes->get(index).size;
|
||||
setLayout (currentSizes->withResizedPanel (index, height, getHeight()), animate);
|
||||
return oldSize != currentSizes->get(index).size;
|
||||
}
|
||||
|
||||
bool ConcertinaPanel::expandPanelFully (Component* component, bool animate)
|
||||
{
|
||||
return setPanelSize (component, getHeight(), animate);
|
||||
}
|
||||
|
||||
void ConcertinaPanel::setMaximumPanelSize (Component* component, int maximumSize)
|
||||
{
|
||||
auto index = indexOfComp (component);
|
||||
jassert (index >= 0); // The specified component doesn't seem to have been added!
|
||||
|
||||
if (index >= 0)
|
||||
{
|
||||
currentSizes->get(index).maxSize = currentSizes->get(index).minSize + maximumSize;
|
||||
resized();
|
||||
}
|
||||
}
|
||||
|
||||
void ConcertinaPanel::setPanelHeaderSize (Component* component, int headerSize)
|
||||
{
|
||||
auto index = indexOfComp (component);
|
||||
jassert (index >= 0); // The specified component doesn't seem to have been added!
|
||||
|
||||
if (index >= 0)
|
||||
{
|
||||
auto oldMin = currentSizes->get (index).minSize;
|
||||
|
||||
currentSizes->get (index).minSize = headerSize;
|
||||
currentSizes->get (index).size += headerSize - oldMin;
|
||||
resized();
|
||||
}
|
||||
}
|
||||
|
||||
void ConcertinaPanel::setCustomPanelHeader (Component* component, Component* customComponent, bool takeOwnership)
|
||||
{
|
||||
OptionalScopedPointer<Component> optional (customComponent, takeOwnership);
|
||||
|
||||
auto index = indexOfComp (component);
|
||||
jassert (index >= 0); // The specified component doesn't seem to have been added!
|
||||
|
||||
if (index >= 0)
|
||||
holders.getUnchecked (index)->setCustomHeaderComponent (optional.release(), takeOwnership);
|
||||
}
|
||||
|
||||
void ConcertinaPanel::resized()
|
||||
{
|
||||
applyLayout (getFittedSizes(), false);
|
||||
}
|
||||
|
||||
int ConcertinaPanel::indexOfComp (Component* comp) const noexcept
|
||||
{
|
||||
for (int i = 0; i < holders.size(); ++i)
|
||||
if (holders.getUnchecked(i)->component == comp)
|
||||
return i;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
ConcertinaPanel::PanelSizes ConcertinaPanel::getFittedSizes() const
|
||||
{
|
||||
return currentSizes->fittedInto (getHeight());
|
||||
}
|
||||
|
||||
void ConcertinaPanel::applyLayout (const PanelSizes& sizes, bool animate)
|
||||
{
|
||||
if (! animate)
|
||||
animator.cancelAllAnimations (false);
|
||||
|
||||
const int animationDuration = 150;
|
||||
auto w = getWidth();
|
||||
int y = 0;
|
||||
|
||||
for (int i = 0; i < holders.size(); ++i)
|
||||
{
|
||||
PanelHolder& p = *holders.getUnchecked (i);
|
||||
|
||||
auto h = sizes.get (i).size;
|
||||
const Rectangle<int> pos (0, y, w, h);
|
||||
|
||||
if (animate)
|
||||
animator.animateComponent (&p, pos, 1.0f, animationDuration, false, 1.0, 1.0);
|
||||
else
|
||||
p.setBounds (pos);
|
||||
|
||||
y += h;
|
||||
}
|
||||
}
|
||||
|
||||
void ConcertinaPanel::setLayout (const PanelSizes& sizes, bool animate)
|
||||
{
|
||||
*currentSizes = sizes;
|
||||
applyLayout (getFittedSizes(), animate);
|
||||
}
|
||||
|
||||
void ConcertinaPanel::panelHeaderDoubleClicked (Component* component)
|
||||
{
|
||||
if (! expandPanelFully (component, true))
|
||||
setPanelSize (component, 0, true);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
std::unique_ptr<AccessibilityHandler> ConcertinaPanel::createAccessibilityHandler()
|
||||
{
|
||||
return std::make_unique<AccessibilityHandler> (*this, AccessibilityRole::group);
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
struct ConcertinaPanel::PanelSizes
|
||||
{
|
||||
struct Panel
|
||||
{
|
||||
Panel() = default;
|
||||
|
||||
Panel (int sz, int mn, int mx) noexcept
|
||||
: size (sz), minSize (mn), maxSize (mx) {}
|
||||
|
||||
int setSize (int newSize) noexcept
|
||||
{
|
||||
jassert (minSize <= maxSize);
|
||||
auto oldSize = size;
|
||||
size = jlimit (minSize, maxSize, newSize);
|
||||
return size - oldSize;
|
||||
}
|
||||
|
||||
int expand (int amount) noexcept
|
||||
{
|
||||
amount = jmin (amount, maxSize - size);
|
||||
size += amount;
|
||||
return amount;
|
||||
}
|
||||
|
||||
int reduce (int amount) noexcept
|
||||
{
|
||||
amount = jmin (amount, size - minSize);
|
||||
size -= amount;
|
||||
return amount;
|
||||
}
|
||||
|
||||
bool canExpand() const noexcept { return size < maxSize; }
|
||||
bool isMinimised() const noexcept { return size <= minSize; }
|
||||
|
||||
int size, minSize, maxSize;
|
||||
};
|
||||
|
||||
Array<Panel> sizes;
|
||||
|
||||
Panel& get (int index) noexcept { return sizes.getReference (index); }
|
||||
const Panel& get (int index) const noexcept { return sizes.getReference (index); }
|
||||
|
||||
PanelSizes withMovedPanel (int index, int targetPosition, int totalSpace) const
|
||||
{
|
||||
auto num = sizes.size();
|
||||
totalSpace = jmax (totalSpace, getMinimumSize (0, num));
|
||||
targetPosition = jmax (targetPosition, totalSpace - getMaximumSize (index, num));
|
||||
|
||||
PanelSizes newSizes (*this);
|
||||
newSizes.stretchRange (0, index, targetPosition - newSizes.getTotalSize (0, index), stretchLast);
|
||||
newSizes.stretchRange (index, num, totalSpace - newSizes.getTotalSize (0, index) - newSizes.getTotalSize (index, num), stretchFirst);
|
||||
return newSizes;
|
||||
}
|
||||
|
||||
PanelSizes fittedInto (int totalSpace) const
|
||||
{
|
||||
auto newSizes (*this);
|
||||
auto num = newSizes.sizes.size();
|
||||
totalSpace = jmax (totalSpace, getMinimumSize (0, num));
|
||||
newSizes.stretchRange (0, num, totalSpace - newSizes.getTotalSize (0, num), stretchAll);
|
||||
return newSizes;
|
||||
}
|
||||
|
||||
PanelSizes withResizedPanel (int index, int panelHeight, int totalSpace) const
|
||||
{
|
||||
PanelSizes newSizes (*this);
|
||||
|
||||
if (totalSpace <= 0)
|
||||
{
|
||||
newSizes.get(index).size = panelHeight;
|
||||
}
|
||||
else
|
||||
{
|
||||
auto num = sizes.size();
|
||||
auto minSize = getMinimumSize (0, num);
|
||||
totalSpace = jmax (totalSpace, minSize);
|
||||
|
||||
newSizes.get(index).setSize (panelHeight);
|
||||
newSizes.stretchRange (0, index, totalSpace - newSizes.getTotalSize (0, num), stretchLast);
|
||||
newSizes.stretchRange (index, num, totalSpace - newSizes.getTotalSize (0, num), stretchLast);
|
||||
newSizes = newSizes.fittedInto (totalSpace);
|
||||
}
|
||||
|
||||
return newSizes;
|
||||
}
|
||||
|
||||
private:
|
||||
enum ExpandMode
|
||||
{
|
||||
stretchAll,
|
||||
stretchFirst,
|
||||
stretchLast
|
||||
};
|
||||
|
||||
void growRangeFirst (int start, int end, int spaceDiff) noexcept
|
||||
{
|
||||
for (int attempts = 4; --attempts >= 0 && spaceDiff > 0;)
|
||||
for (int i = start; i < end && spaceDiff > 0; ++i)
|
||||
spaceDiff -= get (i).expand (spaceDiff);
|
||||
}
|
||||
|
||||
void growRangeLast (int start, int end, int spaceDiff) noexcept
|
||||
{
|
||||
for (int attempts = 4; --attempts >= 0 && spaceDiff > 0;)
|
||||
for (int i = end; --i >= start && spaceDiff > 0;)
|
||||
spaceDiff -= get (i).expand (spaceDiff);
|
||||
}
|
||||
|
||||
void growRangeAll (int start, int end, int spaceDiff) noexcept
|
||||
{
|
||||
Array<Panel*> expandableItems;
|
||||
|
||||
for (int i = start; i < end; ++i)
|
||||
if (get(i).canExpand() && ! get(i).isMinimised())
|
||||
expandableItems.add (& get(i));
|
||||
|
||||
for (int attempts = 4; --attempts >= 0 && spaceDiff > 0;)
|
||||
for (int i = expandableItems.size(); --i >= 0 && spaceDiff > 0;)
|
||||
spaceDiff -= expandableItems.getUnchecked(i)->expand (spaceDiff / (i + 1));
|
||||
|
||||
growRangeLast (start, end, spaceDiff);
|
||||
}
|
||||
|
||||
void shrinkRangeFirst (int start, int end, int spaceDiff) noexcept
|
||||
{
|
||||
for (int i = start; i < end && spaceDiff > 0; ++i)
|
||||
spaceDiff -= get(i).reduce (spaceDiff);
|
||||
}
|
||||
|
||||
void shrinkRangeLast (int start, int end, int spaceDiff) noexcept
|
||||
{
|
||||
for (int i = end; --i >= start && spaceDiff > 0;)
|
||||
spaceDiff -= get(i).reduce (spaceDiff);
|
||||
}
|
||||
|
||||
void stretchRange (int start, int end, int amountToAdd, ExpandMode expandMode) noexcept
|
||||
{
|
||||
if (end > start)
|
||||
{
|
||||
if (amountToAdd > 0)
|
||||
{
|
||||
if (expandMode == stretchAll) growRangeAll (start, end, amountToAdd);
|
||||
else if (expandMode == stretchFirst) growRangeFirst (start, end, amountToAdd);
|
||||
else if (expandMode == stretchLast) growRangeLast (start, end, amountToAdd);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (expandMode == stretchFirst) shrinkRangeFirst (start, end, -amountToAdd);
|
||||
else shrinkRangeLast (start, end, -amountToAdd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int getTotalSize (int start, int end) const noexcept
|
||||
{
|
||||
int tot = 0;
|
||||
while (start < end) tot += get (start++).size;
|
||||
return tot;
|
||||
}
|
||||
|
||||
int getMinimumSize (int start, int end) const noexcept
|
||||
{
|
||||
int tot = 0;
|
||||
while (start < end) tot += get (start++).minSize;
|
||||
return tot;
|
||||
}
|
||||
|
||||
int getMaximumSize (int start, int end) const noexcept
|
||||
{
|
||||
int tot = 0;
|
||||
|
||||
while (start < end)
|
||||
{
|
||||
auto mx = get (start++).maxSize;
|
||||
|
||||
if (mx > 0x100000)
|
||||
return mx;
|
||||
|
||||
tot += mx;
|
||||
}
|
||||
|
||||
return tot;
|
||||
}
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
class ConcertinaPanel::PanelHolder : public Component
|
||||
{
|
||||
public:
|
||||
PanelHolder (Component* comp, bool takeOwnership)
|
||||
: component (comp, takeOwnership)
|
||||
{
|
||||
setRepaintsOnMouseActivity (true);
|
||||
setWantsKeyboardFocus (false);
|
||||
addAndMakeVisible (comp);
|
||||
}
|
||||
|
||||
void paint (Graphics& g) override
|
||||
{
|
||||
if (customHeaderComponent == nullptr)
|
||||
{
|
||||
const Rectangle<int> area (getWidth(), getHeaderSize());
|
||||
g.reduceClipRegion (area);
|
||||
|
||||
getLookAndFeel().drawConcertinaPanelHeader (g, area, isMouseOver(), isMouseButtonDown(),
|
||||
getPanel(), *component);
|
||||
}
|
||||
}
|
||||
|
||||
void resized() override
|
||||
{
|
||||
auto bounds = getLocalBounds();
|
||||
auto headerBounds = bounds.removeFromTop (getHeaderSize());
|
||||
|
||||
if (customHeaderComponent != nullptr)
|
||||
customHeaderComponent->setBounds (headerBounds);
|
||||
|
||||
component->setBounds (bounds);
|
||||
}
|
||||
|
||||
void mouseDown (const MouseEvent&) override
|
||||
{
|
||||
mouseDownY = getY();
|
||||
dragStartSizes = getPanel().getFittedSizes();
|
||||
}
|
||||
|
||||
void mouseDrag (const MouseEvent& e) override
|
||||
{
|
||||
if (e.mouseWasDraggedSinceMouseDown())
|
||||
{
|
||||
auto& panel = getPanel();
|
||||
panel.setLayout (dragStartSizes.withMovedPanel (panel.holders.indexOf (this),
|
||||
mouseDownY + e.getDistanceFromDragStartY(),
|
||||
panel.getHeight()), false);
|
||||
}
|
||||
}
|
||||
|
||||
void mouseDoubleClick (const MouseEvent&) override
|
||||
{
|
||||
getPanel().panelHeaderDoubleClicked (component);
|
||||
}
|
||||
|
||||
void setCustomHeaderComponent (Component* headerComponent, bool shouldTakeOwnership)
|
||||
{
|
||||
customHeaderComponent.set (headerComponent, shouldTakeOwnership);
|
||||
|
||||
if (headerComponent != nullptr)
|
||||
{
|
||||
addAndMakeVisible (customHeaderComponent);
|
||||
customHeaderComponent->addMouseListener (this, false);
|
||||
}
|
||||
}
|
||||
|
||||
OptionalScopedPointer<Component> component;
|
||||
|
||||
private:
|
||||
PanelSizes dragStartSizes;
|
||||
int mouseDownY;
|
||||
OptionalScopedPointer<Component> customHeaderComponent;
|
||||
|
||||
int getHeaderSize() const noexcept
|
||||
{
|
||||
ConcertinaPanel& panel = getPanel();
|
||||
auto ourIndex = panel.holders.indexOf (this);
|
||||
return panel.currentSizes->get(ourIndex).minSize;
|
||||
}
|
||||
|
||||
ConcertinaPanel& getPanel() const
|
||||
{
|
||||
auto panel = dynamic_cast<ConcertinaPanel*> (getParentComponent());
|
||||
jassert (panel != nullptr);
|
||||
return *panel;
|
||||
}
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (PanelHolder)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
ConcertinaPanel::ConcertinaPanel()
|
||||
: currentSizes (new PanelSizes()),
|
||||
headerHeight (20)
|
||||
{
|
||||
}
|
||||
|
||||
ConcertinaPanel::~ConcertinaPanel() {}
|
||||
|
||||
int ConcertinaPanel::getNumPanels() const noexcept
|
||||
{
|
||||
return holders.size();
|
||||
}
|
||||
|
||||
Component* ConcertinaPanel::getPanel (int index) const noexcept
|
||||
{
|
||||
if (PanelHolder* h = holders[index])
|
||||
return h->component;
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void ConcertinaPanel::addPanel (int insertIndex, Component* component, bool takeOwnership)
|
||||
{
|
||||
jassert (component != nullptr); // can't use a null pointer here!
|
||||
jassert (indexOfComp (component) < 0); // You can't add the same component more than once!
|
||||
|
||||
auto holder = new PanelHolder (component, takeOwnership);
|
||||
holders.insert (insertIndex, holder);
|
||||
currentSizes->sizes.insert (insertIndex, PanelSizes::Panel (headerHeight, headerHeight, std::numeric_limits<int>::max()));
|
||||
addAndMakeVisible (holder);
|
||||
resized();
|
||||
}
|
||||
|
||||
void ConcertinaPanel::removePanel (Component* component)
|
||||
{
|
||||
auto index = indexOfComp (component);
|
||||
|
||||
if (index >= 0)
|
||||
{
|
||||
currentSizes->sizes.remove (index);
|
||||
holders.remove (index);
|
||||
resized();
|
||||
}
|
||||
}
|
||||
|
||||
bool ConcertinaPanel::setPanelSize (Component* panelComponent, int height, bool animate)
|
||||
{
|
||||
auto index = indexOfComp (panelComponent);
|
||||
jassert (index >= 0); // The specified component doesn't seem to have been added!
|
||||
|
||||
height += currentSizes->get(index).minSize;
|
||||
auto oldSize = currentSizes->get(index).size;
|
||||
setLayout (currentSizes->withResizedPanel (index, height, getHeight()), animate);
|
||||
return oldSize != currentSizes->get(index).size;
|
||||
}
|
||||
|
||||
bool ConcertinaPanel::expandPanelFully (Component* component, bool animate)
|
||||
{
|
||||
return setPanelSize (component, getHeight(), animate);
|
||||
}
|
||||
|
||||
void ConcertinaPanel::setMaximumPanelSize (Component* component, int maximumSize)
|
||||
{
|
||||
auto index = indexOfComp (component);
|
||||
jassert (index >= 0); // The specified component doesn't seem to have been added!
|
||||
|
||||
if (index >= 0)
|
||||
{
|
||||
currentSizes->get(index).maxSize = currentSizes->get(index).minSize + maximumSize;
|
||||
resized();
|
||||
}
|
||||
}
|
||||
|
||||
void ConcertinaPanel::setPanelHeaderSize (Component* component, int headerSize)
|
||||
{
|
||||
auto index = indexOfComp (component);
|
||||
jassert (index >= 0); // The specified component doesn't seem to have been added!
|
||||
|
||||
if (index >= 0)
|
||||
{
|
||||
auto oldMin = currentSizes->get (index).minSize;
|
||||
|
||||
currentSizes->get (index).minSize = headerSize;
|
||||
currentSizes->get (index).size += headerSize - oldMin;
|
||||
resized();
|
||||
}
|
||||
}
|
||||
|
||||
void ConcertinaPanel::setCustomPanelHeader (Component* component, Component* customComponent, bool takeOwnership)
|
||||
{
|
||||
OptionalScopedPointer<Component> optional (customComponent, takeOwnership);
|
||||
|
||||
auto index = indexOfComp (component);
|
||||
jassert (index >= 0); // The specified component doesn't seem to have been added!
|
||||
|
||||
if (index >= 0)
|
||||
holders.getUnchecked (index)->setCustomHeaderComponent (optional.release(), takeOwnership);
|
||||
}
|
||||
|
||||
void ConcertinaPanel::resized()
|
||||
{
|
||||
applyLayout (getFittedSizes(), false);
|
||||
}
|
||||
|
||||
int ConcertinaPanel::indexOfComp (Component* comp) const noexcept
|
||||
{
|
||||
for (int i = 0; i < holders.size(); ++i)
|
||||
if (holders.getUnchecked(i)->component == comp)
|
||||
return i;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
ConcertinaPanel::PanelSizes ConcertinaPanel::getFittedSizes() const
|
||||
{
|
||||
return currentSizes->fittedInto (getHeight());
|
||||
}
|
||||
|
||||
void ConcertinaPanel::applyLayout (const PanelSizes& sizes, bool animate)
|
||||
{
|
||||
if (! animate)
|
||||
animator.cancelAllAnimations (false);
|
||||
|
||||
const int animationDuration = 150;
|
||||
auto w = getWidth();
|
||||
int y = 0;
|
||||
|
||||
for (int i = 0; i < holders.size(); ++i)
|
||||
{
|
||||
PanelHolder& p = *holders.getUnchecked (i);
|
||||
|
||||
auto h = sizes.get (i).size;
|
||||
const Rectangle<int> pos (0, y, w, h);
|
||||
|
||||
if (animate)
|
||||
animator.animateComponent (&p, pos, 1.0f, animationDuration, false, 1.0, 1.0);
|
||||
else
|
||||
p.setBounds (pos);
|
||||
|
||||
y += h;
|
||||
}
|
||||
}
|
||||
|
||||
void ConcertinaPanel::setLayout (const PanelSizes& sizes, bool animate)
|
||||
{
|
||||
*currentSizes = sizes;
|
||||
applyLayout (getFittedSizes(), animate);
|
||||
}
|
||||
|
||||
void ConcertinaPanel::panelHeaderDoubleClicked (Component* component)
|
||||
{
|
||||
if (! expandPanelFully (component, true))
|
||||
setPanelSize (component, 0, true);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
std::unique_ptr<AccessibilityHandler> ConcertinaPanel::createAccessibilityHandler()
|
||||
{
|
||||
return std::make_unique<AccessibilityHandler> (*this, AccessibilityRole::group);
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
|
@ -1,142 +1,142 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A panel which holds a vertical stack of components which can be expanded
|
||||
and contracted.
|
||||
|
||||
Each section has its own header bar which can be dragged up and down
|
||||
to resize it, or double-clicked to fully expand that section.
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API ConcertinaPanel : public Component
|
||||
{
|
||||
public:
|
||||
/** Creates an empty concertina panel.
|
||||
You can call addPanel() to add some components to it.
|
||||
*/
|
||||
ConcertinaPanel();
|
||||
|
||||
/** Destructor. */
|
||||
~ConcertinaPanel() override;
|
||||
|
||||
/** Adds a component to the panel.
|
||||
@param insertIndex the index at which this component will be inserted, or
|
||||
-1 to append it to the end of the list.
|
||||
@param component the component that will be shown
|
||||
@param takeOwnership if true, then the ConcertinaPanel will take ownership
|
||||
of the content component, and will delete it later when
|
||||
it's no longer needed. If false, it won't delete it, and
|
||||
you must make sure it doesn't get deleted while in use.
|
||||
*/
|
||||
void addPanel (int insertIndex, Component* component, bool takeOwnership);
|
||||
|
||||
/** Removes one of the panels.
|
||||
If the takeOwnership flag was set when the panel was added, then
|
||||
this will also delete the component.
|
||||
*/
|
||||
void removePanel (Component* panelComponent);
|
||||
|
||||
/** Returns the number of panels.
|
||||
@see getPanel
|
||||
*/
|
||||
int getNumPanels() const noexcept;
|
||||
|
||||
/** Returns one of the panels.
|
||||
@see getNumPanels()
|
||||
*/
|
||||
Component* getPanel (int index) const noexcept;
|
||||
|
||||
/** Resizes one of the panels.
|
||||
The panelComponent must point to a valid panel component.
|
||||
If animate is true, the panels will be animated into their new positions;
|
||||
if false, they will just be immediately resized.
|
||||
*/
|
||||
bool setPanelSize (Component* panelComponent, int newHeight, bool animate);
|
||||
|
||||
/** Attempts to make one of the panels full-height.
|
||||
The panelComponent must point to a valid panel component.
|
||||
If this component has had a maximum size set, then it will be
|
||||
expanded to that size. Otherwise, it'll fill as much of the total
|
||||
space as possible.
|
||||
*/
|
||||
bool expandPanelFully (Component* panelComponent, bool animate);
|
||||
|
||||
/** Sets a maximum size for one of the panels. */
|
||||
void setMaximumPanelSize (Component* panelComponent, int maximumSize);
|
||||
|
||||
/** Sets the height of the header section for one of the panels. */
|
||||
void setPanelHeaderSize (Component* panelComponent, int headerSize);
|
||||
|
||||
/** Sets a custom header Component for one of the panels.
|
||||
|
||||
@param panelComponent the panel component to add the custom header to.
|
||||
@param customHeaderComponent the custom component to use for the panel header.
|
||||
This can be nullptr to clear the custom header component
|
||||
and just use the standard LookAndFeel panel.
|
||||
@param takeOwnership if true, then the PanelHolder will take ownership
|
||||
of the custom header component, and will delete it later when
|
||||
it's no longer needed. If false, it won't delete it, and
|
||||
you must make sure it doesn't get deleted while in use.
|
||||
*/
|
||||
void setCustomPanelHeader (Component* panelComponent, Component* customHeaderComponent, bool takeOwnership);
|
||||
|
||||
//==============================================================================
|
||||
/** This abstract base class is implemented by LookAndFeel classes. */
|
||||
struct JUCE_API LookAndFeelMethods
|
||||
{
|
||||
virtual ~LookAndFeelMethods() = default;
|
||||
|
||||
virtual void drawConcertinaPanelHeader (Graphics&, const Rectangle<int>& area,
|
||||
bool isMouseOver, bool isMouseDown,
|
||||
ConcertinaPanel&, Component&) = 0;
|
||||
};
|
||||
|
||||
private:
|
||||
std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override;
|
||||
void resized() override;
|
||||
|
||||
class PanelHolder;
|
||||
struct PanelSizes;
|
||||
std::unique_ptr<PanelSizes> currentSizes;
|
||||
OwnedArray<PanelHolder> holders;
|
||||
ComponentAnimator animator;
|
||||
int headerHeight;
|
||||
|
||||
int indexOfComp (Component*) const noexcept;
|
||||
PanelSizes getFittedSizes() const;
|
||||
void applyLayout (const PanelSizes&, bool animate);
|
||||
void setLayout (const PanelSizes&, bool animate);
|
||||
void panelHeaderDoubleClicked (Component*);
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ConcertinaPanel)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A panel which holds a vertical stack of components which can be expanded
|
||||
and contracted.
|
||||
|
||||
Each section has its own header bar which can be dragged up and down
|
||||
to resize it, or double-clicked to fully expand that section.
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API ConcertinaPanel : public Component
|
||||
{
|
||||
public:
|
||||
/** Creates an empty concertina panel.
|
||||
You can call addPanel() to add some components to it.
|
||||
*/
|
||||
ConcertinaPanel();
|
||||
|
||||
/** Destructor. */
|
||||
~ConcertinaPanel() override;
|
||||
|
||||
/** Adds a component to the panel.
|
||||
@param insertIndex the index at which this component will be inserted, or
|
||||
-1 to append it to the end of the list.
|
||||
@param component the component that will be shown
|
||||
@param takeOwnership if true, then the ConcertinaPanel will take ownership
|
||||
of the content component, and will delete it later when
|
||||
it's no longer needed. If false, it won't delete it, and
|
||||
you must make sure it doesn't get deleted while in use.
|
||||
*/
|
||||
void addPanel (int insertIndex, Component* component, bool takeOwnership);
|
||||
|
||||
/** Removes one of the panels.
|
||||
If the takeOwnership flag was set when the panel was added, then
|
||||
this will also delete the component.
|
||||
*/
|
||||
void removePanel (Component* panelComponent);
|
||||
|
||||
/** Returns the number of panels.
|
||||
@see getPanel
|
||||
*/
|
||||
int getNumPanels() const noexcept;
|
||||
|
||||
/** Returns one of the panels.
|
||||
@see getNumPanels()
|
||||
*/
|
||||
Component* getPanel (int index) const noexcept;
|
||||
|
||||
/** Resizes one of the panels.
|
||||
The panelComponent must point to a valid panel component.
|
||||
If animate is true, the panels will be animated into their new positions;
|
||||
if false, they will just be immediately resized.
|
||||
*/
|
||||
bool setPanelSize (Component* panelComponent, int newHeight, bool animate);
|
||||
|
||||
/** Attempts to make one of the panels full-height.
|
||||
The panelComponent must point to a valid panel component.
|
||||
If this component has had a maximum size set, then it will be
|
||||
expanded to that size. Otherwise, it'll fill as much of the total
|
||||
space as possible.
|
||||
*/
|
||||
bool expandPanelFully (Component* panelComponent, bool animate);
|
||||
|
||||
/** Sets a maximum size for one of the panels. */
|
||||
void setMaximumPanelSize (Component* panelComponent, int maximumSize);
|
||||
|
||||
/** Sets the height of the header section for one of the panels. */
|
||||
void setPanelHeaderSize (Component* panelComponent, int headerSize);
|
||||
|
||||
/** Sets a custom header Component for one of the panels.
|
||||
|
||||
@param panelComponent the panel component to add the custom header to.
|
||||
@param customHeaderComponent the custom component to use for the panel header.
|
||||
This can be nullptr to clear the custom header component
|
||||
and just use the standard LookAndFeel panel.
|
||||
@param takeOwnership if true, then the PanelHolder will take ownership
|
||||
of the custom header component, and will delete it later when
|
||||
it's no longer needed. If false, it won't delete it, and
|
||||
you must make sure it doesn't get deleted while in use.
|
||||
*/
|
||||
void setCustomPanelHeader (Component* panelComponent, Component* customHeaderComponent, bool takeOwnership);
|
||||
|
||||
//==============================================================================
|
||||
/** This abstract base class is implemented by LookAndFeel classes. */
|
||||
struct JUCE_API LookAndFeelMethods
|
||||
{
|
||||
virtual ~LookAndFeelMethods() = default;
|
||||
|
||||
virtual void drawConcertinaPanelHeader (Graphics&, const Rectangle<int>& area,
|
||||
bool isMouseOver, bool isMouseDown,
|
||||
ConcertinaPanel&, Component&) = 0;
|
||||
};
|
||||
|
||||
private:
|
||||
std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override;
|
||||
void resized() override;
|
||||
|
||||
class PanelHolder;
|
||||
struct PanelSizes;
|
||||
std::unique_ptr<PanelSizes> currentSizes;
|
||||
OwnedArray<PanelHolder> holders;
|
||||
ComponentAnimator animator;
|
||||
int headerHeight;
|
||||
|
||||
int indexOfComp (Component*) const noexcept;
|
||||
PanelSizes getFittedSizes() const;
|
||||
void applyLayout (const PanelSizes&, bool animate);
|
||||
void setLayout (const PanelSizes&, bool animate);
|
||||
void panelHeaderDoubleClicked (Component*);
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ConcertinaPanel)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,146 +1,143 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
/**
|
||||
Represents a FlexBox container, which contains and manages the layout of a set
|
||||
of FlexItem objects.
|
||||
|
||||
To use this class, set its parameters appropriately (you can search online for
|
||||
more help on exactly how the FlexBox protocol works!), then add your sub-items
|
||||
to the items array, and call performLayout() in the resized() function of your
|
||||
Component.
|
||||
|
||||
@see FlexItem
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API FlexBox final
|
||||
{
|
||||
public:
|
||||
/** Possible values for the flexDirection property. */
|
||||
enum class Direction
|
||||
{
|
||||
row, /**< Set the main axis direction from left to right. */
|
||||
rowReverse, /**< Set the main axis direction from right to left. */
|
||||
column, /**< Set the main axis direction from top to bottom. */
|
||||
columnReverse /**< Set the main axis direction from bottom to top. */
|
||||
};
|
||||
|
||||
/** Possible values for the flexWrap property. */
|
||||
enum class Wrap
|
||||
{
|
||||
noWrap, /**< Items are forced into a single line. */
|
||||
wrap, /**< Items are wrapped onto multiple lines from top to bottom. */
|
||||
wrapReverse /**< Items are wrapped onto multiple lines from bottom to top. */
|
||||
};
|
||||
|
||||
/** Possible values for the alignContent property. */
|
||||
enum class AlignContent
|
||||
{
|
||||
stretch, /**< Lines of items are stretched from start to end of the cross axis. */
|
||||
flexStart, /**< Lines of items are aligned towards the start of the cross axis. */
|
||||
flexEnd, /**< Lines of items are aligned towards the end of the cross axis. */
|
||||
center, /**< Lines of items are aligned towards the center of the cross axis. */
|
||||
spaceBetween, /**< Lines of items are evenly spaced along the cross axis with spaces between them. */
|
||||
spaceAround /**< Lines of items are evenly spaced along the cross axis with spaces around them. */
|
||||
};
|
||||
|
||||
/** Possible values for the alignItems property. */
|
||||
enum class AlignItems
|
||||
{
|
||||
stretch, /**< Items are stretched from start to end of the cross axis. */
|
||||
flexStart, /**< Items are aligned towards the start of the cross axis. */
|
||||
flexEnd, /**< Items are aligned towards the end of the cross axis. */
|
||||
center /**< Items are aligned towards the center of the cross axis. */
|
||||
};
|
||||
|
||||
/** Possible values for the justifyContent property. */
|
||||
enum class JustifyContent
|
||||
{
|
||||
flexStart, /**< Items are justified towards the start of the main axis. */
|
||||
flexEnd, /**< Items are justified towards the end of the main axis. */
|
||||
center, /**< Items are justified towards the center of the main axis. */
|
||||
spaceBetween, /**< Items are evenly spaced along the main axis with spaces between them. */
|
||||
spaceAround /**< Items are evenly spaced along the main axis with spaces around them. */
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** Creates an empty FlexBox container with default parameters. */
|
||||
FlexBox() noexcept;
|
||||
|
||||
/** Creates an empty FlexBox container with these parameters. */
|
||||
FlexBox (Direction, Wrap, AlignContent, AlignItems, JustifyContent) noexcept;
|
||||
|
||||
/** Creates an empty FlexBox container with the given content-justification mode. */
|
||||
FlexBox (JustifyContent) noexcept;
|
||||
|
||||
/** Destructor. */
|
||||
~FlexBox() noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** Lays-out the box's items within the given rectangle. */
|
||||
void performLayout (Rectangle<float> targetArea);
|
||||
|
||||
/** Lays-out the box's items within the given rectangle. */
|
||||
void performLayout (Rectangle<int> targetArea);
|
||||
|
||||
//==============================================================================
|
||||
/** Specifies how flex items are placed in the flex container, and defines the
|
||||
direction of the main axis.
|
||||
*/
|
||||
Direction flexDirection = Direction::row;
|
||||
|
||||
/** Specifies whether items are forced into a single line or can be wrapped onto multiple lines.
|
||||
If wrapping is allowed, this property also controls the direction in which lines are stacked.
|
||||
*/
|
||||
Wrap flexWrap = Wrap::noWrap;
|
||||
|
||||
/** Specifies how a flex container's lines are placed within the flex container when
|
||||
there is extra space on the cross-axis.
|
||||
This property has no effect on single line layouts.
|
||||
*/
|
||||
AlignContent alignContent = AlignContent::stretch;
|
||||
|
||||
/** Specifies the alignment of flex items along the cross-axis of each line. */
|
||||
AlignItems alignItems = AlignItems::stretch;
|
||||
|
||||
/** Defines how the container distributes space between and around items along the main-axis.
|
||||
The alignment is done after the lengths and auto margins are applied, so that if there is at
|
||||
least one flexible element, with flex-grow different from 0, it will have no effect as there
|
||||
won't be any available space.
|
||||
*/
|
||||
JustifyContent justifyContent = JustifyContent::flexStart;
|
||||
|
||||
/** The set of items to lay-out. */
|
||||
Array<FlexItem> items;
|
||||
|
||||
private:
|
||||
JUCE_LEAK_DETECTOR (FlexBox)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
/**
|
||||
Represents a FlexBox container, which contains and manages the layout of a set
|
||||
of FlexItem objects.
|
||||
|
||||
To use this class, set its parameters appropriately (you can search online for
|
||||
more help on exactly how the FlexBox protocol works!), then add your sub-items
|
||||
to the items array, and call performLayout() in the resized() function of your
|
||||
Component.
|
||||
|
||||
@see FlexItem
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API FlexBox final
|
||||
{
|
||||
public:
|
||||
/** Possible values for the flexDirection property. */
|
||||
enum class Direction
|
||||
{
|
||||
row, /**< Set the main axis direction from left to right. */
|
||||
rowReverse, /**< Set the main axis direction from right to left. */
|
||||
column, /**< Set the main axis direction from top to bottom. */
|
||||
columnReverse /**< Set the main axis direction from bottom to top. */
|
||||
};
|
||||
|
||||
/** Possible values for the flexWrap property. */
|
||||
enum class Wrap
|
||||
{
|
||||
noWrap, /**< Items are forced into a single line. */
|
||||
wrap, /**< Items are wrapped onto multiple lines from top to bottom. */
|
||||
wrapReverse /**< Items are wrapped onto multiple lines from bottom to top. */
|
||||
};
|
||||
|
||||
/** Possible values for the alignContent property. */
|
||||
enum class AlignContent
|
||||
{
|
||||
stretch, /**< Lines of items are stretched from start to end of the cross axis. */
|
||||
flexStart, /**< Lines of items are aligned towards the start of the cross axis. */
|
||||
flexEnd, /**< Lines of items are aligned towards the end of the cross axis. */
|
||||
center, /**< Lines of items are aligned towards the center of the cross axis. */
|
||||
spaceBetween, /**< Lines of items are evenly spaced along the cross axis with spaces between them. */
|
||||
spaceAround /**< Lines of items are evenly spaced along the cross axis with spaces around them. */
|
||||
};
|
||||
|
||||
/** Possible values for the alignItems property. */
|
||||
enum class AlignItems
|
||||
{
|
||||
stretch, /**< Items are stretched from start to end of the cross axis. */
|
||||
flexStart, /**< Items are aligned towards the start of the cross axis. */
|
||||
flexEnd, /**< Items are aligned towards the end of the cross axis. */
|
||||
center /**< Items are aligned towards the center of the cross axis. */
|
||||
};
|
||||
|
||||
/** Possible values for the justifyContent property. */
|
||||
enum class JustifyContent
|
||||
{
|
||||
flexStart, /**< Items are justified towards the start of the main axis. */
|
||||
flexEnd, /**< Items are justified towards the end of the main axis. */
|
||||
center, /**< Items are justified towards the center of the main axis. */
|
||||
spaceBetween, /**< Items are evenly spaced along the main axis with spaces between them. */
|
||||
spaceAround /**< Items are evenly spaced along the main axis with spaces around them. */
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** Creates an empty FlexBox container with default parameters. */
|
||||
FlexBox() noexcept = default;
|
||||
|
||||
/** Creates an empty FlexBox container with these parameters. */
|
||||
FlexBox (Direction, Wrap, AlignContent, AlignItems, JustifyContent) noexcept;
|
||||
|
||||
/** Creates an empty FlexBox container with the given content-justification mode. */
|
||||
FlexBox (JustifyContent) noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** Lays-out the box's items within the given rectangle. */
|
||||
void performLayout (Rectangle<float> targetArea);
|
||||
|
||||
/** Lays-out the box's items within the given rectangle. */
|
||||
void performLayout (Rectangle<int> targetArea);
|
||||
|
||||
//==============================================================================
|
||||
/** Specifies how flex items are placed in the flex container, and defines the
|
||||
direction of the main axis.
|
||||
*/
|
||||
Direction flexDirection = Direction::row;
|
||||
|
||||
/** Specifies whether items are forced into a single line or can be wrapped onto multiple lines.
|
||||
If wrapping is allowed, this property also controls the direction in which lines are stacked.
|
||||
*/
|
||||
Wrap flexWrap = Wrap::noWrap;
|
||||
|
||||
/** Specifies how a flex container's lines are placed within the flex container when
|
||||
there is extra space on the cross-axis.
|
||||
This property has no effect on single line layouts.
|
||||
*/
|
||||
AlignContent alignContent = AlignContent::stretch;
|
||||
|
||||
/** Specifies the alignment of flex items along the cross-axis of each line. */
|
||||
AlignItems alignItems = AlignItems::stretch;
|
||||
|
||||
/** Defines how the container distributes space between and around items along the main-axis.
|
||||
The alignment is done after the lengths and auto margins are applied, so that if there is at
|
||||
least one flexible element, with flex-grow different from 0, it will have no effect as there
|
||||
won't be any available space.
|
||||
*/
|
||||
JustifyContent justifyContent = JustifyContent::flexStart;
|
||||
|
||||
/** The set of items to lay-out. */
|
||||
Array<FlexItem> items;
|
||||
|
||||
private:
|
||||
JUCE_LEAK_DETECTOR (FlexBox)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
|
@ -1,177 +1,177 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
/**
|
||||
Describes the properties of an item inside a FlexBox container.
|
||||
|
||||
@see FlexBox
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API FlexItem final
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates an item with default parameters, and zero size. */
|
||||
FlexItem() noexcept;
|
||||
|
||||
/** Creates an item with the given size. */
|
||||
FlexItem (float width, float height) noexcept;
|
||||
|
||||
/** Creates an item with the given size and target Component. */
|
||||
FlexItem (float width, float height, Component& targetComponent) noexcept;
|
||||
|
||||
/** Creates an item that represents an embedded FlexBox with a given size. */
|
||||
FlexItem (float width, float height, FlexBox& flexBoxToControl) noexcept;
|
||||
|
||||
/** Creates an item with a given target Component. */
|
||||
FlexItem (Component& componentToControl) noexcept;
|
||||
|
||||
/** Creates an item that represents an embedded FlexBox. This class will not
|
||||
create a copy of the supplied flex box. You need to ensure that the
|
||||
life-time of flexBoxToControl is longer than the FlexItem. */
|
||||
FlexItem (FlexBox& flexBoxToControl) noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** The item's current bounds. */
|
||||
Rectangle<float> currentBounds;
|
||||
|
||||
/** If this is non-null, it represents a Component whose bounds are controlled by this item. */
|
||||
Component* associatedComponent = nullptr;
|
||||
|
||||
/** If this is non-null, it represents a FlexBox whose bounds are controlled by this item. */
|
||||
FlexBox* associatedFlexBox = nullptr;
|
||||
|
||||
/** Determines the order used to lay out items in their flex container.
|
||||
Elements are laid out in ascending order of thus order value. Elements with the same order value
|
||||
are laid out in the order in which they appear in the array.
|
||||
*/
|
||||
int order = 0;
|
||||
|
||||
/** Specifies the flex grow factor of this item.
|
||||
This indicates the amount of space inside the flex container the item should take up.
|
||||
*/
|
||||
float flexGrow = 0.0f;
|
||||
|
||||
/** Specifies the flex shrink factor of the item.
|
||||
This indicates the rate at which the item shrinks if there is insufficient space in
|
||||
the container.
|
||||
*/
|
||||
float flexShrink = 1.0f;
|
||||
|
||||
/** Specifies the flex-basis of the item.
|
||||
This is the initial main size of a flex item in the direction of flow. It determines the size
|
||||
of the content-box unless specified otherwise using box-sizing.
|
||||
*/
|
||||
float flexBasis = 0.0f;
|
||||
|
||||
/** Possible value for the alignSelf property */
|
||||
enum class AlignSelf
|
||||
{
|
||||
autoAlign, /**< Follows the FlexBox container's alignItems property. */
|
||||
flexStart, /**< Item is aligned towards the start of the cross axis. */
|
||||
flexEnd, /**< Item is aligned towards the end of the cross axis. */
|
||||
center, /**< Item is aligned towards the center of the cross axis. */
|
||||
stretch /**< Item is stretched from start to end of the cross axis. */
|
||||
};
|
||||
|
||||
/** This is the align-self property of the item.
|
||||
This determines the alignment of the item along the cross-axis (perpendicular to the direction
|
||||
of flow).
|
||||
*/
|
||||
AlignSelf alignSelf = AlignSelf::stretch;
|
||||
|
||||
//==============================================================================
|
||||
/** This constant can be used for sizes to indicate that 'auto' mode should be used. */
|
||||
static const int autoValue = -2;
|
||||
/** This constant can be used for sizes to indicate that no value has been set. */
|
||||
static const int notAssigned = -1;
|
||||
|
||||
float width = (float) notAssigned; /**< The item's width. */
|
||||
float minWidth = 0.0f; /**< The item's minimum width */
|
||||
float maxWidth = (float) notAssigned; /**< The item's maximum width */
|
||||
|
||||
float height = (float) notAssigned; /**< The item's height */
|
||||
float minHeight = 0.0f; /**< The item's minimum height */
|
||||
float maxHeight = (float) notAssigned; /**< The item's maximum height */
|
||||
|
||||
/** Represents a margin. */
|
||||
struct Margin final
|
||||
{
|
||||
Margin() noexcept; /**< Creates a margin of size zero. */
|
||||
Margin (float size) noexcept; /**< Creates a margin with this size on all sides. */
|
||||
Margin (float top, float right, float bottom, float left) noexcept; /**< Creates a margin with these sizes. */
|
||||
|
||||
float left; /**< Left margin size */
|
||||
float right; /**< Right margin size */
|
||||
float top; /**< Top margin size */
|
||||
float bottom; /**< Bottom margin size */
|
||||
};
|
||||
|
||||
/** The margin to leave around this item. */
|
||||
Margin margin;
|
||||
|
||||
//==============================================================================
|
||||
/** Returns a copy of this object with a new flex-grow value. */
|
||||
FlexItem withFlex (float newFlexGrow) const noexcept;
|
||||
|
||||
/** Returns a copy of this object with new flex-grow and flex-shrink values. */
|
||||
FlexItem withFlex (float newFlexGrow, float newFlexShrink) const noexcept;
|
||||
|
||||
/** Returns a copy of this object with new flex-grow, flex-shrink and flex-basis values. */
|
||||
FlexItem withFlex (float newFlexGrow, float newFlexShrink, float newFlexBasis) const noexcept;
|
||||
|
||||
/** Returns a copy of this object with a new width. */
|
||||
FlexItem withWidth (float newWidth) const noexcept;
|
||||
|
||||
/** Returns a copy of this object with a new minimum width. */
|
||||
FlexItem withMinWidth (float newMinWidth) const noexcept;
|
||||
|
||||
/** Returns a copy of this object with a new maximum width. */
|
||||
FlexItem withMaxWidth (float newMaxWidth) const noexcept;
|
||||
|
||||
/** Returns a copy of this object with a new height. */
|
||||
FlexItem withHeight (float newHeight) const noexcept;
|
||||
|
||||
/** Returns a copy of this object with a new minimum height. */
|
||||
FlexItem withMinHeight (float newMinHeight) const noexcept;
|
||||
|
||||
/** Returns a copy of this object with a new maximum height. */
|
||||
FlexItem withMaxHeight (float newMaxHeight) const noexcept;
|
||||
|
||||
/** Returns a copy of this object with a new margin. */
|
||||
FlexItem withMargin (Margin) const noexcept;
|
||||
|
||||
/** Returns a copy of this object with a new order. */
|
||||
FlexItem withOrder (int newOrder) const noexcept;
|
||||
|
||||
/** Returns a copy of this object with a new alignSelf value. */
|
||||
FlexItem withAlignSelf (AlignSelf newAlignSelf) const noexcept;
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
/**
|
||||
Describes the properties of an item inside a FlexBox container.
|
||||
|
||||
@see FlexBox
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API FlexItem final
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates an item with default parameters, and zero size. */
|
||||
FlexItem() noexcept;
|
||||
|
||||
/** Creates an item with the given size. */
|
||||
FlexItem (float width, float height) noexcept;
|
||||
|
||||
/** Creates an item with the given size and target Component. */
|
||||
FlexItem (float width, float height, Component& targetComponent) noexcept;
|
||||
|
||||
/** Creates an item that represents an embedded FlexBox with a given size. */
|
||||
FlexItem (float width, float height, FlexBox& flexBoxToControl) noexcept;
|
||||
|
||||
/** Creates an item with a given target Component. */
|
||||
FlexItem (Component& componentToControl) noexcept;
|
||||
|
||||
/** Creates an item that represents an embedded FlexBox. This class will not
|
||||
create a copy of the supplied flex box. You need to ensure that the
|
||||
life-time of flexBoxToControl is longer than the FlexItem. */
|
||||
FlexItem (FlexBox& flexBoxToControl) noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** The item's current bounds. */
|
||||
Rectangle<float> currentBounds;
|
||||
|
||||
/** If this is non-null, it represents a Component whose bounds are controlled by this item. */
|
||||
Component* associatedComponent = nullptr;
|
||||
|
||||
/** If this is non-null, it represents a FlexBox whose bounds are controlled by this item. */
|
||||
FlexBox* associatedFlexBox = nullptr;
|
||||
|
||||
/** Determines the order used to lay out items in their flex container.
|
||||
Elements are laid out in ascending order of thus order value. Elements with the same order value
|
||||
are laid out in the order in which they appear in the array.
|
||||
*/
|
||||
int order = 0;
|
||||
|
||||
/** Specifies the flex grow factor of this item.
|
||||
This indicates the amount of space inside the flex container the item should take up.
|
||||
*/
|
||||
float flexGrow = 0.0f;
|
||||
|
||||
/** Specifies the flex shrink factor of the item.
|
||||
This indicates the rate at which the item shrinks if there is insufficient space in
|
||||
the container.
|
||||
*/
|
||||
float flexShrink = 1.0f;
|
||||
|
||||
/** Specifies the flex-basis of the item.
|
||||
This is the initial main size of a flex item in the direction of flow. It determines the size
|
||||
of the content-box unless specified otherwise using box-sizing.
|
||||
*/
|
||||
float flexBasis = 0.0f;
|
||||
|
||||
/** Possible value for the alignSelf property */
|
||||
enum class AlignSelf
|
||||
{
|
||||
autoAlign, /**< Follows the FlexBox container's alignItems property. */
|
||||
flexStart, /**< Item is aligned towards the start of the cross axis. */
|
||||
flexEnd, /**< Item is aligned towards the end of the cross axis. */
|
||||
center, /**< Item is aligned towards the center of the cross axis. */
|
||||
stretch /**< Item is stretched from start to end of the cross axis. */
|
||||
};
|
||||
|
||||
/** This is the align-self property of the item.
|
||||
This determines the alignment of the item along the cross-axis (perpendicular to the direction
|
||||
of flow).
|
||||
*/
|
||||
AlignSelf alignSelf = AlignSelf::autoAlign;
|
||||
|
||||
//==============================================================================
|
||||
/** This constant can be used for sizes to indicate that 'auto' mode should be used. */
|
||||
static const int autoValue = -2;
|
||||
/** This constant can be used for sizes to indicate that no value has been set. */
|
||||
static const int notAssigned = -1;
|
||||
|
||||
float width = (float) notAssigned; /**< The item's width. */
|
||||
float minWidth = 0.0f; /**< The item's minimum width */
|
||||
float maxWidth = (float) notAssigned; /**< The item's maximum width */
|
||||
|
||||
float height = (float) notAssigned; /**< The item's height */
|
||||
float minHeight = 0.0f; /**< The item's minimum height */
|
||||
float maxHeight = (float) notAssigned; /**< The item's maximum height */
|
||||
|
||||
/** Represents a margin. */
|
||||
struct Margin final
|
||||
{
|
||||
Margin() noexcept; /**< Creates a margin of size zero. */
|
||||
Margin (float size) noexcept; /**< Creates a margin with this size on all sides. */
|
||||
Margin (float top, float right, float bottom, float left) noexcept; /**< Creates a margin with these sizes. */
|
||||
|
||||
float left; /**< Left margin size */
|
||||
float right; /**< Right margin size */
|
||||
float top; /**< Top margin size */
|
||||
float bottom; /**< Bottom margin size */
|
||||
};
|
||||
|
||||
/** The margin to leave around this item. */
|
||||
Margin margin;
|
||||
|
||||
//==============================================================================
|
||||
/** Returns a copy of this object with a new flex-grow value. */
|
||||
FlexItem withFlex (float newFlexGrow) const noexcept;
|
||||
|
||||
/** Returns a copy of this object with new flex-grow and flex-shrink values. */
|
||||
FlexItem withFlex (float newFlexGrow, float newFlexShrink) const noexcept;
|
||||
|
||||
/** Returns a copy of this object with new flex-grow, flex-shrink and flex-basis values. */
|
||||
FlexItem withFlex (float newFlexGrow, float newFlexShrink, float newFlexBasis) const noexcept;
|
||||
|
||||
/** Returns a copy of this object with a new width. */
|
||||
FlexItem withWidth (float newWidth) const noexcept;
|
||||
|
||||
/** Returns a copy of this object with a new minimum width. */
|
||||
FlexItem withMinWidth (float newMinWidth) const noexcept;
|
||||
|
||||
/** Returns a copy of this object with a new maximum width. */
|
||||
FlexItem withMaxWidth (float newMaxWidth) const noexcept;
|
||||
|
||||
/** Returns a copy of this object with a new height. */
|
||||
FlexItem withHeight (float newHeight) const noexcept;
|
||||
|
||||
/** Returns a copy of this object with a new minimum height. */
|
||||
FlexItem withMinHeight (float newMinHeight) const noexcept;
|
||||
|
||||
/** Returns a copy of this object with a new maximum height. */
|
||||
FlexItem withMaxHeight (float newMaxHeight) const noexcept;
|
||||
|
||||
/** Returns a copy of this object with a new margin. */
|
||||
FlexItem withMargin (Margin) const noexcept;
|
||||
|
||||
/** Returns a copy of this object with a new order. */
|
||||
FlexItem withOrder (int newOrder) const noexcept;
|
||||
|
||||
/** Returns a copy of this object with a new alignSelf value. */
|
||||
FlexItem withAlignSelf (AlignSelf newAlignSelf) const noexcept;
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
|
2709
deps/juce/modules/juce_gui_basics/layout/juce_Grid.cpp
vendored
2709
deps/juce/modules/juce_gui_basics/layout/juce_Grid.cpp
vendored
File diff suppressed because it is too large
Load Diff
458
deps/juce/modules/juce_gui_basics/layout/juce_Grid.h
vendored
458
deps/juce/modules/juce_gui_basics/layout/juce_Grid.h
vendored
@ -1,229 +1,229 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
/**
|
||||
Container that handles geometry for grid layouts (fixed columns and rows) using a set of declarative rules.
|
||||
|
||||
Implemented from the `CSS Grid Layout` specification as described at:
|
||||
https://css-tricks.com/snippets/css/complete-guide-grid/
|
||||
|
||||
@see GridItem
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API Grid final
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** A size in pixels */
|
||||
struct Px final
|
||||
{
|
||||
explicit Px (float p) : pixels (static_cast<long double>(p)) { /*sta (p >= 0.0f);*/ }
|
||||
explicit Px (int p) : pixels (static_cast<long double>(p)) { /*sta (p >= 0.0f);*/ }
|
||||
explicit constexpr Px (long double p) : pixels (p) {}
|
||||
explicit constexpr Px (unsigned long long p) : pixels (static_cast<long double>(p)) {}
|
||||
|
||||
long double pixels;
|
||||
};
|
||||
|
||||
/** A fractional ratio integer */
|
||||
struct Fr final
|
||||
{
|
||||
explicit Fr (int f) : fraction (static_cast<unsigned long long> (f)) {}
|
||||
explicit constexpr Fr (unsigned long long p) : fraction (p) {}
|
||||
|
||||
unsigned long long fraction;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** Represents a track. */
|
||||
struct TrackInfo final
|
||||
{
|
||||
/** Creates a track with auto dimension. */
|
||||
TrackInfo() noexcept;
|
||||
|
||||
TrackInfo (Px sizeInPixels) noexcept;
|
||||
TrackInfo (Fr fractionOfFreeSpace) noexcept;
|
||||
|
||||
TrackInfo (Px sizeInPixels, const String& endLineNameToUse) noexcept;
|
||||
TrackInfo (Fr fractionOfFreeSpace, const String& endLineNameToUse) noexcept;
|
||||
|
||||
TrackInfo (const String& startLineNameToUse, Px sizeInPixels) noexcept;
|
||||
TrackInfo (const String& startLineNameToUse, Fr fractionOfFreeSpace) noexcept;
|
||||
|
||||
TrackInfo (const String& startLineNameToUse, Px sizeInPixels, const String& endLineNameToUse) noexcept;
|
||||
TrackInfo (const String& startLineNameToUse, Fr fractionOfFreeSpace, const String& endLineNameToUse) noexcept;
|
||||
|
||||
bool isAuto() const noexcept { return hasKeyword; }
|
||||
bool isFractional() const noexcept { return isFraction; }
|
||||
bool isPixels() const noexcept { return ! isFraction; }
|
||||
const String& getStartLineName() const noexcept { return startLineName; }
|
||||
const String& getEndLineName() const noexcept { return endLineName; }
|
||||
|
||||
/** Get the track's size - which might mean an absolute pixels value or a fractional ratio. */
|
||||
float getSize() const noexcept { return size; }
|
||||
|
||||
private:
|
||||
friend class Grid;
|
||||
float getAbsoluteSize (float relativeFractionalUnit) const;
|
||||
|
||||
float size = 0; // Either a fraction or an absolute size in pixels
|
||||
bool isFraction = false;
|
||||
bool hasKeyword = false;
|
||||
|
||||
String startLineName, endLineName;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** Possible values for the justifyItems property. */
|
||||
enum class JustifyItems : int
|
||||
{
|
||||
start = 0, /**< Content inside the item is justified towards the left. */
|
||||
end, /**< Content inside the item is justified towards the right. */
|
||||
center, /**< Content inside the item is justified towards the center. */
|
||||
stretch /**< Content inside the item is stretched from left to right. */
|
||||
};
|
||||
|
||||
/** Possible values for the alignItems property. */
|
||||
enum class AlignItems : int
|
||||
{
|
||||
start = 0, /**< Content inside the item is aligned towards the top. */
|
||||
end, /**< Content inside the item is aligned towards the bottom. */
|
||||
center, /**< Content inside the item is aligned towards the center. */
|
||||
stretch /**< Content inside the item is stretched from top to bottom. */
|
||||
};
|
||||
|
||||
/** Possible values for the justifyContent property. */
|
||||
enum class JustifyContent
|
||||
{
|
||||
start, /**< Items are justified towards the left of the container. */
|
||||
end, /**< Items are justified towards the right of the container. */
|
||||
center, /**< Items are justified towards the center of the container. */
|
||||
stretch, /**< Items are stretched from left to right of the container. */
|
||||
spaceAround, /**< Items are evenly spaced along the row with spaces between them. */
|
||||
spaceBetween, /**< Items are evenly spaced along the row with spaces around them. */
|
||||
spaceEvenly /**< Items are evenly spaced along the row with even amount of spaces between them. */
|
||||
};
|
||||
|
||||
/** Possible values for the alignContent property. */
|
||||
enum class AlignContent
|
||||
{
|
||||
start, /**< Items are aligned towards the top of the container. */
|
||||
end, /**< Items are aligned towards the bottom of the container. */
|
||||
center, /**< Items are aligned towards the center of the container. */
|
||||
stretch, /**< Items are stretched from top to bottom of the container. */
|
||||
spaceAround, /**< Items are evenly spaced along the column with spaces between them. */
|
||||
spaceBetween, /**< Items are evenly spaced along the column with spaces around them. */
|
||||
spaceEvenly /**< Items are evenly spaced along the column with even amount of spaces between them. */
|
||||
};
|
||||
|
||||
/** Possible values for the autoFlow property. */
|
||||
enum class AutoFlow
|
||||
{
|
||||
row, /**< Fills the grid by adding rows of items. */
|
||||
column, /**< Fills the grid by adding columns of items. */
|
||||
rowDense, /**< Fills the grid by adding rows of items and attempts to fill in gaps. */
|
||||
columnDense /**< Fills the grid by adding columns of items and attempts to fill in gaps. */
|
||||
};
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/** Creates an empty Grid container with default parameters. */
|
||||
Grid() = default;
|
||||
|
||||
/** Destructor */
|
||||
~Grid() noexcept = default;
|
||||
|
||||
//==============================================================================
|
||||
/** Specifies the alignment of content inside the items along the rows. */
|
||||
JustifyItems justifyItems = JustifyItems::stretch;
|
||||
|
||||
/** Specifies the alignment of content inside the items along the columns. */
|
||||
AlignItems alignItems = AlignItems::stretch;
|
||||
|
||||
/** Specifies the alignment of items along the rows. */
|
||||
JustifyContent justifyContent = JustifyContent::stretch;
|
||||
|
||||
/** Specifies the alignment of items along the columns. */
|
||||
AlignContent alignContent = AlignContent::stretch;
|
||||
|
||||
/** Specifies how the auto-placement algorithm places items. */
|
||||
AutoFlow autoFlow = AutoFlow::row;
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/** The set of column tracks to lay out. */
|
||||
Array<TrackInfo> templateColumns;
|
||||
|
||||
/** The set of row tracks to lay out. */
|
||||
Array<TrackInfo> templateRows;
|
||||
|
||||
/** Template areas */
|
||||
StringArray templateAreas;
|
||||
|
||||
/** The row track for auto dimension. */
|
||||
TrackInfo autoRows;
|
||||
|
||||
/** The column track for auto dimension. */
|
||||
TrackInfo autoColumns;
|
||||
|
||||
/** The gap in pixels between columns. */
|
||||
Px columnGap { 0 };
|
||||
/** The gap in pixels between rows. */
|
||||
Px rowGap { 0 };
|
||||
|
||||
/** Sets the gap between rows and columns in pixels. */
|
||||
void setGap (Px sizeInPixels) noexcept { rowGap = columnGap = sizeInPixels; }
|
||||
|
||||
//==============================================================================
|
||||
/** The set of items to lay-out. */
|
||||
Array<GridItem> items;
|
||||
|
||||
//==============================================================================
|
||||
/** Lays-out the grid's items within the given rectangle. */
|
||||
void performLayout (Rectangle<int>);
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the number of columns. */
|
||||
int getNumberOfColumns() const noexcept { return templateColumns.size(); }
|
||||
/** Returns the number of rows. */
|
||||
int getNumberOfRows() const noexcept { return templateRows.size(); }
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
struct SizeCalculation;
|
||||
struct PlacementHelpers;
|
||||
struct AutoPlacement;
|
||||
struct BoxAlignment;
|
||||
};
|
||||
|
||||
constexpr Grid::Px operator"" _px (long double px) { return Grid::Px { px }; }
|
||||
constexpr Grid::Px operator"" _px (unsigned long long px) { return Grid::Px { px }; }
|
||||
constexpr Grid::Fr operator"" _fr (unsigned long long fr) { return Grid::Fr { fr }; }
|
||||
|
||||
} // namespace juce
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
/**
|
||||
Container that handles geometry for grid layouts (fixed columns and rows) using a set of declarative rules.
|
||||
|
||||
Implemented from the `CSS Grid Layout` specification as described at:
|
||||
https://css-tricks.com/snippets/css/complete-guide-grid/
|
||||
|
||||
@see GridItem
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API Grid final
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** A size in pixels */
|
||||
struct Px final
|
||||
{
|
||||
explicit Px (float p) : pixels (static_cast<long double>(p)) { /*sta (p >= 0.0f);*/ }
|
||||
explicit Px (int p) : pixels (static_cast<long double>(p)) { /*sta (p >= 0.0f);*/ }
|
||||
explicit constexpr Px (long double p) : pixels (p) {}
|
||||
explicit constexpr Px (unsigned long long p) : pixels (static_cast<long double>(p)) {}
|
||||
|
||||
long double pixels;
|
||||
};
|
||||
|
||||
/** A fractional ratio integer */
|
||||
struct Fr final
|
||||
{
|
||||
explicit Fr (int f) : fraction (static_cast<unsigned long long> (f)) {}
|
||||
explicit constexpr Fr (unsigned long long p) : fraction (p) {}
|
||||
|
||||
unsigned long long fraction;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** Represents a track. */
|
||||
struct TrackInfo final
|
||||
{
|
||||
/** Creates a track with auto dimension. */
|
||||
TrackInfo() noexcept;
|
||||
|
||||
TrackInfo (Px sizeInPixels) noexcept;
|
||||
TrackInfo (Fr fractionOfFreeSpace) noexcept;
|
||||
|
||||
TrackInfo (Px sizeInPixels, const String& endLineNameToUse) noexcept;
|
||||
TrackInfo (Fr fractionOfFreeSpace, const String& endLineNameToUse) noexcept;
|
||||
|
||||
TrackInfo (const String& startLineNameToUse, Px sizeInPixels) noexcept;
|
||||
TrackInfo (const String& startLineNameToUse, Fr fractionOfFreeSpace) noexcept;
|
||||
|
||||
TrackInfo (const String& startLineNameToUse, Px sizeInPixels, const String& endLineNameToUse) noexcept;
|
||||
TrackInfo (const String& startLineNameToUse, Fr fractionOfFreeSpace, const String& endLineNameToUse) noexcept;
|
||||
|
||||
bool isAuto() const noexcept { return hasKeyword; }
|
||||
bool isFractional() const noexcept { return isFraction; }
|
||||
bool isPixels() const noexcept { return ! isFraction; }
|
||||
const String& getStartLineName() const noexcept { return startLineName; }
|
||||
const String& getEndLineName() const noexcept { return endLineName; }
|
||||
|
||||
/** Get the track's size - which might mean an absolute pixels value or a fractional ratio. */
|
||||
float getSize() const noexcept { return size; }
|
||||
|
||||
private:
|
||||
friend class Grid;
|
||||
float getAbsoluteSize (float relativeFractionalUnit) const;
|
||||
|
||||
float size = 0; // Either a fraction or an absolute size in pixels
|
||||
bool isFraction = false;
|
||||
bool hasKeyword = false;
|
||||
|
||||
String startLineName, endLineName;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** Possible values for the justifyItems property. */
|
||||
enum class JustifyItems : int
|
||||
{
|
||||
start = 0, /**< Content inside the item is justified towards the left. */
|
||||
end, /**< Content inside the item is justified towards the right. */
|
||||
center, /**< Content inside the item is justified towards the center. */
|
||||
stretch /**< Content inside the item is stretched from left to right. */
|
||||
};
|
||||
|
||||
/** Possible values for the alignItems property. */
|
||||
enum class AlignItems : int
|
||||
{
|
||||
start = 0, /**< Content inside the item is aligned towards the top. */
|
||||
end, /**< Content inside the item is aligned towards the bottom. */
|
||||
center, /**< Content inside the item is aligned towards the center. */
|
||||
stretch /**< Content inside the item is stretched from top to bottom. */
|
||||
};
|
||||
|
||||
/** Possible values for the justifyContent property. */
|
||||
enum class JustifyContent
|
||||
{
|
||||
start, /**< Items are justified towards the left of the container. */
|
||||
end, /**< Items are justified towards the right of the container. */
|
||||
center, /**< Items are justified towards the center of the container. */
|
||||
stretch, /**< Items are stretched from left to right of the container. */
|
||||
spaceAround, /**< Items are evenly spaced along the row with spaces between them. */
|
||||
spaceBetween, /**< Items are evenly spaced along the row with spaces around them. */
|
||||
spaceEvenly /**< Items are evenly spaced along the row with even amount of spaces between them. */
|
||||
};
|
||||
|
||||
/** Possible values for the alignContent property. */
|
||||
enum class AlignContent
|
||||
{
|
||||
start, /**< Items are aligned towards the top of the container. */
|
||||
end, /**< Items are aligned towards the bottom of the container. */
|
||||
center, /**< Items are aligned towards the center of the container. */
|
||||
stretch, /**< Items are stretched from top to bottom of the container. */
|
||||
spaceAround, /**< Items are evenly spaced along the column with spaces between them. */
|
||||
spaceBetween, /**< Items are evenly spaced along the column with spaces around them. */
|
||||
spaceEvenly /**< Items are evenly spaced along the column with even amount of spaces between them. */
|
||||
};
|
||||
|
||||
/** Possible values for the autoFlow property. */
|
||||
enum class AutoFlow
|
||||
{
|
||||
row, /**< Fills the grid by adding rows of items. */
|
||||
column, /**< Fills the grid by adding columns of items. */
|
||||
rowDense, /**< Fills the grid by adding rows of items and attempts to fill in gaps. */
|
||||
columnDense /**< Fills the grid by adding columns of items and attempts to fill in gaps. */
|
||||
};
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/** Creates an empty Grid container with default parameters. */
|
||||
Grid() = default;
|
||||
|
||||
/** Destructor */
|
||||
~Grid() noexcept = default;
|
||||
|
||||
//==============================================================================
|
||||
/** Specifies the alignment of content inside the items along the rows. */
|
||||
JustifyItems justifyItems = JustifyItems::stretch;
|
||||
|
||||
/** Specifies the alignment of content inside the items along the columns. */
|
||||
AlignItems alignItems = AlignItems::stretch;
|
||||
|
||||
/** Specifies the alignment of items along the rows. */
|
||||
JustifyContent justifyContent = JustifyContent::stretch;
|
||||
|
||||
/** Specifies the alignment of items along the columns. */
|
||||
AlignContent alignContent = AlignContent::stretch;
|
||||
|
||||
/** Specifies how the auto-placement algorithm places items. */
|
||||
AutoFlow autoFlow = AutoFlow::row;
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/** The set of column tracks to lay out. */
|
||||
Array<TrackInfo> templateColumns;
|
||||
|
||||
/** The set of row tracks to lay out. */
|
||||
Array<TrackInfo> templateRows;
|
||||
|
||||
/** Template areas */
|
||||
StringArray templateAreas;
|
||||
|
||||
/** The row track for auto dimension. */
|
||||
TrackInfo autoRows;
|
||||
|
||||
/** The column track for auto dimension. */
|
||||
TrackInfo autoColumns;
|
||||
|
||||
/** The gap in pixels between columns. */
|
||||
Px columnGap { 0 };
|
||||
/** The gap in pixels between rows. */
|
||||
Px rowGap { 0 };
|
||||
|
||||
/** Sets the gap between rows and columns in pixels. */
|
||||
void setGap (Px sizeInPixels) noexcept { rowGap = columnGap = sizeInPixels; }
|
||||
|
||||
//==============================================================================
|
||||
/** The set of items to lay-out. */
|
||||
Array<GridItem> items;
|
||||
|
||||
//==============================================================================
|
||||
/** Lays-out the grid's items within the given rectangle. */
|
||||
void performLayout (Rectangle<int>);
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the number of columns. */
|
||||
int getNumberOfColumns() const noexcept { return templateColumns.size(); }
|
||||
/** Returns the number of rows. */
|
||||
int getNumberOfRows() const noexcept { return templateRows.size(); }
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
struct SizeCalculation;
|
||||
struct PlacementHelpers;
|
||||
struct AutoPlacement;
|
||||
struct BoxAlignment;
|
||||
};
|
||||
|
||||
constexpr Grid::Px operator"" _px (long double px) { return Grid::Px { px }; }
|
||||
constexpr Grid::Px operator"" _px (unsigned long long px) { return Grid::Px { px }; }
|
||||
constexpr Grid::Fr operator"" _fr (unsigned long long fr) { return Grid::Fr { fr }; }
|
||||
|
||||
} // namespace juce
|
||||
|
@ -1,181 +1,179 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
GridItem::Property::Property() noexcept : isAuto (true)
|
||||
{
|
||||
}
|
||||
|
||||
GridItem::Property::Property (GridItem::Keyword keyword) noexcept : isAuto (keyword == GridItem::Keyword::autoValue)
|
||||
{
|
||||
jassert (keyword == GridItem::Keyword::autoValue);
|
||||
}
|
||||
|
||||
GridItem::Property::Property (const char* lineNameToUse) noexcept : GridItem::Property (String (lineNameToUse))
|
||||
{
|
||||
}
|
||||
|
||||
GridItem::Property::Property (const String& lineNameToUse) noexcept : name (lineNameToUse), number (1)
|
||||
{
|
||||
}
|
||||
|
||||
GridItem::Property::Property (int numberToUse) noexcept : number (numberToUse)
|
||||
{
|
||||
}
|
||||
|
||||
GridItem::Property::Property (int numberToUse, const String& lineNameToUse) noexcept
|
||||
: name (lineNameToUse), number (numberToUse)
|
||||
{
|
||||
}
|
||||
|
||||
GridItem::Property::Property (Span spanToUse) noexcept
|
||||
: name (spanToUse.name), number (spanToUse.number), isSpan (true)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
//==============================================================================
|
||||
GridItem::Margin::Margin() noexcept : left(), right(), top(), bottom() {}
|
||||
|
||||
GridItem::Margin::Margin (int v) noexcept : GridItem::Margin::Margin (static_cast<float> (v)) {}
|
||||
|
||||
GridItem::Margin::Margin (float v) noexcept : left (v), right (v), top (v), bottom (v) {}
|
||||
|
||||
GridItem::Margin::Margin (float t, float r, float b, float l) noexcept : left (l), right (r), top (t), bottom (b) {}
|
||||
|
||||
//==============================================================================
|
||||
GridItem::GridItem() noexcept {}
|
||||
GridItem::~GridItem() noexcept {}
|
||||
|
||||
GridItem::GridItem (Component& componentToUse) noexcept : associatedComponent (&componentToUse) {}
|
||||
GridItem::GridItem (Component* componentToUse) noexcept : associatedComponent (componentToUse) {}
|
||||
|
||||
void GridItem::setArea (Property rowStart, Property columnStart, Property rowEnd, Property columnEnd)
|
||||
{
|
||||
column.start = columnStart;
|
||||
column.end = columnEnd;
|
||||
row.start = rowStart;
|
||||
row.end = rowEnd;
|
||||
}
|
||||
|
||||
void GridItem::setArea (Property rowStart, Property columnStart)
|
||||
{
|
||||
column.start = columnStart;
|
||||
row.start = rowStart;
|
||||
}
|
||||
|
||||
void GridItem::setArea (const String& areaName)
|
||||
{
|
||||
area = areaName;
|
||||
}
|
||||
|
||||
GridItem GridItem::withArea (Property rowStart, Property columnStart, Property rowEnd, Property columnEnd) const noexcept
|
||||
{
|
||||
auto gi = *this;
|
||||
gi.setArea (rowStart, columnStart, rowEnd, columnEnd);
|
||||
return gi;
|
||||
}
|
||||
|
||||
GridItem GridItem::withArea (Property rowStart, Property columnStart) const noexcept
|
||||
{
|
||||
auto gi = *this;
|
||||
gi.setArea (rowStart, columnStart);
|
||||
return gi;
|
||||
}
|
||||
|
||||
GridItem GridItem::withArea (const String& areaName) const noexcept
|
||||
{
|
||||
auto gi = *this;
|
||||
gi.setArea (areaName);
|
||||
return gi;
|
||||
}
|
||||
|
||||
GridItem GridItem::withRow (StartAndEndProperty newRow) const noexcept
|
||||
{
|
||||
auto gi = *this;
|
||||
gi.row = newRow;
|
||||
return gi;
|
||||
}
|
||||
|
||||
GridItem GridItem::withColumn (StartAndEndProperty newColumn) const noexcept
|
||||
{
|
||||
auto gi = *this;
|
||||
gi.column = newColumn;
|
||||
return gi;
|
||||
}
|
||||
|
||||
GridItem GridItem::withAlignSelf (AlignSelf newAlignSelf) const noexcept
|
||||
{
|
||||
auto gi = *this;
|
||||
gi.alignSelf = newAlignSelf;
|
||||
return gi;
|
||||
}
|
||||
|
||||
GridItem GridItem::withJustifySelf (JustifySelf newJustifySelf) const noexcept
|
||||
{
|
||||
auto gi = *this;
|
||||
gi.justifySelf = newJustifySelf;
|
||||
return gi;
|
||||
}
|
||||
|
||||
GridItem GridItem::withWidth (float newWidth) const noexcept
|
||||
{
|
||||
auto gi = *this;
|
||||
gi.width = newWidth;
|
||||
return gi;
|
||||
}
|
||||
|
||||
GridItem GridItem::withHeight (float newHeight) const noexcept
|
||||
{
|
||||
auto gi = *this;
|
||||
gi.height = newHeight;
|
||||
return gi;
|
||||
}
|
||||
|
||||
GridItem GridItem::withSize (float newWidth, float newHeight) const noexcept
|
||||
{
|
||||
auto gi = *this;
|
||||
gi.width = newWidth;
|
||||
gi.height = newHeight;
|
||||
return gi;
|
||||
}
|
||||
|
||||
GridItem GridItem::withMargin (Margin newHeight) const noexcept
|
||||
{
|
||||
auto gi = *this;
|
||||
gi.margin = newHeight;
|
||||
return gi;
|
||||
}
|
||||
|
||||
GridItem GridItem::withOrder (int newOrder) const noexcept
|
||||
{
|
||||
auto gi = *this;
|
||||
gi.order = newOrder;
|
||||
return gi;
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
GridItem::Property::Property() noexcept : isAuto (true)
|
||||
{
|
||||
}
|
||||
|
||||
GridItem::Property::Property (GridItem::Keyword keyword) noexcept : isAuto (keyword == GridItem::Keyword::autoValue)
|
||||
{
|
||||
jassert (keyword == GridItem::Keyword::autoValue);
|
||||
}
|
||||
|
||||
GridItem::Property::Property (const char* lineNameToUse) noexcept : GridItem::Property (String (lineNameToUse))
|
||||
{
|
||||
}
|
||||
|
||||
GridItem::Property::Property (const String& lineNameToUse) noexcept : name (lineNameToUse), number (1)
|
||||
{
|
||||
}
|
||||
|
||||
GridItem::Property::Property (int numberToUse) noexcept : number (numberToUse)
|
||||
{
|
||||
}
|
||||
|
||||
GridItem::Property::Property (int numberToUse, const String& lineNameToUse) noexcept
|
||||
: name (lineNameToUse), number (numberToUse)
|
||||
{
|
||||
}
|
||||
|
||||
GridItem::Property::Property (Span spanToUse) noexcept
|
||||
: name (spanToUse.name), number (spanToUse.number), isSpan (true)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
//==============================================================================
|
||||
GridItem::Margin::Margin() noexcept : left(), right(), top(), bottom() {}
|
||||
|
||||
GridItem::Margin::Margin (int v) noexcept : GridItem::Margin::Margin (static_cast<float> (v)) {}
|
||||
|
||||
GridItem::Margin::Margin (float v) noexcept : left (v), right (v), top (v), bottom (v) {}
|
||||
|
||||
GridItem::Margin::Margin (float t, float r, float b, float l) noexcept : left (l), right (r), top (t), bottom (b) {}
|
||||
|
||||
//==============================================================================
|
||||
GridItem::GridItem() noexcept = default;
|
||||
GridItem::GridItem (Component& componentToUse) noexcept : associatedComponent (&componentToUse) {}
|
||||
GridItem::GridItem (Component* componentToUse) noexcept : associatedComponent (componentToUse) {}
|
||||
|
||||
void GridItem::setArea (Property rowStart, Property columnStart, Property rowEnd, Property columnEnd)
|
||||
{
|
||||
column.start = columnStart;
|
||||
column.end = columnEnd;
|
||||
row.start = rowStart;
|
||||
row.end = rowEnd;
|
||||
}
|
||||
|
||||
void GridItem::setArea (Property rowStart, Property columnStart)
|
||||
{
|
||||
column.start = columnStart;
|
||||
row.start = rowStart;
|
||||
}
|
||||
|
||||
void GridItem::setArea (const String& areaName)
|
||||
{
|
||||
area = areaName;
|
||||
}
|
||||
|
||||
GridItem GridItem::withArea (Property rowStart, Property columnStart, Property rowEnd, Property columnEnd) const noexcept
|
||||
{
|
||||
auto gi = *this;
|
||||
gi.setArea (rowStart, columnStart, rowEnd, columnEnd);
|
||||
return gi;
|
||||
}
|
||||
|
||||
GridItem GridItem::withArea (Property rowStart, Property columnStart) const noexcept
|
||||
{
|
||||
auto gi = *this;
|
||||
gi.setArea (rowStart, columnStart);
|
||||
return gi;
|
||||
}
|
||||
|
||||
GridItem GridItem::withArea (const String& areaName) const noexcept
|
||||
{
|
||||
auto gi = *this;
|
||||
gi.setArea (areaName);
|
||||
return gi;
|
||||
}
|
||||
|
||||
GridItem GridItem::withRow (StartAndEndProperty newRow) const noexcept
|
||||
{
|
||||
auto gi = *this;
|
||||
gi.row = newRow;
|
||||
return gi;
|
||||
}
|
||||
|
||||
GridItem GridItem::withColumn (StartAndEndProperty newColumn) const noexcept
|
||||
{
|
||||
auto gi = *this;
|
||||
gi.column = newColumn;
|
||||
return gi;
|
||||
}
|
||||
|
||||
GridItem GridItem::withAlignSelf (AlignSelf newAlignSelf) const noexcept
|
||||
{
|
||||
auto gi = *this;
|
||||
gi.alignSelf = newAlignSelf;
|
||||
return gi;
|
||||
}
|
||||
|
||||
GridItem GridItem::withJustifySelf (JustifySelf newJustifySelf) const noexcept
|
||||
{
|
||||
auto gi = *this;
|
||||
gi.justifySelf = newJustifySelf;
|
||||
return gi;
|
||||
}
|
||||
|
||||
GridItem GridItem::withWidth (float newWidth) const noexcept
|
||||
{
|
||||
auto gi = *this;
|
||||
gi.width = newWidth;
|
||||
return gi;
|
||||
}
|
||||
|
||||
GridItem GridItem::withHeight (float newHeight) const noexcept
|
||||
{
|
||||
auto gi = *this;
|
||||
gi.height = newHeight;
|
||||
return gi;
|
||||
}
|
||||
|
||||
GridItem GridItem::withSize (float newWidth, float newHeight) const noexcept
|
||||
{
|
||||
auto gi = *this;
|
||||
gi.width = newWidth;
|
||||
gi.height = newHeight;
|
||||
return gi;
|
||||
}
|
||||
|
||||
GridItem GridItem::withMargin (Margin newHeight) const noexcept
|
||||
{
|
||||
auto gi = *this;
|
||||
gi.margin = newHeight;
|
||||
return gi;
|
||||
}
|
||||
|
||||
GridItem GridItem::withOrder (int newOrder) const noexcept
|
||||
{
|
||||
auto gi = *this;
|
||||
gi.order = newOrder;
|
||||
return gi;
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
|
@ -1,243 +1,240 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
/**
|
||||
Defines an item in a Grid
|
||||
@see Grid
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API GridItem
|
||||
{
|
||||
public:
|
||||
enum class Keyword { autoValue };
|
||||
|
||||
//==============================================================================
|
||||
/** Represents a span. */
|
||||
struct Span
|
||||
{
|
||||
explicit Span (int numberToUse) noexcept : number (numberToUse)
|
||||
{
|
||||
/* Span must be at least one and positive */
|
||||
jassert (numberToUse > 0);
|
||||
}
|
||||
|
||||
explicit Span (int numberToUse, const String& nameToUse) : Span (numberToUse)
|
||||
{
|
||||
/* Name must not be empty */
|
||||
jassert (nameToUse.isNotEmpty());
|
||||
name = nameToUse;
|
||||
}
|
||||
|
||||
explicit Span (const String& nameToUse) : name (nameToUse)
|
||||
{
|
||||
/* Name must not be empty */
|
||||
jassert (nameToUse.isNotEmpty());
|
||||
}
|
||||
|
||||
int number = 1;
|
||||
String name;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** Represents a property. */
|
||||
struct Property
|
||||
{
|
||||
Property() noexcept;
|
||||
|
||||
Property (Keyword keyword) noexcept;
|
||||
|
||||
Property (const char* lineNameToUse) noexcept;
|
||||
|
||||
Property (const String& lineNameToUse) noexcept;
|
||||
|
||||
Property (int numberToUse) noexcept;
|
||||
|
||||
Property (int numberToUse, const String& lineNameToUse) noexcept;
|
||||
|
||||
Property (Span spanToUse) noexcept;
|
||||
|
||||
bool hasSpan() const noexcept { return isSpan && ! isAuto; }
|
||||
bool hasAbsolute() const noexcept { return ! (isSpan || isAuto); }
|
||||
bool hasAuto() const noexcept { return isAuto; }
|
||||
bool hasName() const noexcept { return name.isNotEmpty(); }
|
||||
const String& getName() const noexcept { return name; }
|
||||
int getNumber() const noexcept { return number; }
|
||||
|
||||
private:
|
||||
String name;
|
||||
int number = 1; /** Either an absolute line number or number of lines to span across. */
|
||||
bool isSpan = false;
|
||||
bool isAuto = false;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** Represents start and end properties. */
|
||||
struct StartAndEndProperty { Property start, end; };
|
||||
|
||||
//==============================================================================
|
||||
/** Possible values for the justifySelf property. */
|
||||
enum class JustifySelf : int
|
||||
{
|
||||
start = 0, /**< Content inside the item is justified towards the left. */
|
||||
end, /**< Content inside the item is justified towards the right. */
|
||||
center, /**< Content inside the item is justified towards the center. */
|
||||
stretch, /**< Content inside the item is stretched from left to right. */
|
||||
autoValue /**< Follows the Grid container's justifyItems property. */
|
||||
};
|
||||
|
||||
/** Possible values for the alignSelf property. */
|
||||
enum class AlignSelf : int
|
||||
{
|
||||
start = 0, /**< Content inside the item is aligned towards the top. */
|
||||
end, /**< Content inside the item is aligned towards the bottom. */
|
||||
center, /**< Content inside the item is aligned towards the center. */
|
||||
stretch, /**< Content inside the item is stretched from top to bottom. */
|
||||
autoValue /**< Follows the Grid container's alignItems property. */
|
||||
};
|
||||
|
||||
/** Creates an item with default parameters. */
|
||||
GridItem() noexcept;
|
||||
/** Creates an item with a given Component to use. */
|
||||
GridItem (Component& componentToUse) noexcept;
|
||||
/** Creates an item with a given Component to use. */
|
||||
GridItem (Component* componentToUse) noexcept;
|
||||
|
||||
/** Destructor. */
|
||||
~GridItem() noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** If this is non-null, it represents a Component whose bounds are controlled by this item. */
|
||||
Component* associatedComponent = nullptr;
|
||||
|
||||
//==============================================================================
|
||||
/** Determines the order used to lay out items in their grid container. */
|
||||
int order = 0;
|
||||
|
||||
/** This is the justify-self property of the item.
|
||||
This determines the alignment of the item along the row.
|
||||
*/
|
||||
JustifySelf justifySelf = JustifySelf::autoValue;
|
||||
|
||||
/** This is the align-self property of the item.
|
||||
This determines the alignment of the item along the column.
|
||||
*/
|
||||
AlignSelf alignSelf = AlignSelf::autoValue;
|
||||
|
||||
/** These are the start and end properties of the column. */
|
||||
StartAndEndProperty column = { Keyword::autoValue, Keyword::autoValue };
|
||||
|
||||
/** These are the start and end properties of the row. */
|
||||
StartAndEndProperty row = { Keyword::autoValue, Keyword::autoValue };
|
||||
|
||||
/** */
|
||||
String area;
|
||||
|
||||
//==============================================================================
|
||||
enum
|
||||
{
|
||||
useDefaultValue = -2, /* TODO: useDefaultValue should be named useAuto */
|
||||
notAssigned = -1
|
||||
};
|
||||
|
||||
/* TODO: move all of this into a common class that is shared with the FlexItem */
|
||||
float width = notAssigned;
|
||||
float minWidth = 0.0f;
|
||||
float maxWidth = notAssigned;
|
||||
|
||||
float height = notAssigned;
|
||||
float minHeight = 0.0f;
|
||||
float maxHeight = notAssigned;
|
||||
|
||||
/** Represents a margin. */
|
||||
struct Margin
|
||||
{
|
||||
Margin() noexcept;
|
||||
Margin (int size) noexcept;
|
||||
Margin (float size) noexcept;
|
||||
Margin (float top, float right, float bottom, float left) noexcept; /**< Creates a margin with these sizes. */
|
||||
|
||||
float left;
|
||||
float right;
|
||||
float top;
|
||||
float bottom;
|
||||
};
|
||||
|
||||
/** The margin to leave around this item. */
|
||||
Margin margin;
|
||||
|
||||
/** The item's current bounds. */
|
||||
Rectangle<float> currentBounds;
|
||||
|
||||
/** Short-hand */
|
||||
void setArea (Property rowStart, Property columnStart, Property rowEnd, Property columnEnd);
|
||||
|
||||
/** Short-hand, span of 1 by default */
|
||||
void setArea (Property rowStart, Property columnStart);
|
||||
|
||||
/** Short-hand */
|
||||
void setArea (const String& areaName);
|
||||
|
||||
/** Short-hand */
|
||||
GridItem withArea (Property rowStart, Property columnStart, Property rowEnd, Property columnEnd) const noexcept;
|
||||
|
||||
/** Short-hand, span of 1 by default */
|
||||
GridItem withArea (Property rowStart, Property columnStart) const noexcept;
|
||||
|
||||
/** Short-hand */
|
||||
GridItem withArea (const String& areaName) const noexcept;
|
||||
|
||||
/** Returns a copy of this object with a new row property. */
|
||||
GridItem withRow (StartAndEndProperty row) const noexcept;
|
||||
|
||||
/** Returns a copy of this object with a new column property. */
|
||||
GridItem withColumn (StartAndEndProperty column) const noexcept;
|
||||
|
||||
/** Returns a copy of this object with a new alignSelf property. */
|
||||
GridItem withAlignSelf (AlignSelf newAlignSelf) const noexcept;
|
||||
|
||||
/** Returns a copy of this object with a new justifySelf property. */
|
||||
GridItem withJustifySelf (JustifySelf newJustifySelf) const noexcept;
|
||||
|
||||
/** Returns a copy of this object with a new width. */
|
||||
GridItem withWidth (float newWidth) const noexcept;
|
||||
|
||||
/** Returns a copy of this object with a new height. */
|
||||
GridItem withHeight (float newHeight) const noexcept;
|
||||
|
||||
/** Returns a copy of this object with a new size. */
|
||||
GridItem withSize (float newWidth, float newHeight) const noexcept;
|
||||
|
||||
/** Returns a copy of this object with a new margin. */
|
||||
GridItem withMargin (Margin newMargin) const noexcept;
|
||||
|
||||
/** Returns a copy of this object with a new order. */
|
||||
GridItem withOrder (int newOrder) const noexcept;
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
/**
|
||||
Defines an item in a Grid
|
||||
@see Grid
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API GridItem
|
||||
{
|
||||
public:
|
||||
enum class Keyword { autoValue };
|
||||
|
||||
//==============================================================================
|
||||
/** Represents a span. */
|
||||
struct Span
|
||||
{
|
||||
explicit Span (int numberToUse) noexcept : number (numberToUse)
|
||||
{
|
||||
/* Span must be at least one and positive */
|
||||
jassert (numberToUse > 0);
|
||||
}
|
||||
|
||||
explicit Span (int numberToUse, const String& nameToUse) : Span (numberToUse)
|
||||
{
|
||||
/* Name must not be empty */
|
||||
jassert (nameToUse.isNotEmpty());
|
||||
name = nameToUse;
|
||||
}
|
||||
|
||||
explicit Span (const String& nameToUse) : name (nameToUse)
|
||||
{
|
||||
/* Name must not be empty */
|
||||
jassert (nameToUse.isNotEmpty());
|
||||
}
|
||||
|
||||
int number = 1;
|
||||
String name;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** Represents a property. */
|
||||
struct Property
|
||||
{
|
||||
Property() noexcept;
|
||||
|
||||
Property (Keyword keyword) noexcept;
|
||||
|
||||
Property (const char* lineNameToUse) noexcept;
|
||||
|
||||
Property (const String& lineNameToUse) noexcept;
|
||||
|
||||
Property (int numberToUse) noexcept;
|
||||
|
||||
Property (int numberToUse, const String& lineNameToUse) noexcept;
|
||||
|
||||
Property (Span spanToUse) noexcept;
|
||||
|
||||
bool hasSpan() const noexcept { return isSpan && ! isAuto; }
|
||||
bool hasAbsolute() const noexcept { return ! (isSpan || isAuto); }
|
||||
bool hasAuto() const noexcept { return isAuto; }
|
||||
bool hasName() const noexcept { return name.isNotEmpty(); }
|
||||
const String& getName() const noexcept { return name; }
|
||||
int getNumber() const noexcept { return number; }
|
||||
|
||||
private:
|
||||
String name;
|
||||
int number = 1; /** Either an absolute line number or number of lines to span across. */
|
||||
bool isSpan = false;
|
||||
bool isAuto = false;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** Represents start and end properties. */
|
||||
struct StartAndEndProperty { Property start, end; };
|
||||
|
||||
//==============================================================================
|
||||
/** Possible values for the justifySelf property. */
|
||||
enum class JustifySelf : int
|
||||
{
|
||||
start = 0, /**< Content inside the item is justified towards the left. */
|
||||
end, /**< Content inside the item is justified towards the right. */
|
||||
center, /**< Content inside the item is justified towards the center. */
|
||||
stretch, /**< Content inside the item is stretched from left to right. */
|
||||
autoValue /**< Follows the Grid container's justifyItems property. */
|
||||
};
|
||||
|
||||
/** Possible values for the alignSelf property. */
|
||||
enum class AlignSelf : int
|
||||
{
|
||||
start = 0, /**< Content inside the item is aligned towards the top. */
|
||||
end, /**< Content inside the item is aligned towards the bottom. */
|
||||
center, /**< Content inside the item is aligned towards the center. */
|
||||
stretch, /**< Content inside the item is stretched from top to bottom. */
|
||||
autoValue /**< Follows the Grid container's alignItems property. */
|
||||
};
|
||||
|
||||
/** Creates an item with default parameters. */
|
||||
GridItem() noexcept;
|
||||
/** Creates an item with a given Component to use. */
|
||||
GridItem (Component& componentToUse) noexcept;
|
||||
/** Creates an item with a given Component to use. */
|
||||
GridItem (Component* componentToUse) noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** If this is non-null, it represents a Component whose bounds are controlled by this item. */
|
||||
Component* associatedComponent = nullptr;
|
||||
|
||||
//==============================================================================
|
||||
/** Determines the order used to lay out items in their grid container. */
|
||||
int order = 0;
|
||||
|
||||
/** This is the justify-self property of the item.
|
||||
This determines the alignment of the item along the row.
|
||||
*/
|
||||
JustifySelf justifySelf = JustifySelf::autoValue;
|
||||
|
||||
/** This is the align-self property of the item.
|
||||
This determines the alignment of the item along the column.
|
||||
*/
|
||||
AlignSelf alignSelf = AlignSelf::autoValue;
|
||||
|
||||
/** These are the start and end properties of the column. */
|
||||
StartAndEndProperty column = { Keyword::autoValue, Keyword::autoValue };
|
||||
|
||||
/** These are the start and end properties of the row. */
|
||||
StartAndEndProperty row = { Keyword::autoValue, Keyword::autoValue };
|
||||
|
||||
/** */
|
||||
String area;
|
||||
|
||||
//==============================================================================
|
||||
enum
|
||||
{
|
||||
useDefaultValue = -2, /* TODO: useDefaultValue should be named useAuto */
|
||||
notAssigned = -1
|
||||
};
|
||||
|
||||
/* TODO: move all of this into a common class that is shared with the FlexItem */
|
||||
float width = notAssigned;
|
||||
float minWidth = 0.0f;
|
||||
float maxWidth = notAssigned;
|
||||
|
||||
float height = notAssigned;
|
||||
float minHeight = 0.0f;
|
||||
float maxHeight = notAssigned;
|
||||
|
||||
/** Represents a margin. */
|
||||
struct Margin
|
||||
{
|
||||
Margin() noexcept;
|
||||
Margin (int size) noexcept;
|
||||
Margin (float size) noexcept;
|
||||
Margin (float top, float right, float bottom, float left) noexcept; /**< Creates a margin with these sizes. */
|
||||
|
||||
float left;
|
||||
float right;
|
||||
float top;
|
||||
float bottom;
|
||||
};
|
||||
|
||||
/** The margin to leave around this item. */
|
||||
Margin margin;
|
||||
|
||||
/** The item's current bounds. */
|
||||
Rectangle<float> currentBounds;
|
||||
|
||||
/** Short-hand */
|
||||
void setArea (Property rowStart, Property columnStart, Property rowEnd, Property columnEnd);
|
||||
|
||||
/** Short-hand, span of 1 by default */
|
||||
void setArea (Property rowStart, Property columnStart);
|
||||
|
||||
/** Short-hand */
|
||||
void setArea (const String& areaName);
|
||||
|
||||
/** Short-hand */
|
||||
GridItem withArea (Property rowStart, Property columnStart, Property rowEnd, Property columnEnd) const noexcept;
|
||||
|
||||
/** Short-hand, span of 1 by default */
|
||||
GridItem withArea (Property rowStart, Property columnStart) const noexcept;
|
||||
|
||||
/** Short-hand */
|
||||
GridItem withArea (const String& areaName) const noexcept;
|
||||
|
||||
/** Returns a copy of this object with a new row property. */
|
||||
GridItem withRow (StartAndEndProperty row) const noexcept;
|
||||
|
||||
/** Returns a copy of this object with a new column property. */
|
||||
GridItem withColumn (StartAndEndProperty column) const noexcept;
|
||||
|
||||
/** Returns a copy of this object with a new alignSelf property. */
|
||||
GridItem withAlignSelf (AlignSelf newAlignSelf) const noexcept;
|
||||
|
||||
/** Returns a copy of this object with a new justifySelf property. */
|
||||
GridItem withJustifySelf (JustifySelf newJustifySelf) const noexcept;
|
||||
|
||||
/** Returns a copy of this object with a new width. */
|
||||
GridItem withWidth (float newWidth) const noexcept;
|
||||
|
||||
/** Returns a copy of this object with a new height. */
|
||||
GridItem withHeight (float newHeight) const noexcept;
|
||||
|
||||
/** Returns a copy of this object with a new size. */
|
||||
GridItem withSize (float newWidth, float newHeight) const noexcept;
|
||||
|
||||
/** Returns a copy of this object with a new margin. */
|
||||
GridItem withMargin (Margin newMargin) const noexcept;
|
||||
|
||||
/** Returns a copy of this object with a new order. */
|
||||
GridItem withOrder (int newOrder) const noexcept;
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
|
@ -1,78 +1,78 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
GroupComponent::GroupComponent (const String& name,
|
||||
const String& labelText)
|
||||
: Component (name),
|
||||
text (labelText),
|
||||
justification (Justification::left)
|
||||
{
|
||||
setInterceptsMouseClicks (false, true);
|
||||
}
|
||||
|
||||
GroupComponent::~GroupComponent() {}
|
||||
|
||||
void GroupComponent::setText (const String& newText)
|
||||
{
|
||||
if (text != newText)
|
||||
{
|
||||
text = newText;
|
||||
repaint();
|
||||
}
|
||||
}
|
||||
|
||||
String GroupComponent::getText() const
|
||||
{
|
||||
return text;
|
||||
}
|
||||
|
||||
void GroupComponent::setTextLabelPosition (Justification newJustification)
|
||||
{
|
||||
if (justification != newJustification)
|
||||
{
|
||||
justification = newJustification;
|
||||
repaint();
|
||||
}
|
||||
}
|
||||
|
||||
void GroupComponent::paint (Graphics& g)
|
||||
{
|
||||
getLookAndFeel().drawGroupComponentOutline (g, getWidth(), getHeight(),
|
||||
text, justification, *this);
|
||||
}
|
||||
|
||||
void GroupComponent::enablementChanged() { repaint(); }
|
||||
void GroupComponent::colourChanged() { repaint(); }
|
||||
|
||||
//==============================================================================
|
||||
std::unique_ptr<AccessibilityHandler> GroupComponent::createAccessibilityHandler()
|
||||
{
|
||||
return std::make_unique<AccessibilityHandler> (*this, AccessibilityRole::group);
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
GroupComponent::GroupComponent (const String& name,
|
||||
const String& labelText)
|
||||
: Component (name),
|
||||
text (labelText),
|
||||
justification (Justification::left)
|
||||
{
|
||||
setInterceptsMouseClicks (false, true);
|
||||
}
|
||||
|
||||
GroupComponent::~GroupComponent() {}
|
||||
|
||||
void GroupComponent::setText (const String& newText)
|
||||
{
|
||||
if (text != newText)
|
||||
{
|
||||
text = newText;
|
||||
repaint();
|
||||
}
|
||||
}
|
||||
|
||||
String GroupComponent::getText() const
|
||||
{
|
||||
return text;
|
||||
}
|
||||
|
||||
void GroupComponent::setTextLabelPosition (Justification newJustification)
|
||||
{
|
||||
if (justification != newJustification)
|
||||
{
|
||||
justification = newJustification;
|
||||
repaint();
|
||||
}
|
||||
}
|
||||
|
||||
void GroupComponent::paint (Graphics& g)
|
||||
{
|
||||
getLookAndFeel().drawGroupComponentOutline (g, getWidth(), getHeight(),
|
||||
text, justification, *this);
|
||||
}
|
||||
|
||||
void GroupComponent::enablementChanged() { repaint(); }
|
||||
void GroupComponent::colourChanged() { repaint(); }
|
||||
|
||||
//==============================================================================
|
||||
std::unique_ptr<AccessibilityHandler> GroupComponent::createAccessibilityHandler()
|
||||
{
|
||||
return std::make_unique<AccessibilityHandler> (*this, AccessibilityRole::group);
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
|
@ -1,111 +1,111 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A component that draws an outline around itself and has an optional title at
|
||||
the top, for drawing an outline around a group of controls.
|
||||
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API GroupComponent : public Component
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates a GroupComponent.
|
||||
|
||||
@param componentName the name to give the component
|
||||
@param labelText the text to show at the top of the outline
|
||||
*/
|
||||
GroupComponent (const String& componentName = {},
|
||||
const String& labelText = {});
|
||||
|
||||
/** Destructor. */
|
||||
~GroupComponent() override;
|
||||
|
||||
//==============================================================================
|
||||
/** Changes the text that's shown at the top of the component. */
|
||||
void setText (const String& newText);
|
||||
|
||||
/** Returns the currently displayed text label. */
|
||||
String getText() const;
|
||||
|
||||
/** Sets the positioning of the text label.
|
||||
(The default is Justification::left)
|
||||
@see getTextLabelPosition
|
||||
*/
|
||||
void setTextLabelPosition (Justification justification);
|
||||
|
||||
/** Returns the current text label position.
|
||||
@see setTextLabelPosition
|
||||
*/
|
||||
Justification getTextLabelPosition() const noexcept { return justification; }
|
||||
|
||||
//==============================================================================
|
||||
/** A set of colour IDs to use to change the colour of various aspects of the component.
|
||||
|
||||
These constants can be used either via the Component::setColour(), or LookAndFeel::setColour()
|
||||
methods.
|
||||
|
||||
@see Component::setColour, Component::findColour, LookAndFeel::setColour, LookAndFeel::findColour
|
||||
*/
|
||||
enum ColourIds
|
||||
{
|
||||
outlineColourId = 0x1005400, /**< The colour to use for drawing the line around the edge. */
|
||||
textColourId = 0x1005410 /**< The colour to use to draw the text label. */
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** This abstract base class is implemented by LookAndFeel classes. */
|
||||
struct JUCE_API LookAndFeelMethods
|
||||
{
|
||||
virtual ~LookAndFeelMethods() = default;
|
||||
|
||||
virtual void drawGroupComponentOutline (Graphics&, int w, int h, const String& text,
|
||||
const Justification&, GroupComponent&) = 0;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** @internal */
|
||||
void paint (Graphics&) override;
|
||||
/** @internal */
|
||||
void enablementChanged() override;
|
||||
/** @internal */
|
||||
void colourChanged() override;
|
||||
|
||||
private:
|
||||
std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override;
|
||||
|
||||
String text;
|
||||
Justification justification;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE (GroupComponent)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A component that draws an outline around itself and has an optional title at
|
||||
the top, for drawing an outline around a group of controls.
|
||||
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API GroupComponent : public Component
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates a GroupComponent.
|
||||
|
||||
@param componentName the name to give the component
|
||||
@param labelText the text to show at the top of the outline
|
||||
*/
|
||||
GroupComponent (const String& componentName = {},
|
||||
const String& labelText = {});
|
||||
|
||||
/** Destructor. */
|
||||
~GroupComponent() override;
|
||||
|
||||
//==============================================================================
|
||||
/** Changes the text that's shown at the top of the component. */
|
||||
void setText (const String& newText);
|
||||
|
||||
/** Returns the currently displayed text label. */
|
||||
String getText() const;
|
||||
|
||||
/** Sets the positioning of the text label.
|
||||
(The default is Justification::left)
|
||||
@see getTextLabelPosition
|
||||
*/
|
||||
void setTextLabelPosition (Justification justification);
|
||||
|
||||
/** Returns the current text label position.
|
||||
@see setTextLabelPosition
|
||||
*/
|
||||
Justification getTextLabelPosition() const noexcept { return justification; }
|
||||
|
||||
//==============================================================================
|
||||
/** A set of colour IDs to use to change the colour of various aspects of the component.
|
||||
|
||||
These constants can be used either via the Component::setColour(), or LookAndFeel::setColour()
|
||||
methods.
|
||||
|
||||
@see Component::setColour, Component::findColour, LookAndFeel::setColour, LookAndFeel::findColour
|
||||
*/
|
||||
enum ColourIds
|
||||
{
|
||||
outlineColourId = 0x1005400, /**< The colour to use for drawing the line around the edge. */
|
||||
textColourId = 0x1005410 /**< The colour to use to draw the text label. */
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** This abstract base class is implemented by LookAndFeel classes. */
|
||||
struct JUCE_API LookAndFeelMethods
|
||||
{
|
||||
virtual ~LookAndFeelMethods() = default;
|
||||
|
||||
virtual void drawGroupComponentOutline (Graphics&, int w, int h, const String& text,
|
||||
const Justification&, GroupComponent&) = 0;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** @internal */
|
||||
void paint (Graphics&) override;
|
||||
/** @internal */
|
||||
void enablementChanged() override;
|
||||
/** @internal */
|
||||
void colourChanged() override;
|
||||
|
||||
private:
|
||||
std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override;
|
||||
|
||||
String text;
|
||||
Justification justification;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE (GroupComponent)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,376 +1,381 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
class MultiDocumentPanel;
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
This is a derivative of DocumentWindow that is used inside a MultiDocumentPanel
|
||||
component.
|
||||
|
||||
It's like a normal DocumentWindow but has some extra functionality to make sure
|
||||
everything works nicely inside a MultiDocumentPanel.
|
||||
|
||||
@see MultiDocumentPanel
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API MultiDocumentPanelWindow : public DocumentWindow
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/**
|
||||
*/
|
||||
MultiDocumentPanelWindow (Colour backgroundColour);
|
||||
|
||||
/** Destructor. */
|
||||
~MultiDocumentPanelWindow() override;
|
||||
|
||||
//==============================================================================
|
||||
/** @internal */
|
||||
void maximiseButtonPressed() override;
|
||||
/** @internal */
|
||||
void closeButtonPressed() override;
|
||||
/** @internal */
|
||||
void activeWindowStatusChanged() override;
|
||||
/** @internal */
|
||||
void broughtToFront() override;
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
void updateOrder();
|
||||
MultiDocumentPanel* getOwner() const noexcept;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MultiDocumentPanelWindow)
|
||||
};
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A component that contains a set of other components either in floating windows
|
||||
or tabs.
|
||||
|
||||
This acts as a panel that can be used to hold a set of open document windows, with
|
||||
different layout modes.
|
||||
|
||||
Use addDocument() and closeDocument() to add or remove components from the
|
||||
panel - never use any of the Component methods to access the panel's child
|
||||
components directly, as these are managed internally.
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API MultiDocumentPanel : public Component,
|
||||
private ComponentListener
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates an empty panel.
|
||||
|
||||
Use addDocument() and closeDocument() to add or remove components from the
|
||||
panel - never use any of the Component methods to access the panel's child
|
||||
components directly, as these are managed internally.
|
||||
*/
|
||||
MultiDocumentPanel();
|
||||
|
||||
/** Destructor.
|
||||
|
||||
When deleted, this will call closeAllDocuments (false) to make sure all its
|
||||
components are deleted. If you need to make sure all documents are saved
|
||||
before closing, then you should call closeAllDocuments (true) and check that
|
||||
it returns true before deleting the panel.
|
||||
*/
|
||||
~MultiDocumentPanel() override;
|
||||
|
||||
//==============================================================================
|
||||
#if JUCE_MODAL_LOOPS_PERMITTED
|
||||
/** Tries to close all the documents.
|
||||
|
||||
If checkItsOkToCloseFirst is true, then the tryToCloseDocument() method will
|
||||
be called for each open document, and any of these calls fails, this method
|
||||
will stop and return false, leaving some documents still open.
|
||||
|
||||
If checkItsOkToCloseFirst is false, then all documents will be closed
|
||||
unconditionally.
|
||||
|
||||
@see closeDocument
|
||||
*/
|
||||
bool closeAllDocuments (bool checkItsOkToCloseFirst);
|
||||
#endif
|
||||
|
||||
/** Tries to close all the documents.
|
||||
|
||||
If checkItsOkToCloseFirst is true, then the tryToCloseDocumentAsync() method will
|
||||
be called for each open document, and any of these calls fails, this method
|
||||
will stop and provide an argument of false to the callback, leaving some documents
|
||||
still open.
|
||||
|
||||
If checkItsOkToCloseFirst is false, then all documents will be closed
|
||||
unconditionally.
|
||||
|
||||
@see closeDocument
|
||||
*/
|
||||
void closeAllDocumentsAsync (bool checkItsOkToCloseFirst,
|
||||
std::function<void (bool)> callback);
|
||||
|
||||
/** Adds a document component to the panel.
|
||||
|
||||
If the number of documents would exceed the limit set by setMaximumNumDocuments() then
|
||||
this will fail and return false. (If it does fail, the component passed-in will not be
|
||||
deleted, even if deleteWhenRemoved was set to true).
|
||||
|
||||
The MultiDocumentPanel will deal with creating a window border to go around your component,
|
||||
so just pass in the bare content component here, no need to give it a ResizableWindow
|
||||
or DocumentWindow.
|
||||
|
||||
@param component the component to add
|
||||
@param backgroundColour the background colour to use to fill the component's
|
||||
window or tab
|
||||
@param deleteWhenRemoved if true, then when the component is removed by closeDocument()
|
||||
or closeAllDocuments(), then it will be deleted. If false, then
|
||||
the caller must handle the component's deletion
|
||||
*/
|
||||
bool addDocument (Component* component,
|
||||
Colour backgroundColour,
|
||||
bool deleteWhenRemoved);
|
||||
|
||||
#if JUCE_MODAL_LOOPS_PERMITTED
|
||||
/** Closes one of the documents.
|
||||
|
||||
If checkItsOkToCloseFirst is true, then the tryToCloseDocument() method will
|
||||
be called, and if it fails, this method will return false without closing the
|
||||
document.
|
||||
|
||||
If checkItsOkToCloseFirst is false, then the documents will be closed
|
||||
unconditionally.
|
||||
|
||||
The component will be deleted if the deleteWhenRemoved parameter was set to
|
||||
true when it was added with addDocument.
|
||||
|
||||
@see addDocument, closeAllDocuments
|
||||
*/
|
||||
bool closeDocument (Component* component,
|
||||
bool checkItsOkToCloseFirst);
|
||||
#endif
|
||||
|
||||
/** Closes one of the documents.
|
||||
|
||||
If checkItsOkToCloseFirst is true, then the tryToCloseDocumentAsync() method will
|
||||
be called, and if it fails, this method will call the callback with a false
|
||||
argument without closing the document.
|
||||
|
||||
If checkItsOkToCloseFirst is false, then the documents will be closed
|
||||
unconditionally.
|
||||
|
||||
The component will be deleted if the deleteWhenRemoved parameter was set to
|
||||
true when it was added with addDocument.
|
||||
|
||||
@see addDocument, closeAllDocuments
|
||||
*/
|
||||
void closeDocumentAsync (Component* component,
|
||||
bool checkItsOkToCloseFirst,
|
||||
std::function<void (bool)> callback);
|
||||
|
||||
/** Returns the number of open document windows.
|
||||
|
||||
@see getDocument
|
||||
*/
|
||||
int getNumDocuments() const noexcept;
|
||||
|
||||
/** Returns one of the open documents.
|
||||
|
||||
The order of the documents in this array may change when they are added, removed
|
||||
or moved around.
|
||||
|
||||
@see getNumDocuments
|
||||
*/
|
||||
Component* getDocument (int index) const noexcept;
|
||||
|
||||
/** Returns the document component that is currently focused or on top.
|
||||
|
||||
If currently using floating windows, then this will be the component in the currently
|
||||
active window, or the top component if none are active.
|
||||
|
||||
If it's currently in tabbed mode, then it'll return the component in the active tab.
|
||||
|
||||
@see setActiveDocument
|
||||
*/
|
||||
Component* getActiveDocument() const noexcept;
|
||||
|
||||
/** Makes one of the components active and brings it to the top.
|
||||
|
||||
@see getActiveDocument
|
||||
*/
|
||||
void setActiveDocument (Component* component);
|
||||
|
||||
/** Callback which gets invoked when the currently-active document changes. */
|
||||
virtual void activeDocumentChanged();
|
||||
|
||||
/** Sets a limit on how many windows can be open at once.
|
||||
|
||||
If this is zero or less there's no limit (the default). addDocument() will fail
|
||||
if this number is exceeded.
|
||||
*/
|
||||
void setMaximumNumDocuments (int maximumNumDocuments);
|
||||
|
||||
/** Sets an option to make the document fullscreen if there's only one document open.
|
||||
|
||||
If set to true, then if there's only one document, it'll fill the whole of this
|
||||
component without tabs or a window border. If false, then tabs or a window
|
||||
will always be shown, even if there's only one document. If there's more than
|
||||
one document open, then this option makes no difference.
|
||||
*/
|
||||
void useFullscreenWhenOneDocument (bool shouldUseTabs);
|
||||
|
||||
/** Returns the result of the last time useFullscreenWhenOneDocument() was called.
|
||||
*/
|
||||
bool isFullscreenWhenOneDocument() const noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** The different layout modes available. */
|
||||
enum LayoutMode
|
||||
{
|
||||
FloatingWindows, /**< In this mode, there are overlapping DocumentWindow components for each document. */
|
||||
MaximisedWindowsWithTabs /**< In this mode, a TabbedComponent is used to show one document at a time. */
|
||||
};
|
||||
|
||||
/** Changes the panel's mode.
|
||||
|
||||
@see LayoutMode, getLayoutMode
|
||||
*/
|
||||
void setLayoutMode (LayoutMode newLayoutMode);
|
||||
|
||||
/** Returns the current layout mode. */
|
||||
LayoutMode getLayoutMode() const noexcept { return mode; }
|
||||
|
||||
/** Sets the background colour for the whole panel.
|
||||
|
||||
Each document has its own background colour, but this is the one used to fill the areas
|
||||
behind them.
|
||||
*/
|
||||
void setBackgroundColour (Colour newBackgroundColour);
|
||||
|
||||
/** Returns the current background colour.
|
||||
|
||||
@see setBackgroundColour
|
||||
*/
|
||||
Colour getBackgroundColour() const noexcept { return backgroundColour; }
|
||||
|
||||
/** If the panel is being used in tabbed mode, this returns the TabbedComponent that's involved. */
|
||||
TabbedComponent* getCurrentTabbedComponent() const noexcept { return tabComponent.get(); }
|
||||
|
||||
//==============================================================================
|
||||
#if JUCE_MODAL_LOOPS_PERMITTED
|
||||
/** A subclass must override this to say whether its currently ok for a document
|
||||
to be closed.
|
||||
|
||||
This method is called by closeDocument() and closeAllDocuments() to indicate that
|
||||
a document should be saved if possible, ready for it to be closed.
|
||||
|
||||
If this method returns true, then it means the document is ok and can be closed.
|
||||
|
||||
If it returns false, then it means that the closeDocument() method should stop
|
||||
and not close.
|
||||
|
||||
Normally, you'd use this method to ask the user if they want to save any changes,
|
||||
then return true if the save operation went ok. If the user cancelled the save
|
||||
operation you could return false here to abort the close operation.
|
||||
|
||||
If your component is based on the FileBasedDocument class, then you'd probably want
|
||||
to call FileBasedDocument::saveIfNeededAndUserAgrees() and return true if this returned
|
||||
FileBasedDocument::savedOk
|
||||
|
||||
@see closeDocument, FileBasedDocument::saveIfNeededAndUserAgrees()
|
||||
*/
|
||||
virtual bool tryToCloseDocument (Component* component);
|
||||
#endif
|
||||
|
||||
/** A subclass must override this to say whether its currently ok for a document
|
||||
to be closed.
|
||||
|
||||
This method is called by closeDocumentAsync() and closeAllDocumentsAsync()
|
||||
to indicate that a document should be saved if possible, ready for it to be closed.
|
||||
|
||||
If the callback is called with a true argument, then it means the document is ok
|
||||
and can be closed.
|
||||
|
||||
If the callback is called with a false argument, then it means that the
|
||||
closeDocumentAsync() method should stop and not close.
|
||||
|
||||
Normally, you'd use this method to ask the user if they want to save any changes,
|
||||
then return true if the save operation went ok. If the user cancelled the save
|
||||
operation you could give a value of false to the callback to abort the close operation.
|
||||
|
||||
If your component is based on the FileBasedDocument class, then you'd probably want
|
||||
to call FileBasedDocument::saveIfNeededAndUserAgreesAsync() and call the calback with
|
||||
true if this returned FileBasedDocument::savedOk.
|
||||
|
||||
@see closeDocumentAsync, FileBasedDocument::saveIfNeededAndUserAgreesAsync()
|
||||
*/
|
||||
virtual void tryToCloseDocumentAsync (Component* component, std::function<void (bool)> callback) = 0;
|
||||
|
||||
/** Creates a new window to be used for a document.
|
||||
|
||||
The default implementation of this just returns a basic MultiDocumentPanelWindow object,
|
||||
but you might want to override it to return a custom component.
|
||||
*/
|
||||
virtual MultiDocumentPanelWindow* createNewDocumentWindow();
|
||||
|
||||
//==============================================================================
|
||||
/** @internal */
|
||||
void paint (Graphics&) override;
|
||||
/** @internal */
|
||||
void resized() override;
|
||||
/** @internal */
|
||||
void componentNameChanged (Component&) override;
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
void closeDocumentInternal (Component*);
|
||||
static void closeLastDocumentRecursive (SafePointer<MultiDocumentPanel>,
|
||||
bool,
|
||||
std::function<void (bool)>);
|
||||
|
||||
//==============================================================================
|
||||
struct TabbedComponentInternal;
|
||||
friend class MultiDocumentPanelWindow;
|
||||
|
||||
Component* getContainerComp (Component*) const;
|
||||
void updateOrder();
|
||||
void addWindow (Component*);
|
||||
|
||||
LayoutMode mode = MaximisedWindowsWithTabs;
|
||||
Array<Component*> components;
|
||||
std::unique_ptr<TabbedComponent> tabComponent;
|
||||
Colour backgroundColour { Colours::lightblue };
|
||||
int maximumNumDocuments = 0, numDocsBeforeTabsUsed = 0;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MultiDocumentPanel)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
class MultiDocumentPanel;
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
This is a derivative of DocumentWindow that is used inside a MultiDocumentPanel
|
||||
component.
|
||||
|
||||
It's like a normal DocumentWindow but has some extra functionality to make sure
|
||||
everything works nicely inside a MultiDocumentPanel.
|
||||
|
||||
@see MultiDocumentPanel
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API MultiDocumentPanelWindow : public DocumentWindow
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/**
|
||||
*/
|
||||
MultiDocumentPanelWindow (Colour backgroundColour);
|
||||
|
||||
/** Destructor. */
|
||||
~MultiDocumentPanelWindow() override;
|
||||
|
||||
//==============================================================================
|
||||
/** @internal */
|
||||
void maximiseButtonPressed() override;
|
||||
/** @internal */
|
||||
void closeButtonPressed() override;
|
||||
/** @internal */
|
||||
void activeWindowStatusChanged() override;
|
||||
/** @internal */
|
||||
void broughtToFront() override;
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
void updateActiveDocument();
|
||||
MultiDocumentPanel* getOwner() const noexcept;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MultiDocumentPanelWindow)
|
||||
};
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A component that contains a set of other components either in floating windows
|
||||
or tabs.
|
||||
|
||||
This acts as a panel that can be used to hold a set of open document windows, with
|
||||
different layout modes.
|
||||
|
||||
Use addDocument() and closeDocument() to add or remove components from the
|
||||
panel - never use any of the Component methods to access the panel's child
|
||||
components directly, as these are managed internally.
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API MultiDocumentPanel : public Component,
|
||||
private ComponentListener
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates an empty panel.
|
||||
|
||||
Use addDocument() and closeDocument() to add or remove components from the
|
||||
panel - never use any of the Component methods to access the panel's child
|
||||
components directly, as these are managed internally.
|
||||
*/
|
||||
MultiDocumentPanel();
|
||||
|
||||
/** Destructor.
|
||||
|
||||
When deleted, this will call close all open documents to make sure all its
|
||||
components are deleted. If you need to make sure all documents are saved
|
||||
before closing, then you should call closeAllDocumentsAsync() with
|
||||
checkItsOkToCloseFirst == true and check the provided callback result is true
|
||||
before deleting the panel.
|
||||
*/
|
||||
~MultiDocumentPanel() override;
|
||||
|
||||
//==============================================================================
|
||||
#if JUCE_MODAL_LOOPS_PERMITTED
|
||||
/** Tries to close all the documents.
|
||||
|
||||
If checkItsOkToCloseFirst is true, then the tryToCloseDocument() method will
|
||||
be called for each open document, and any of these calls fails, this method
|
||||
will stop and return false, leaving some documents still open.
|
||||
|
||||
If checkItsOkToCloseFirst is false, then all documents will be closed
|
||||
unconditionally.
|
||||
|
||||
@see closeDocument
|
||||
*/
|
||||
bool closeAllDocuments (bool checkItsOkToCloseFirst);
|
||||
#endif
|
||||
|
||||
/** Tries to close all the documents.
|
||||
|
||||
If checkItsOkToCloseFirst is true, then the tryToCloseDocumentAsync() method will
|
||||
be called for each open document, and any of these calls fails, this method
|
||||
will stop and provide an argument of false to the callback, leaving some documents
|
||||
still open.
|
||||
|
||||
If checkItsOkToCloseFirst is false, then all documents will be closed
|
||||
unconditionally.
|
||||
|
||||
@see closeDocumentAsync
|
||||
*/
|
||||
void closeAllDocumentsAsync (bool checkItsOkToCloseFirst,
|
||||
std::function<void (bool)> callback);
|
||||
|
||||
/** Adds a document component to the panel.
|
||||
|
||||
If the number of documents would exceed the limit set by setMaximumNumDocuments() then
|
||||
this will fail and return false. (If it does fail, the component passed-in will not be
|
||||
deleted, even if deleteWhenRemoved was set to true).
|
||||
|
||||
The MultiDocumentPanel will deal with creating a window border to go around your component,
|
||||
so just pass in the bare content component here, no need to give it a ResizableWindow
|
||||
or DocumentWindow.
|
||||
|
||||
@param component the component to add
|
||||
@param backgroundColour the background colour to use to fill the component's
|
||||
window or tab
|
||||
@param deleteWhenRemoved if true, then when the component is removed by closeDocumentAsync()
|
||||
or closeAllDocumentsAsync(), then it will be deleted. If false, then
|
||||
the caller must handle the component's deletion
|
||||
*/
|
||||
bool addDocument (Component* component,
|
||||
Colour backgroundColour,
|
||||
bool deleteWhenRemoved);
|
||||
|
||||
#if JUCE_MODAL_LOOPS_PERMITTED
|
||||
/** Closes one of the documents.
|
||||
|
||||
If checkItsOkToCloseFirst is true, then the tryToCloseDocument() method will
|
||||
be called, and if it fails, this method will return false without closing the
|
||||
document.
|
||||
|
||||
If checkItsOkToCloseFirst is false, then the documents will be closed
|
||||
unconditionally.
|
||||
|
||||
The component will be deleted if the deleteWhenRemoved parameter was set to
|
||||
true when it was added with addDocument.
|
||||
|
||||
@see addDocument, closeAllDocuments
|
||||
*/
|
||||
bool closeDocument (Component* component,
|
||||
bool checkItsOkToCloseFirst);
|
||||
#endif
|
||||
|
||||
/** Closes one of the documents.
|
||||
|
||||
If checkItsOkToCloseFirst is true, then the tryToCloseDocumentAsync() method will
|
||||
be called, and if it fails, this method will call the callback with a false
|
||||
argument without closing the document.
|
||||
|
||||
If checkItsOkToCloseFirst is false, then the documents will be closed
|
||||
unconditionally.
|
||||
|
||||
The component will be deleted if the deleteWhenRemoved parameter was set to
|
||||
true when it was added with addDocument.
|
||||
|
||||
@see addDocument, closeAllDocumentsAsync
|
||||
*/
|
||||
void closeDocumentAsync (Component* component,
|
||||
bool checkItsOkToCloseFirst,
|
||||
std::function<void (bool)> callback);
|
||||
|
||||
/** Returns the number of open document windows.
|
||||
|
||||
@see getDocument
|
||||
*/
|
||||
int getNumDocuments() const noexcept;
|
||||
|
||||
/** Returns one of the open documents.
|
||||
|
||||
The order of the documents in this array may change when they are added, removed
|
||||
or moved around.
|
||||
|
||||
@see getNumDocuments
|
||||
*/
|
||||
Component* getDocument (int index) const noexcept;
|
||||
|
||||
/** Returns the document component that is currently focused or on top.
|
||||
|
||||
If currently using floating windows, then this will be the component in the currently
|
||||
active window, or the top component if none are active.
|
||||
|
||||
If it's currently in tabbed mode, then it'll return the component in the active tab.
|
||||
|
||||
@see setActiveDocument
|
||||
*/
|
||||
Component* getActiveDocument() const noexcept;
|
||||
|
||||
/** Makes one of the components active and brings it to the top.
|
||||
|
||||
@see getActiveDocument
|
||||
*/
|
||||
void setActiveDocument (Component* component);
|
||||
|
||||
/** Callback which gets invoked when the currently-active document changes. */
|
||||
virtual void activeDocumentChanged();
|
||||
|
||||
/** Sets a limit on how many windows can be open at once.
|
||||
|
||||
If this is zero or less there's no limit (the default). addDocument() will fail
|
||||
if this number is exceeded.
|
||||
*/
|
||||
void setMaximumNumDocuments (int maximumNumDocuments);
|
||||
|
||||
/** Sets an option to make the document fullscreen if there's only one document open.
|
||||
|
||||
If set to true, then if there's only one document, it'll fill the whole of this
|
||||
component without tabs or a window border. If false, then tabs or a window
|
||||
will always be shown, even if there's only one document. If there's more than
|
||||
one document open, then this option makes no difference.
|
||||
*/
|
||||
void useFullscreenWhenOneDocument (bool shouldUseTabs);
|
||||
|
||||
/** Returns the result of the last time useFullscreenWhenOneDocument() was called.
|
||||
*/
|
||||
bool isFullscreenWhenOneDocument() const noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** The different layout modes available. */
|
||||
enum LayoutMode
|
||||
{
|
||||
FloatingWindows, /**< In this mode, there are overlapping DocumentWindow components for each document. */
|
||||
MaximisedWindowsWithTabs /**< In this mode, a TabbedComponent is used to show one document at a time. */
|
||||
};
|
||||
|
||||
/** Changes the panel's mode.
|
||||
|
||||
@see LayoutMode, getLayoutMode
|
||||
*/
|
||||
void setLayoutMode (LayoutMode newLayoutMode);
|
||||
|
||||
/** Returns the current layout mode. */
|
||||
LayoutMode getLayoutMode() const noexcept { return mode; }
|
||||
|
||||
/** Sets the background colour for the whole panel.
|
||||
|
||||
Each document has its own background colour, but this is the one used to fill the areas
|
||||
behind them.
|
||||
*/
|
||||
void setBackgroundColour (Colour newBackgroundColour);
|
||||
|
||||
/** Returns the current background colour.
|
||||
|
||||
@see setBackgroundColour
|
||||
*/
|
||||
Colour getBackgroundColour() const noexcept { return backgroundColour; }
|
||||
|
||||
/** If the panel is being used in tabbed mode, this returns the TabbedComponent that's involved. */
|
||||
TabbedComponent* getCurrentTabbedComponent() const noexcept { return tabComponent.get(); }
|
||||
|
||||
//==============================================================================
|
||||
#if JUCE_MODAL_LOOPS_PERMITTED
|
||||
/** A subclass must override this to say whether its currently ok for a document
|
||||
to be closed.
|
||||
|
||||
This method is called by closeDocument() and closeAllDocuments() to indicate that
|
||||
a document should be saved if possible, ready for it to be closed.
|
||||
|
||||
If this method returns true, then it means the document is ok and can be closed.
|
||||
|
||||
If it returns false, then it means that the closeDocument() method should stop
|
||||
and not close.
|
||||
|
||||
Normally, you'd use this method to ask the user if they want to save any changes,
|
||||
then return true if the save operation went ok. If the user cancelled the save
|
||||
operation you could return false here to abort the close operation.
|
||||
|
||||
If your component is based on the FileBasedDocument class, then you'd probably want
|
||||
to call FileBasedDocument::saveIfNeededAndUserAgrees() and return true if this returned
|
||||
FileBasedDocument::savedOk
|
||||
|
||||
@see closeDocument, FileBasedDocument::saveIfNeededAndUserAgrees()
|
||||
*/
|
||||
virtual bool tryToCloseDocument (Component* component);
|
||||
#endif
|
||||
|
||||
/** A subclass must override this to say whether its currently ok for a document
|
||||
to be closed.
|
||||
|
||||
This method is called by closeDocumentAsync() and closeAllDocumentsAsync()
|
||||
to indicate that a document should be saved if possible, ready for it to be closed.
|
||||
|
||||
If the callback is called with a true argument, then it means the document is ok
|
||||
and can be closed.
|
||||
|
||||
If the callback is called with a false argument, then it means that the
|
||||
closeDocumentAsync() method should stop and not close.
|
||||
|
||||
Normally, you'd use this method to ask the user if they want to save any changes,
|
||||
then return true if the save operation went ok. If the user cancelled the save
|
||||
operation you could give a value of false to the callback to abort the close operation.
|
||||
|
||||
If your component is based on the FileBasedDocument class, then you'd probably want
|
||||
to call FileBasedDocument::saveIfNeededAndUserAgreesAsync() and call the callback with
|
||||
true if this returned FileBasedDocument::savedOk.
|
||||
|
||||
@see closeDocumentAsync, FileBasedDocument::saveIfNeededAndUserAgreesAsync()
|
||||
*/
|
||||
virtual void tryToCloseDocumentAsync (Component* component, std::function<void (bool)> callback) = 0;
|
||||
|
||||
/** Creates a new window to be used for a document.
|
||||
|
||||
The default implementation of this just returns a basic MultiDocumentPanelWindow object,
|
||||
but you might want to override it to return a custom component.
|
||||
*/
|
||||
virtual MultiDocumentPanelWindow* createNewDocumentWindow();
|
||||
|
||||
//==============================================================================
|
||||
/** @internal */
|
||||
void paint (Graphics&) override;
|
||||
/** @internal */
|
||||
void resized() override;
|
||||
/** @internal */
|
||||
void componentNameChanged (Component&) override;
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
void closeDocumentInternal (Component*);
|
||||
static void closeLastDocumentRecursive (SafePointer<MultiDocumentPanel>,
|
||||
bool,
|
||||
std::function<void (bool)>);
|
||||
|
||||
//==============================================================================
|
||||
struct TabbedComponentInternal;
|
||||
friend class MultiDocumentPanelWindow;
|
||||
|
||||
Component* getContainerComp (Component*) const;
|
||||
void updateActiveDocumentFromUIState();
|
||||
void updateActiveDocument (Component*);
|
||||
void addWindow (Component*);
|
||||
void recreateLayout();
|
||||
|
||||
LayoutMode mode = MaximisedWindowsWithTabs;
|
||||
Array<Component*> components;
|
||||
Component* activeComponent = nullptr;
|
||||
bool isLayoutBeingChanged = false;
|
||||
std::unique_ptr<TabbedComponent> tabComponent;
|
||||
Colour backgroundColour { Colours::lightblue };
|
||||
int maximumNumDocuments = 0, numDocsBeforeTabsUsed = 0;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MultiDocumentPanel)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
|
@ -1,195 +1,195 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
ResizableBorderComponent::Zone::Zone() noexcept {}
|
||||
ResizableBorderComponent::Zone::Zone (int zoneFlags) noexcept : zone (zoneFlags) {}
|
||||
ResizableBorderComponent::Zone::Zone (const ResizableBorderComponent::Zone& other) noexcept : zone (other.zone) {}
|
||||
|
||||
ResizableBorderComponent::Zone& ResizableBorderComponent::Zone::operator= (const ResizableBorderComponent::Zone& other) noexcept
|
||||
{
|
||||
zone = other.zone;
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool ResizableBorderComponent::Zone::operator== (const ResizableBorderComponent::Zone& other) const noexcept { return zone == other.zone; }
|
||||
bool ResizableBorderComponent::Zone::operator!= (const ResizableBorderComponent::Zone& other) const noexcept { return zone != other.zone; }
|
||||
|
||||
ResizableBorderComponent::Zone ResizableBorderComponent::Zone::fromPositionOnBorder (Rectangle<int> totalSize,
|
||||
BorderSize<int> border,
|
||||
Point<int> position)
|
||||
{
|
||||
int z = 0;
|
||||
|
||||
if (totalSize.contains (position)
|
||||
&& ! border.subtractedFrom (totalSize).contains (position))
|
||||
{
|
||||
auto minW = jmax (totalSize.getWidth() / 10, jmin (10, totalSize.getWidth() / 3));
|
||||
|
||||
if (position.x < jmax (border.getLeft(), minW) && border.getLeft() > 0)
|
||||
z |= left;
|
||||
else if (position.x >= totalSize.getWidth() - jmax (border.getRight(), minW) && border.getRight() > 0)
|
||||
z |= right;
|
||||
|
||||
auto minH = jmax (totalSize.getHeight() / 10, jmin (10, totalSize.getHeight() / 3));
|
||||
|
||||
if (position.y < jmax (border.getTop(), minH) && border.getTop() > 0)
|
||||
z |= top;
|
||||
else if (position.y >= totalSize.getHeight() - jmax (border.getBottom(), minH) && border.getBottom() > 0)
|
||||
z |= bottom;
|
||||
}
|
||||
|
||||
return Zone (z);
|
||||
}
|
||||
|
||||
MouseCursor ResizableBorderComponent::Zone::getMouseCursor() const noexcept
|
||||
{
|
||||
auto mc = MouseCursor::NormalCursor;
|
||||
|
||||
switch (zone)
|
||||
{
|
||||
case (left | top): mc = MouseCursor::TopLeftCornerResizeCursor; break;
|
||||
case top: mc = MouseCursor::TopEdgeResizeCursor; break;
|
||||
case (right | top): mc = MouseCursor::TopRightCornerResizeCursor; break;
|
||||
case left: mc = MouseCursor::LeftEdgeResizeCursor; break;
|
||||
case right: mc = MouseCursor::RightEdgeResizeCursor; break;
|
||||
case (left | bottom): mc = MouseCursor::BottomLeftCornerResizeCursor; break;
|
||||
case bottom: mc = MouseCursor::BottomEdgeResizeCursor; break;
|
||||
case (right | bottom): mc = MouseCursor::BottomRightCornerResizeCursor; break;
|
||||
default: break;
|
||||
}
|
||||
|
||||
return mc;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
ResizableBorderComponent::ResizableBorderComponent (Component* componentToResize,
|
||||
ComponentBoundsConstrainer* boundsConstrainer)
|
||||
: component (componentToResize),
|
||||
constrainer (boundsConstrainer),
|
||||
borderSize (5)
|
||||
{
|
||||
}
|
||||
|
||||
ResizableBorderComponent::~ResizableBorderComponent() = default;
|
||||
|
||||
//==============================================================================
|
||||
void ResizableBorderComponent::paint (Graphics& g)
|
||||
{
|
||||
getLookAndFeel().drawResizableFrame (g, getWidth(), getHeight(), borderSize);
|
||||
}
|
||||
|
||||
void ResizableBorderComponent::mouseEnter (const MouseEvent& e)
|
||||
{
|
||||
updateMouseZone (e);
|
||||
}
|
||||
|
||||
void ResizableBorderComponent::mouseMove (const MouseEvent& e)
|
||||
{
|
||||
updateMouseZone (e);
|
||||
}
|
||||
|
||||
void ResizableBorderComponent::mouseDown (const MouseEvent& e)
|
||||
{
|
||||
if (component == nullptr)
|
||||
{
|
||||
jassertfalse; // You've deleted the component that this resizer was supposed to be using!
|
||||
return;
|
||||
}
|
||||
|
||||
updateMouseZone (e);
|
||||
|
||||
originalBounds = component->getBounds();
|
||||
|
||||
if (constrainer != nullptr)
|
||||
constrainer->resizeStart();
|
||||
}
|
||||
|
||||
void ResizableBorderComponent::mouseDrag (const MouseEvent& e)
|
||||
{
|
||||
if (component == nullptr)
|
||||
{
|
||||
jassertfalse; // You've deleted the component that this resizer was supposed to be using!
|
||||
return;
|
||||
}
|
||||
|
||||
auto newBounds = mouseZone.resizeRectangleBy (originalBounds, e.getOffsetFromDragStart());
|
||||
|
||||
if (constrainer != nullptr)
|
||||
{
|
||||
constrainer->setBoundsForComponent (component, newBounds,
|
||||
mouseZone.isDraggingTopEdge(),
|
||||
mouseZone.isDraggingLeftEdge(),
|
||||
mouseZone.isDraggingBottomEdge(),
|
||||
mouseZone.isDraggingRightEdge());
|
||||
}
|
||||
else
|
||||
{
|
||||
if (auto* p = component->getPositioner())
|
||||
p->applyNewBounds (newBounds);
|
||||
else
|
||||
component->setBounds (newBounds);
|
||||
}
|
||||
}
|
||||
|
||||
void ResizableBorderComponent::mouseUp (const MouseEvent&)
|
||||
{
|
||||
if (constrainer != nullptr)
|
||||
constrainer->resizeEnd();
|
||||
}
|
||||
|
||||
bool ResizableBorderComponent::hitTest (int x, int y)
|
||||
{
|
||||
return ! borderSize.subtractedFrom (getLocalBounds()).contains (x, y);
|
||||
}
|
||||
|
||||
void ResizableBorderComponent::setBorderThickness (BorderSize<int> newBorderSize)
|
||||
{
|
||||
if (borderSize != newBorderSize)
|
||||
{
|
||||
borderSize = newBorderSize;
|
||||
repaint();
|
||||
}
|
||||
}
|
||||
|
||||
BorderSize<int> ResizableBorderComponent::getBorderThickness() const
|
||||
{
|
||||
return borderSize;
|
||||
}
|
||||
|
||||
void ResizableBorderComponent::updateMouseZone (const MouseEvent& e)
|
||||
{
|
||||
auto newZone = Zone::fromPositionOnBorder (getLocalBounds(), borderSize, e.getPosition());
|
||||
|
||||
if (mouseZone != newZone)
|
||||
{
|
||||
mouseZone = newZone;
|
||||
setMouseCursor (newZone.getMouseCursor());
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
ResizableBorderComponent::Zone::Zone() noexcept {}
|
||||
ResizableBorderComponent::Zone::Zone (int zoneFlags) noexcept : zone (zoneFlags) {}
|
||||
ResizableBorderComponent::Zone::Zone (const ResizableBorderComponent::Zone& other) noexcept : zone (other.zone) {}
|
||||
|
||||
ResizableBorderComponent::Zone& ResizableBorderComponent::Zone::operator= (const ResizableBorderComponent::Zone& other) noexcept
|
||||
{
|
||||
zone = other.zone;
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool ResizableBorderComponent::Zone::operator== (const ResizableBorderComponent::Zone& other) const noexcept { return zone == other.zone; }
|
||||
bool ResizableBorderComponent::Zone::operator!= (const ResizableBorderComponent::Zone& other) const noexcept { return zone != other.zone; }
|
||||
|
||||
ResizableBorderComponent::Zone ResizableBorderComponent::Zone::fromPositionOnBorder (Rectangle<int> totalSize,
|
||||
BorderSize<int> border,
|
||||
Point<int> position)
|
||||
{
|
||||
int z = 0;
|
||||
|
||||
if (totalSize.contains (position)
|
||||
&& ! border.subtractedFrom (totalSize).contains (position))
|
||||
{
|
||||
auto minW = jmax (totalSize.getWidth() / 10, jmin (10, totalSize.getWidth() / 3));
|
||||
|
||||
if (position.x < jmax (border.getLeft(), minW) && border.getLeft() > 0)
|
||||
z |= left;
|
||||
else if (position.x >= totalSize.getWidth() - jmax (border.getRight(), minW) && border.getRight() > 0)
|
||||
z |= right;
|
||||
|
||||
auto minH = jmax (totalSize.getHeight() / 10, jmin (10, totalSize.getHeight() / 3));
|
||||
|
||||
if (position.y < jmax (border.getTop(), minH) && border.getTop() > 0)
|
||||
z |= top;
|
||||
else if (position.y >= totalSize.getHeight() - jmax (border.getBottom(), minH) && border.getBottom() > 0)
|
||||
z |= bottom;
|
||||
}
|
||||
|
||||
return Zone (z);
|
||||
}
|
||||
|
||||
MouseCursor ResizableBorderComponent::Zone::getMouseCursor() const noexcept
|
||||
{
|
||||
auto mc = MouseCursor::NormalCursor;
|
||||
|
||||
switch (zone)
|
||||
{
|
||||
case (left | top): mc = MouseCursor::TopLeftCornerResizeCursor; break;
|
||||
case top: mc = MouseCursor::TopEdgeResizeCursor; break;
|
||||
case (right | top): mc = MouseCursor::TopRightCornerResizeCursor; break;
|
||||
case left: mc = MouseCursor::LeftEdgeResizeCursor; break;
|
||||
case right: mc = MouseCursor::RightEdgeResizeCursor; break;
|
||||
case (left | bottom): mc = MouseCursor::BottomLeftCornerResizeCursor; break;
|
||||
case bottom: mc = MouseCursor::BottomEdgeResizeCursor; break;
|
||||
case (right | bottom): mc = MouseCursor::BottomRightCornerResizeCursor; break;
|
||||
default: break;
|
||||
}
|
||||
|
||||
return mc;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
ResizableBorderComponent::ResizableBorderComponent (Component* componentToResize,
|
||||
ComponentBoundsConstrainer* boundsConstrainer)
|
||||
: component (componentToResize),
|
||||
constrainer (boundsConstrainer),
|
||||
borderSize (5)
|
||||
{
|
||||
}
|
||||
|
||||
ResizableBorderComponent::~ResizableBorderComponent() = default;
|
||||
|
||||
//==============================================================================
|
||||
void ResizableBorderComponent::paint (Graphics& g)
|
||||
{
|
||||
getLookAndFeel().drawResizableFrame (g, getWidth(), getHeight(), borderSize);
|
||||
}
|
||||
|
||||
void ResizableBorderComponent::mouseEnter (const MouseEvent& e)
|
||||
{
|
||||
updateMouseZone (e);
|
||||
}
|
||||
|
||||
void ResizableBorderComponent::mouseMove (const MouseEvent& e)
|
||||
{
|
||||
updateMouseZone (e);
|
||||
}
|
||||
|
||||
void ResizableBorderComponent::mouseDown (const MouseEvent& e)
|
||||
{
|
||||
if (component == nullptr)
|
||||
{
|
||||
jassertfalse; // You've deleted the component that this resizer was supposed to be using!
|
||||
return;
|
||||
}
|
||||
|
||||
updateMouseZone (e);
|
||||
|
||||
originalBounds = component->getBounds();
|
||||
|
||||
if (constrainer != nullptr)
|
||||
constrainer->resizeStart();
|
||||
}
|
||||
|
||||
void ResizableBorderComponent::mouseDrag (const MouseEvent& e)
|
||||
{
|
||||
if (component == nullptr)
|
||||
{
|
||||
jassertfalse; // You've deleted the component that this resizer was supposed to be using!
|
||||
return;
|
||||
}
|
||||
|
||||
auto newBounds = mouseZone.resizeRectangleBy (originalBounds, e.getOffsetFromDragStart());
|
||||
|
||||
if (constrainer != nullptr)
|
||||
{
|
||||
constrainer->setBoundsForComponent (component, newBounds,
|
||||
mouseZone.isDraggingTopEdge(),
|
||||
mouseZone.isDraggingLeftEdge(),
|
||||
mouseZone.isDraggingBottomEdge(),
|
||||
mouseZone.isDraggingRightEdge());
|
||||
}
|
||||
else
|
||||
{
|
||||
if (auto* p = component->getPositioner())
|
||||
p->applyNewBounds (newBounds);
|
||||
else
|
||||
component->setBounds (newBounds);
|
||||
}
|
||||
}
|
||||
|
||||
void ResizableBorderComponent::mouseUp (const MouseEvent&)
|
||||
{
|
||||
if (constrainer != nullptr)
|
||||
constrainer->resizeEnd();
|
||||
}
|
||||
|
||||
bool ResizableBorderComponent::hitTest (int x, int y)
|
||||
{
|
||||
return ! borderSize.subtractedFrom (getLocalBounds()).contains (x, y);
|
||||
}
|
||||
|
||||
void ResizableBorderComponent::setBorderThickness (BorderSize<int> newBorderSize)
|
||||
{
|
||||
if (borderSize != newBorderSize)
|
||||
{
|
||||
borderSize = newBorderSize;
|
||||
repaint();
|
||||
}
|
||||
}
|
||||
|
||||
BorderSize<int> ResizableBorderComponent::getBorderThickness() const
|
||||
{
|
||||
return borderSize;
|
||||
}
|
||||
|
||||
void ResizableBorderComponent::updateMouseZone (const MouseEvent& e)
|
||||
{
|
||||
auto newZone = Zone::fromPositionOnBorder (getLocalBounds(), borderSize, e.getPosition());
|
||||
|
||||
if (mouseZone != newZone)
|
||||
{
|
||||
mouseZone = newZone;
|
||||
setMouseCursor (newZone.getMouseCursor());
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
|
@ -1,196 +1,196 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A component that resizes its parent component when dragged.
|
||||
|
||||
This component forms a frame around the edge of a component, allowing it to
|
||||
be dragged by the edges or corners to resize it - like the way windows are
|
||||
resized in MSWindows or Linux.
|
||||
|
||||
To use it, just add it to your component, making it fill the entire parent component
|
||||
(there's a mouse hit-test that only traps mouse-events which land around the
|
||||
edge of the component, so it's even ok to put it on top of any other components
|
||||
you're using). Make sure you rescale the resizer component to fill the parent
|
||||
each time the parent's size changes.
|
||||
|
||||
@see ResizableCornerComponent
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API ResizableBorderComponent : public Component
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates a resizer.
|
||||
|
||||
Pass in the target component which you want to be resized when this one is
|
||||
dragged.
|
||||
|
||||
The target component will usually be a parent of the resizer component, but this
|
||||
isn't mandatory.
|
||||
|
||||
Remember that when the target component is resized, it'll need to move and
|
||||
resize this component to keep it in place, as this won't happen automatically.
|
||||
|
||||
If the constrainer parameter is not a nullptr, then this object will be used to
|
||||
enforce limits on the size and position that the component can be stretched to.
|
||||
Make sure that the constrainer isn't deleted while still in use by this object.
|
||||
|
||||
@see ComponentBoundsConstrainer
|
||||
*/
|
||||
ResizableBorderComponent (Component* componentToResize,
|
||||
ComponentBoundsConstrainer* constrainer);
|
||||
|
||||
/** Destructor. */
|
||||
~ResizableBorderComponent() override;
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/** Specifies how many pixels wide the draggable edges of this component are.
|
||||
|
||||
@see getBorderThickness
|
||||
*/
|
||||
void setBorderThickness (BorderSize<int> newBorderSize);
|
||||
|
||||
/** Returns the number of pixels wide that the draggable edges of this component are.
|
||||
|
||||
@see setBorderThickness
|
||||
*/
|
||||
BorderSize<int> getBorderThickness() const;
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/** Represents the different sections of a resizable border, which allow it to
|
||||
resized in different ways.
|
||||
*/
|
||||
class Zone
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
enum Zones
|
||||
{
|
||||
centre = 0,
|
||||
left = 1,
|
||||
top = 2,
|
||||
right = 4,
|
||||
bottom = 8
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** Creates a Zone from a combination of the flags in zoneFlags. */
|
||||
explicit Zone (int zoneFlags) noexcept;
|
||||
|
||||
Zone() noexcept;
|
||||
Zone (const Zone&) noexcept;
|
||||
Zone& operator= (const Zone&) noexcept;
|
||||
|
||||
bool operator== (const Zone&) const noexcept;
|
||||
bool operator!= (const Zone&) const noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** Given a point within a rectangle with a resizable border, this returns the
|
||||
zone that the point lies within.
|
||||
*/
|
||||
static Zone fromPositionOnBorder (Rectangle<int> totalSize,
|
||||
BorderSize<int> border,
|
||||
Point<int> position);
|
||||
|
||||
/** Returns an appropriate mouse-cursor for this resize zone. */
|
||||
MouseCursor getMouseCursor() const noexcept;
|
||||
|
||||
/** Returns true if dragging this zone will move the enire object without resizing it. */
|
||||
bool isDraggingWholeObject() const noexcept { return zone == centre; }
|
||||
/** Returns true if dragging this zone will move the object's left edge. */
|
||||
bool isDraggingLeftEdge() const noexcept { return (zone & left) != 0; }
|
||||
/** Returns true if dragging this zone will move the object's right edge. */
|
||||
bool isDraggingRightEdge() const noexcept { return (zone & right) != 0; }
|
||||
/** Returns true if dragging this zone will move the object's top edge. */
|
||||
bool isDraggingTopEdge() const noexcept { return (zone & top) != 0; }
|
||||
/** Returns true if dragging this zone will move the object's bottom edge. */
|
||||
bool isDraggingBottomEdge() const noexcept { return (zone & bottom) != 0; }
|
||||
|
||||
/** Resizes this rectangle by the given amount, moving just the edges that this zone
|
||||
applies to.
|
||||
*/
|
||||
template <typename ValueType>
|
||||
Rectangle<ValueType> resizeRectangleBy (Rectangle<ValueType> original,
|
||||
const Point<ValueType>& distance) const noexcept
|
||||
{
|
||||
if (isDraggingWholeObject())
|
||||
return original + distance;
|
||||
|
||||
if (isDraggingLeftEdge()) original.setLeft (jmin (original.getRight(), original.getX() + distance.x));
|
||||
if (isDraggingRightEdge()) original.setWidth (jmax (ValueType(), original.getWidth() + distance.x));
|
||||
if (isDraggingTopEdge()) original.setTop (jmin (original.getBottom(), original.getY() + distance.y));
|
||||
if (isDraggingBottomEdge()) original.setHeight (jmax (ValueType(), original.getHeight() + distance.y));
|
||||
|
||||
return original;
|
||||
}
|
||||
|
||||
/** Returns the raw flags for this zone. */
|
||||
int getZoneFlags() const noexcept { return zone; }
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
int zone = centre;
|
||||
};
|
||||
|
||||
/** Returns the zone in which the mouse was last seen. */
|
||||
Zone getCurrentZone() const noexcept { return mouseZone; }
|
||||
|
||||
protected:
|
||||
/** @internal */
|
||||
void paint (Graphics&) override;
|
||||
/** @internal */
|
||||
void mouseEnter (const MouseEvent&) override;
|
||||
/** @internal */
|
||||
void mouseMove (const MouseEvent&) override;
|
||||
/** @internal */
|
||||
void mouseDown (const MouseEvent&) override;
|
||||
/** @internal */
|
||||
void mouseDrag (const MouseEvent&) override;
|
||||
/** @internal */
|
||||
void mouseUp (const MouseEvent&) override;
|
||||
/** @internal */
|
||||
bool hitTest (int x, int y) override;
|
||||
|
||||
private:
|
||||
WeakReference<Component> component;
|
||||
ComponentBoundsConstrainer* constrainer;
|
||||
BorderSize<int> borderSize;
|
||||
Rectangle<int> originalBounds;
|
||||
Zone mouseZone;
|
||||
|
||||
void updateMouseZone (const MouseEvent&);
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ResizableBorderComponent)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A component that resizes its parent component when dragged.
|
||||
|
||||
This component forms a frame around the edge of a component, allowing it to
|
||||
be dragged by the edges or corners to resize it - like the way windows are
|
||||
resized in MSWindows or Linux.
|
||||
|
||||
To use it, just add it to your component, making it fill the entire parent component
|
||||
(there's a mouse hit-test that only traps mouse-events which land around the
|
||||
edge of the component, so it's even ok to put it on top of any other components
|
||||
you're using). Make sure you rescale the resizer component to fill the parent
|
||||
each time the parent's size changes.
|
||||
|
||||
@see ResizableCornerComponent
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API ResizableBorderComponent : public Component
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates a resizer.
|
||||
|
||||
Pass in the target component which you want to be resized when this one is
|
||||
dragged.
|
||||
|
||||
The target component will usually be a parent of the resizer component, but this
|
||||
isn't mandatory.
|
||||
|
||||
Remember that when the target component is resized, it'll need to move and
|
||||
resize this component to keep it in place, as this won't happen automatically.
|
||||
|
||||
If the constrainer parameter is not a nullptr, then this object will be used to
|
||||
enforce limits on the size and position that the component can be stretched to.
|
||||
Make sure that the constrainer isn't deleted while still in use by this object.
|
||||
|
||||
@see ComponentBoundsConstrainer
|
||||
*/
|
||||
ResizableBorderComponent (Component* componentToResize,
|
||||
ComponentBoundsConstrainer* constrainer);
|
||||
|
||||
/** Destructor. */
|
||||
~ResizableBorderComponent() override;
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/** Specifies how many pixels wide the draggable edges of this component are.
|
||||
|
||||
@see getBorderThickness
|
||||
*/
|
||||
void setBorderThickness (BorderSize<int> newBorderSize);
|
||||
|
||||
/** Returns the number of pixels wide that the draggable edges of this component are.
|
||||
|
||||
@see setBorderThickness
|
||||
*/
|
||||
BorderSize<int> getBorderThickness() const;
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/** Represents the different sections of a resizable border, which allow it to
|
||||
resized in different ways.
|
||||
*/
|
||||
class Zone
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
enum Zones
|
||||
{
|
||||
centre = 0,
|
||||
left = 1,
|
||||
top = 2,
|
||||
right = 4,
|
||||
bottom = 8
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** Creates a Zone from a combination of the flags in zoneFlags. */
|
||||
explicit Zone (int zoneFlags) noexcept;
|
||||
|
||||
Zone() noexcept;
|
||||
Zone (const Zone&) noexcept;
|
||||
Zone& operator= (const Zone&) noexcept;
|
||||
|
||||
bool operator== (const Zone&) const noexcept;
|
||||
bool operator!= (const Zone&) const noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** Given a point within a rectangle with a resizable border, this returns the
|
||||
zone that the point lies within.
|
||||
*/
|
||||
static Zone fromPositionOnBorder (Rectangle<int> totalSize,
|
||||
BorderSize<int> border,
|
||||
Point<int> position);
|
||||
|
||||
/** Returns an appropriate mouse-cursor for this resize zone. */
|
||||
MouseCursor getMouseCursor() const noexcept;
|
||||
|
||||
/** Returns true if dragging this zone will move the enire object without resizing it. */
|
||||
bool isDraggingWholeObject() const noexcept { return zone == centre; }
|
||||
/** Returns true if dragging this zone will move the object's left edge. */
|
||||
bool isDraggingLeftEdge() const noexcept { return (zone & left) != 0; }
|
||||
/** Returns true if dragging this zone will move the object's right edge. */
|
||||
bool isDraggingRightEdge() const noexcept { return (zone & right) != 0; }
|
||||
/** Returns true if dragging this zone will move the object's top edge. */
|
||||
bool isDraggingTopEdge() const noexcept { return (zone & top) != 0; }
|
||||
/** Returns true if dragging this zone will move the object's bottom edge. */
|
||||
bool isDraggingBottomEdge() const noexcept { return (zone & bottom) != 0; }
|
||||
|
||||
/** Resizes this rectangle by the given amount, moving just the edges that this zone
|
||||
applies to.
|
||||
*/
|
||||
template <typename ValueType>
|
||||
Rectangle<ValueType> resizeRectangleBy (Rectangle<ValueType> original,
|
||||
const Point<ValueType>& distance) const noexcept
|
||||
{
|
||||
if (isDraggingWholeObject())
|
||||
return original + distance;
|
||||
|
||||
if (isDraggingLeftEdge()) original.setLeft (jmin (original.getRight(), original.getX() + distance.x));
|
||||
if (isDraggingRightEdge()) original.setWidth (jmax (ValueType(), original.getWidth() + distance.x));
|
||||
if (isDraggingTopEdge()) original.setTop (jmin (original.getBottom(), original.getY() + distance.y));
|
||||
if (isDraggingBottomEdge()) original.setHeight (jmax (ValueType(), original.getHeight() + distance.y));
|
||||
|
||||
return original;
|
||||
}
|
||||
|
||||
/** Returns the raw flags for this zone. */
|
||||
int getZoneFlags() const noexcept { return zone; }
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
int zone = centre;
|
||||
};
|
||||
|
||||
/** Returns the zone in which the mouse was last seen. */
|
||||
Zone getCurrentZone() const noexcept { return mouseZone; }
|
||||
|
||||
protected:
|
||||
/** @internal */
|
||||
void paint (Graphics&) override;
|
||||
/** @internal */
|
||||
void mouseEnter (const MouseEvent&) override;
|
||||
/** @internal */
|
||||
void mouseMove (const MouseEvent&) override;
|
||||
/** @internal */
|
||||
void mouseDown (const MouseEvent&) override;
|
||||
/** @internal */
|
||||
void mouseDrag (const MouseEvent&) override;
|
||||
/** @internal */
|
||||
void mouseUp (const MouseEvent&) override;
|
||||
/** @internal */
|
||||
bool hitTest (int x, int y) override;
|
||||
|
||||
private:
|
||||
WeakReference<Component> component;
|
||||
ComponentBoundsConstrainer* constrainer;
|
||||
BorderSize<int> borderSize;
|
||||
Rectangle<int> originalBounds;
|
||||
Zone mouseZone;
|
||||
|
||||
void updateMouseZone (const MouseEvent&);
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ResizableBorderComponent)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
|
@ -1,96 +1,96 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
ResizableCornerComponent::ResizableCornerComponent (Component* componentToResize,
|
||||
ComponentBoundsConstrainer* boundsConstrainer)
|
||||
: component (componentToResize),
|
||||
constrainer (boundsConstrainer)
|
||||
{
|
||||
setRepaintsOnMouseActivity (true);
|
||||
setMouseCursor (MouseCursor::BottomRightCornerResizeCursor);
|
||||
}
|
||||
|
||||
ResizableCornerComponent::~ResizableCornerComponent() = default;
|
||||
|
||||
//==============================================================================
|
||||
void ResizableCornerComponent::paint (Graphics& g)
|
||||
{
|
||||
getLookAndFeel().drawCornerResizer (g, getWidth(), getHeight(),
|
||||
isMouseOverOrDragging(),
|
||||
isMouseButtonDown());
|
||||
}
|
||||
|
||||
void ResizableCornerComponent::mouseDown (const MouseEvent&)
|
||||
{
|
||||
if (component == nullptr)
|
||||
{
|
||||
jassertfalse; // You've deleted the component that this resizer is supposed to be controlling!
|
||||
return;
|
||||
}
|
||||
|
||||
originalBounds = component->getBounds();
|
||||
|
||||
if (constrainer != nullptr)
|
||||
constrainer->resizeStart();
|
||||
}
|
||||
|
||||
void ResizableCornerComponent::mouseDrag (const MouseEvent& e)
|
||||
{
|
||||
if (component == nullptr)
|
||||
{
|
||||
jassertfalse; // You've deleted the component that this resizer is supposed to be controlling!
|
||||
return;
|
||||
}
|
||||
|
||||
auto r = originalBounds.withSize (originalBounds.getWidth() + e.getDistanceFromDragStartX(),
|
||||
originalBounds.getHeight() + e.getDistanceFromDragStartY());
|
||||
|
||||
if (constrainer != nullptr)
|
||||
constrainer->setBoundsForComponent (component, r, false, false, true, true);
|
||||
else if (auto pos = component->getPositioner())
|
||||
pos->applyNewBounds (r);
|
||||
else
|
||||
component->setBounds (r);
|
||||
}
|
||||
|
||||
void ResizableCornerComponent::mouseUp (const MouseEvent&)
|
||||
{
|
||||
if (constrainer != nullptr)
|
||||
constrainer->resizeEnd();
|
||||
}
|
||||
|
||||
bool ResizableCornerComponent::hitTest (int x, int y)
|
||||
{
|
||||
if (getWidth() <= 0)
|
||||
return false;
|
||||
|
||||
auto yAtX = getHeight() - (getHeight() * x / getWidth());
|
||||
return y >= yAtX - getHeight() / 4;
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
ResizableCornerComponent::ResizableCornerComponent (Component* componentToResize,
|
||||
ComponentBoundsConstrainer* boundsConstrainer)
|
||||
: component (componentToResize),
|
||||
constrainer (boundsConstrainer)
|
||||
{
|
||||
setRepaintsOnMouseActivity (true);
|
||||
setMouseCursor (MouseCursor::BottomRightCornerResizeCursor);
|
||||
}
|
||||
|
||||
ResizableCornerComponent::~ResizableCornerComponent() = default;
|
||||
|
||||
//==============================================================================
|
||||
void ResizableCornerComponent::paint (Graphics& g)
|
||||
{
|
||||
getLookAndFeel().drawCornerResizer (g, getWidth(), getHeight(),
|
||||
isMouseOverOrDragging(),
|
||||
isMouseButtonDown());
|
||||
}
|
||||
|
||||
void ResizableCornerComponent::mouseDown (const MouseEvent&)
|
||||
{
|
||||
if (component == nullptr)
|
||||
{
|
||||
jassertfalse; // You've deleted the component that this resizer is supposed to be controlling!
|
||||
return;
|
||||
}
|
||||
|
||||
originalBounds = component->getBounds();
|
||||
|
||||
if (constrainer != nullptr)
|
||||
constrainer->resizeStart();
|
||||
}
|
||||
|
||||
void ResizableCornerComponent::mouseDrag (const MouseEvent& e)
|
||||
{
|
||||
if (component == nullptr)
|
||||
{
|
||||
jassertfalse; // You've deleted the component that this resizer is supposed to be controlling!
|
||||
return;
|
||||
}
|
||||
|
||||
auto r = originalBounds.withSize (originalBounds.getWidth() + e.getDistanceFromDragStartX(),
|
||||
originalBounds.getHeight() + e.getDistanceFromDragStartY());
|
||||
|
||||
if (constrainer != nullptr)
|
||||
constrainer->setBoundsForComponent (component, r, false, false, true, true);
|
||||
else if (auto pos = component->getPositioner())
|
||||
pos->applyNewBounds (r);
|
||||
else
|
||||
component->setBounds (r);
|
||||
}
|
||||
|
||||
void ResizableCornerComponent::mouseUp (const MouseEvent&)
|
||||
{
|
||||
if (constrainer != nullptr)
|
||||
constrainer->resizeEnd();
|
||||
}
|
||||
|
||||
bool ResizableCornerComponent::hitTest (int x, int y)
|
||||
{
|
||||
if (getWidth() <= 0)
|
||||
return false;
|
||||
|
||||
auto yAtX = getHeight() - (getHeight() * x / getWidth());
|
||||
return y >= yAtX - getHeight() / 4;
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
|
@ -1,92 +1,92 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/** A component that resizes a parent component when dragged.
|
||||
|
||||
This is the small triangular stripey resizer component you get in the bottom-right
|
||||
of windows (more commonly on the Mac than Windows). Put one in the corner of
|
||||
a larger component and it will automatically resize its parent when it gets dragged
|
||||
around.
|
||||
|
||||
@see ResizableBorderComponent
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API ResizableCornerComponent : public Component
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates a resizer.
|
||||
|
||||
Pass in the target component which you want to be resized when this one is
|
||||
dragged.
|
||||
|
||||
The target component will usually be a parent of the resizer component, but this
|
||||
isn't mandatory.
|
||||
|
||||
Remember that when the target component is resized, it'll need to move and
|
||||
resize this component to keep it in place, as this won't happen automatically.
|
||||
|
||||
If a constrainer object is provided, then this object will be used to enforce
|
||||
limits on the size and position that the component can be stretched to. Make sure
|
||||
that the constrainer isn't deleted while still in use by this object. If you
|
||||
pass a nullptr in here, no limits will be put on the sizes it can be stretched to.
|
||||
|
||||
@see ComponentBoundsConstrainer
|
||||
*/
|
||||
ResizableCornerComponent (Component* componentToResize,
|
||||
ComponentBoundsConstrainer* constrainer);
|
||||
|
||||
/** Destructor. */
|
||||
~ResizableCornerComponent() override;
|
||||
|
||||
|
||||
protected:
|
||||
//==============================================================================
|
||||
/** @internal */
|
||||
void paint (Graphics&) override;
|
||||
/** @internal */
|
||||
void mouseDown (const MouseEvent&) override;
|
||||
/** @internal */
|
||||
void mouseDrag (const MouseEvent&) override;
|
||||
/** @internal */
|
||||
void mouseUp (const MouseEvent&) override;
|
||||
/** @internal */
|
||||
bool hitTest (int x, int y) override;
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
WeakReference<Component> component;
|
||||
ComponentBoundsConstrainer* constrainer;
|
||||
Rectangle<int> originalBounds;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ResizableCornerComponent)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/** A component that resizes a parent component when dragged.
|
||||
|
||||
This is the small triangular stripey resizer component you get in the bottom-right
|
||||
of windows (more commonly on the Mac than Windows). Put one in the corner of
|
||||
a larger component and it will automatically resize its parent when it gets dragged
|
||||
around.
|
||||
|
||||
@see ResizableBorderComponent
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API ResizableCornerComponent : public Component
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates a resizer.
|
||||
|
||||
Pass in the target component which you want to be resized when this one is
|
||||
dragged.
|
||||
|
||||
The target component will usually be a parent of the resizer component, but this
|
||||
isn't mandatory.
|
||||
|
||||
Remember that when the target component is resized, it'll need to move and
|
||||
resize this component to keep it in place, as this won't happen automatically.
|
||||
|
||||
If a constrainer object is provided, then this object will be used to enforce
|
||||
limits on the size and position that the component can be stretched to. Make sure
|
||||
that the constrainer isn't deleted while still in use by this object. If you
|
||||
pass a nullptr in here, no limits will be put on the sizes it can be stretched to.
|
||||
|
||||
@see ComponentBoundsConstrainer
|
||||
*/
|
||||
ResizableCornerComponent (Component* componentToResize,
|
||||
ComponentBoundsConstrainer* constrainer);
|
||||
|
||||
/** Destructor. */
|
||||
~ResizableCornerComponent() override;
|
||||
|
||||
|
||||
protected:
|
||||
//==============================================================================
|
||||
/** @internal */
|
||||
void paint (Graphics&) override;
|
||||
/** @internal */
|
||||
void mouseDown (const MouseEvent&) override;
|
||||
/** @internal */
|
||||
void mouseDrag (const MouseEvent&) override;
|
||||
/** @internal */
|
||||
void mouseUp (const MouseEvent&) override;
|
||||
/** @internal */
|
||||
bool hitTest (int x, int y) override;
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
WeakReference<Component> component;
|
||||
ComponentBoundsConstrainer* constrainer;
|
||||
Rectangle<int> originalBounds;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ResizableCornerComponent)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
|
@ -1,111 +1,111 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
ResizableEdgeComponent::ResizableEdgeComponent (Component* componentToResize,
|
||||
ComponentBoundsConstrainer* boundsConstrainer,
|
||||
Edge e)
|
||||
: component (componentToResize),
|
||||
constrainer (boundsConstrainer),
|
||||
edge (e)
|
||||
{
|
||||
setRepaintsOnMouseActivity (true);
|
||||
setMouseCursor (isVertical() ? MouseCursor::LeftRightResizeCursor
|
||||
: MouseCursor::UpDownResizeCursor);
|
||||
}
|
||||
|
||||
ResizableEdgeComponent::~ResizableEdgeComponent() = default;
|
||||
|
||||
//==============================================================================
|
||||
bool ResizableEdgeComponent::isVertical() const noexcept
|
||||
{
|
||||
return edge == leftEdge || edge == rightEdge;
|
||||
}
|
||||
|
||||
void ResizableEdgeComponent::paint (Graphics& g)
|
||||
{
|
||||
getLookAndFeel().drawStretchableLayoutResizerBar (g, getWidth(), getHeight(), isVertical(),
|
||||
isMouseOver(), isMouseButtonDown());
|
||||
}
|
||||
|
||||
void ResizableEdgeComponent::mouseDown (const MouseEvent&)
|
||||
{
|
||||
if (component == nullptr)
|
||||
{
|
||||
jassertfalse; // You've deleted the component that this resizer was supposed to be using!
|
||||
return;
|
||||
}
|
||||
|
||||
originalBounds = component->getBounds();
|
||||
|
||||
if (constrainer != nullptr)
|
||||
constrainer->resizeStart();
|
||||
}
|
||||
|
||||
void ResizableEdgeComponent::mouseDrag (const MouseEvent& e)
|
||||
{
|
||||
if (component == nullptr)
|
||||
{
|
||||
jassertfalse; // You've deleted the component that this resizer was supposed to be using!
|
||||
return;
|
||||
}
|
||||
|
||||
auto newBounds = originalBounds;
|
||||
|
||||
switch (edge)
|
||||
{
|
||||
case leftEdge: newBounds.setLeft (jmin (newBounds.getRight(), newBounds.getX() + e.getDistanceFromDragStartX())); break;
|
||||
case rightEdge: newBounds.setWidth (jmax (0, newBounds.getWidth() + e.getDistanceFromDragStartX())); break;
|
||||
case topEdge: newBounds.setTop (jmin (newBounds.getBottom(), newBounds.getY() + e.getDistanceFromDragStartY())); break;
|
||||
case bottomEdge: newBounds.setHeight (jmax (0, newBounds.getHeight() + e.getDistanceFromDragStartY())); break;
|
||||
default: jassertfalse; break;
|
||||
}
|
||||
|
||||
if (constrainer != nullptr)
|
||||
{
|
||||
constrainer->setBoundsForComponent (component, newBounds,
|
||||
edge == topEdge,
|
||||
edge == leftEdge,
|
||||
edge == bottomEdge,
|
||||
edge == rightEdge);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (auto* p = component->getPositioner())
|
||||
p->applyNewBounds (newBounds);
|
||||
else
|
||||
component->setBounds (newBounds);
|
||||
}
|
||||
}
|
||||
|
||||
void ResizableEdgeComponent::mouseUp (const MouseEvent&)
|
||||
{
|
||||
if (constrainer != nullptr)
|
||||
constrainer->resizeEnd();
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
ResizableEdgeComponent::ResizableEdgeComponent (Component* componentToResize,
|
||||
ComponentBoundsConstrainer* boundsConstrainer,
|
||||
Edge e)
|
||||
: component (componentToResize),
|
||||
constrainer (boundsConstrainer),
|
||||
edge (e)
|
||||
{
|
||||
setRepaintsOnMouseActivity (true);
|
||||
setMouseCursor (isVertical() ? MouseCursor::LeftRightResizeCursor
|
||||
: MouseCursor::UpDownResizeCursor);
|
||||
}
|
||||
|
||||
ResizableEdgeComponent::~ResizableEdgeComponent() = default;
|
||||
|
||||
//==============================================================================
|
||||
bool ResizableEdgeComponent::isVertical() const noexcept
|
||||
{
|
||||
return edge == leftEdge || edge == rightEdge;
|
||||
}
|
||||
|
||||
void ResizableEdgeComponent::paint (Graphics& g)
|
||||
{
|
||||
getLookAndFeel().drawStretchableLayoutResizerBar (g, getWidth(), getHeight(), isVertical(),
|
||||
isMouseOver(), isMouseButtonDown());
|
||||
}
|
||||
|
||||
void ResizableEdgeComponent::mouseDown (const MouseEvent&)
|
||||
{
|
||||
if (component == nullptr)
|
||||
{
|
||||
jassertfalse; // You've deleted the component that this resizer was supposed to be using!
|
||||
return;
|
||||
}
|
||||
|
||||
originalBounds = component->getBounds();
|
||||
|
||||
if (constrainer != nullptr)
|
||||
constrainer->resizeStart();
|
||||
}
|
||||
|
||||
void ResizableEdgeComponent::mouseDrag (const MouseEvent& e)
|
||||
{
|
||||
if (component == nullptr)
|
||||
{
|
||||
jassertfalse; // You've deleted the component that this resizer was supposed to be using!
|
||||
return;
|
||||
}
|
||||
|
||||
auto newBounds = originalBounds;
|
||||
|
||||
switch (edge)
|
||||
{
|
||||
case leftEdge: newBounds.setLeft (jmin (newBounds.getRight(), newBounds.getX() + e.getDistanceFromDragStartX())); break;
|
||||
case rightEdge: newBounds.setWidth (jmax (0, newBounds.getWidth() + e.getDistanceFromDragStartX())); break;
|
||||
case topEdge: newBounds.setTop (jmin (newBounds.getBottom(), newBounds.getY() + e.getDistanceFromDragStartY())); break;
|
||||
case bottomEdge: newBounds.setHeight (jmax (0, newBounds.getHeight() + e.getDistanceFromDragStartY())); break;
|
||||
default: jassertfalse; break;
|
||||
}
|
||||
|
||||
if (constrainer != nullptr)
|
||||
{
|
||||
constrainer->setBoundsForComponent (component, newBounds,
|
||||
edge == topEdge,
|
||||
edge == leftEdge,
|
||||
edge == bottomEdge,
|
||||
edge == rightEdge);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (auto* p = component->getPositioner())
|
||||
p->applyNewBounds (newBounds);
|
||||
else
|
||||
component->setBounds (newBounds);
|
||||
}
|
||||
}
|
||||
|
||||
void ResizableEdgeComponent::mouseUp (const MouseEvent&)
|
||||
{
|
||||
if (constrainer != nullptr)
|
||||
constrainer->resizeEnd();
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
|
@ -1,100 +1,100 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A component that resizes its parent component when dragged.
|
||||
|
||||
This component forms a bar along one edge of a component, allowing it to
|
||||
be dragged by that edges to resize it.
|
||||
|
||||
To use it, just add it to your component, positioning it along the appropriate
|
||||
edge. Make sure you reposition the resizer component each time the parent's size
|
||||
changes, to keep it in the correct position.
|
||||
|
||||
@see ResizableBorderComponent, ResizableCornerComponent
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API ResizableEdgeComponent : public Component
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
enum Edge
|
||||
{
|
||||
leftEdge, /**< Indicates a vertical bar that can be dragged left/right to move the component's left-hand edge. */
|
||||
rightEdge, /**< Indicates a vertical bar that can be dragged left/right to move the component's right-hand edge. */
|
||||
topEdge, /**< Indicates a horizontal bar that can be dragged up/down to move the top of the component. */
|
||||
bottomEdge /**< Indicates a horizontal bar that can be dragged up/down to move the bottom of the component. */
|
||||
};
|
||||
|
||||
/** Creates a resizer bar.
|
||||
|
||||
Pass in the target component which you want to be resized when this one is
|
||||
dragged. The target component will usually be this component's parent, but this
|
||||
isn't mandatory.
|
||||
|
||||
Remember that when the target component is resized, it'll need to move and
|
||||
resize this component to keep it in place, as this won't happen automatically.
|
||||
|
||||
If the constrainer parameter is not a nullptr, then this object will be used to
|
||||
enforce limits on the size and position that the component can be stretched to.
|
||||
Make sure that the constrainer isn't deleted while still in use by this object.
|
||||
|
||||
@see ComponentBoundsConstrainer
|
||||
*/
|
||||
ResizableEdgeComponent (Component* componentToResize,
|
||||
ComponentBoundsConstrainer* constrainer,
|
||||
Edge edgeToResize);
|
||||
|
||||
/** Destructor. */
|
||||
~ResizableEdgeComponent() override;
|
||||
|
||||
bool isVertical() const noexcept;
|
||||
|
||||
protected:
|
||||
//==============================================================================
|
||||
/** @internal */
|
||||
void paint (Graphics&) override;
|
||||
/** @internal */
|
||||
void mouseDown (const MouseEvent&) override;
|
||||
/** @internal */
|
||||
void mouseDrag (const MouseEvent&) override;
|
||||
/** @internal */
|
||||
void mouseUp (const MouseEvent&) override;
|
||||
|
||||
private:
|
||||
WeakReference<Component> component;
|
||||
ComponentBoundsConstrainer* constrainer;
|
||||
Rectangle<int> originalBounds;
|
||||
const Edge edge;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ResizableEdgeComponent)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A component that resizes its parent component when dragged.
|
||||
|
||||
This component forms a bar along one edge of a component, allowing it to
|
||||
be dragged by that edges to resize it.
|
||||
|
||||
To use it, just add it to your component, positioning it along the appropriate
|
||||
edge. Make sure you reposition the resizer component each time the parent's size
|
||||
changes, to keep it in the correct position.
|
||||
|
||||
@see ResizableBorderComponent, ResizableCornerComponent
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API ResizableEdgeComponent : public Component
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
enum Edge
|
||||
{
|
||||
leftEdge, /**< Indicates a vertical bar that can be dragged left/right to move the component's left-hand edge. */
|
||||
rightEdge, /**< Indicates a vertical bar that can be dragged left/right to move the component's right-hand edge. */
|
||||
topEdge, /**< Indicates a horizontal bar that can be dragged up/down to move the top of the component. */
|
||||
bottomEdge /**< Indicates a horizontal bar that can be dragged up/down to move the bottom of the component. */
|
||||
};
|
||||
|
||||
/** Creates a resizer bar.
|
||||
|
||||
Pass in the target component which you want to be resized when this one is
|
||||
dragged. The target component will usually be this component's parent, but this
|
||||
isn't mandatory.
|
||||
|
||||
Remember that when the target component is resized, it'll need to move and
|
||||
resize this component to keep it in place, as this won't happen automatically.
|
||||
|
||||
If the constrainer parameter is not a nullptr, then this object will be used to
|
||||
enforce limits on the size and position that the component can be stretched to.
|
||||
Make sure that the constrainer isn't deleted while still in use by this object.
|
||||
|
||||
@see ComponentBoundsConstrainer
|
||||
*/
|
||||
ResizableEdgeComponent (Component* componentToResize,
|
||||
ComponentBoundsConstrainer* constrainer,
|
||||
Edge edgeToResize);
|
||||
|
||||
/** Destructor. */
|
||||
~ResizableEdgeComponent() override;
|
||||
|
||||
bool isVertical() const noexcept;
|
||||
|
||||
protected:
|
||||
//==============================================================================
|
||||
/** @internal */
|
||||
void paint (Graphics&) override;
|
||||
/** @internal */
|
||||
void mouseDown (const MouseEvent&) override;
|
||||
/** @internal */
|
||||
void mouseDrag (const MouseEvent&) override;
|
||||
/** @internal */
|
||||
void mouseUp (const MouseEvent&) override;
|
||||
|
||||
private:
|
||||
WeakReference<Component> component;
|
||||
ComponentBoundsConstrainer* constrainer;
|
||||
Rectangle<int> originalBounds;
|
||||
const Edge edge;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ResizableEdgeComponent)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
|
@ -1,477 +1,477 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
class ScrollBar::ScrollbarButton : public Button
|
||||
{
|
||||
public:
|
||||
ScrollbarButton (int direc, ScrollBar& s)
|
||||
: Button (String()), direction (direc), owner (s)
|
||||
{
|
||||
setWantsKeyboardFocus (false);
|
||||
}
|
||||
|
||||
void paintButton (Graphics& g, bool over, bool down) override
|
||||
{
|
||||
getLookAndFeel().drawScrollbarButton (g, owner, getWidth(), getHeight(),
|
||||
direction, owner.isVertical(), over, down);
|
||||
}
|
||||
|
||||
void clicked() override
|
||||
{
|
||||
owner.moveScrollbarInSteps ((direction == 1 || direction == 2) ? 1 : -1);
|
||||
}
|
||||
|
||||
using Button::clicked;
|
||||
|
||||
int direction;
|
||||
|
||||
private:
|
||||
ScrollBar& owner;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ScrollbarButton)
|
||||
};
|
||||
|
||||
|
||||
//==============================================================================
|
||||
ScrollBar::ScrollBar (bool shouldBeVertical) : vertical (shouldBeVertical)
|
||||
{
|
||||
setRepaintsOnMouseActivity (true);
|
||||
setFocusContainerType (FocusContainerType::keyboardFocusContainer);
|
||||
}
|
||||
|
||||
ScrollBar::~ScrollBar()
|
||||
{
|
||||
upButton.reset();
|
||||
downButton.reset();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void ScrollBar::setRangeLimits (Range<double> newRangeLimit, NotificationType notification)
|
||||
{
|
||||
if (totalRange != newRangeLimit)
|
||||
{
|
||||
totalRange = newRangeLimit;
|
||||
setCurrentRange (visibleRange, notification);
|
||||
updateThumbPosition();
|
||||
}
|
||||
}
|
||||
|
||||
void ScrollBar::setRangeLimits (double newMinimum, double newMaximum, NotificationType notification)
|
||||
{
|
||||
jassert (newMaximum >= newMinimum); // these can't be the wrong way round!
|
||||
setRangeLimits (Range<double> (newMinimum, newMaximum), notification);
|
||||
}
|
||||
|
||||
bool ScrollBar::setCurrentRange (Range<double> newRange, NotificationType notification)
|
||||
{
|
||||
auto constrainedRange = totalRange.constrainRange (newRange);
|
||||
|
||||
if (visibleRange != constrainedRange)
|
||||
{
|
||||
visibleRange = constrainedRange;
|
||||
|
||||
updateThumbPosition();
|
||||
|
||||
if (notification != dontSendNotification)
|
||||
triggerAsyncUpdate();
|
||||
|
||||
if (notification == sendNotificationSync)
|
||||
handleUpdateNowIfNeeded();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void ScrollBar::setCurrentRange (double newStart, double newSize, NotificationType notification)
|
||||
{
|
||||
setCurrentRange (Range<double> (newStart, newStart + newSize), notification);
|
||||
}
|
||||
|
||||
void ScrollBar::setCurrentRangeStart (double newStart, NotificationType notification)
|
||||
{
|
||||
setCurrentRange (visibleRange.movedToStartAt (newStart), notification);
|
||||
}
|
||||
|
||||
void ScrollBar::setSingleStepSize (double newSingleStepSize) noexcept
|
||||
{
|
||||
singleStepSize = newSingleStepSize;
|
||||
}
|
||||
|
||||
bool ScrollBar::moveScrollbarInSteps (int howManySteps, NotificationType notification)
|
||||
{
|
||||
return setCurrentRange (visibleRange + howManySteps * singleStepSize, notification);
|
||||
}
|
||||
|
||||
bool ScrollBar::moveScrollbarInPages (int howManyPages, NotificationType notification)
|
||||
{
|
||||
return setCurrentRange (visibleRange + howManyPages * visibleRange.getLength(), notification);
|
||||
}
|
||||
|
||||
bool ScrollBar::scrollToTop (NotificationType notification)
|
||||
{
|
||||
return setCurrentRange (visibleRange.movedToStartAt (getMinimumRangeLimit()), notification);
|
||||
}
|
||||
|
||||
bool ScrollBar::scrollToBottom (NotificationType notification)
|
||||
{
|
||||
return setCurrentRange (visibleRange.movedToEndAt (getMaximumRangeLimit()), notification);
|
||||
}
|
||||
|
||||
void ScrollBar::setButtonRepeatSpeed (int newInitialDelay,
|
||||
int newRepeatDelay,
|
||||
int newMinimumDelay)
|
||||
{
|
||||
initialDelayInMillisecs = newInitialDelay;
|
||||
repeatDelayInMillisecs = newRepeatDelay;
|
||||
minimumDelayInMillisecs = newMinimumDelay;
|
||||
|
||||
if (upButton != nullptr)
|
||||
{
|
||||
upButton ->setRepeatSpeed (newInitialDelay, newRepeatDelay, newMinimumDelay);
|
||||
downButton->setRepeatSpeed (newInitialDelay, newRepeatDelay, newMinimumDelay);
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void ScrollBar::addListener (Listener* listener)
|
||||
{
|
||||
listeners.add (listener);
|
||||
}
|
||||
|
||||
void ScrollBar::removeListener (Listener* listener)
|
||||
{
|
||||
listeners.remove (listener);
|
||||
}
|
||||
|
||||
void ScrollBar::handleAsyncUpdate()
|
||||
{
|
||||
auto start = visibleRange.getStart(); // (need to use a temp variable for VC7 compatibility)
|
||||
listeners.call ([this, start] (Listener& l) { l.scrollBarMoved (this, start); });
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void ScrollBar::updateThumbPosition()
|
||||
{
|
||||
auto minimumScrollBarThumbSize = getLookAndFeel().getMinimumScrollbarThumbSize (*this);
|
||||
|
||||
int newThumbSize = roundToInt (totalRange.getLength() > 0 ? (visibleRange.getLength() * thumbAreaSize) / totalRange.getLength()
|
||||
: thumbAreaSize);
|
||||
|
||||
if (newThumbSize < minimumScrollBarThumbSize)
|
||||
newThumbSize = jmin (minimumScrollBarThumbSize, thumbAreaSize - 1);
|
||||
|
||||
if (newThumbSize > thumbAreaSize)
|
||||
newThumbSize = thumbAreaSize;
|
||||
|
||||
int newThumbStart = thumbAreaStart;
|
||||
|
||||
if (totalRange.getLength() > visibleRange.getLength())
|
||||
newThumbStart += roundToInt (((visibleRange.getStart() - totalRange.getStart()) * (thumbAreaSize - newThumbSize))
|
||||
/ (totalRange.getLength() - visibleRange.getLength()));
|
||||
|
||||
Component::setVisible (getVisibility());
|
||||
|
||||
if (thumbStart != newThumbStart || thumbSize != newThumbSize)
|
||||
{
|
||||
auto repaintStart = jmin (thumbStart, newThumbStart) - 4;
|
||||
auto repaintSize = jmax (thumbStart + thumbSize, newThumbStart + newThumbSize) + 8 - repaintStart;
|
||||
|
||||
if (vertical)
|
||||
repaint (0, repaintStart, getWidth(), repaintSize);
|
||||
else
|
||||
repaint (repaintStart, 0, repaintSize, getHeight());
|
||||
|
||||
thumbStart = newThumbStart;
|
||||
thumbSize = newThumbSize;
|
||||
}
|
||||
}
|
||||
|
||||
void ScrollBar::setOrientation (bool shouldBeVertical)
|
||||
{
|
||||
if (vertical != shouldBeVertical)
|
||||
{
|
||||
vertical = shouldBeVertical;
|
||||
|
||||
if (upButton != nullptr)
|
||||
{
|
||||
upButton->direction = vertical ? 0 : 3;
|
||||
downButton->direction = vertical ? 2 : 1;
|
||||
}
|
||||
|
||||
updateThumbPosition();
|
||||
}
|
||||
}
|
||||
|
||||
void ScrollBar::setAutoHide (bool shouldHideWhenFullRange)
|
||||
{
|
||||
autohides = shouldHideWhenFullRange;
|
||||
updateThumbPosition();
|
||||
}
|
||||
|
||||
bool ScrollBar::autoHides() const noexcept
|
||||
{
|
||||
return autohides;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void ScrollBar::paint (Graphics& g)
|
||||
{
|
||||
if (thumbAreaSize > 0)
|
||||
{
|
||||
auto& lf = getLookAndFeel();
|
||||
|
||||
auto thumb = (thumbAreaSize > lf.getMinimumScrollbarThumbSize (*this))
|
||||
? thumbSize : 0;
|
||||
|
||||
if (vertical)
|
||||
lf.drawScrollbar (g, *this, 0, thumbAreaStart, getWidth(), thumbAreaSize,
|
||||
vertical, thumbStart, thumb, isMouseOver(), isMouseButtonDown());
|
||||
else
|
||||
lf.drawScrollbar (g, *this, thumbAreaStart, 0, thumbAreaSize, getHeight(),
|
||||
vertical, thumbStart, thumb, isMouseOver(), isMouseButtonDown());
|
||||
}
|
||||
}
|
||||
|
||||
void ScrollBar::lookAndFeelChanged()
|
||||
{
|
||||
setComponentEffect (getLookAndFeel().getScrollbarEffect());
|
||||
|
||||
if (isVisible())
|
||||
resized();
|
||||
}
|
||||
|
||||
void ScrollBar::resized()
|
||||
{
|
||||
auto length = vertical ? getHeight() : getWidth();
|
||||
|
||||
auto& lf = getLookAndFeel();
|
||||
bool buttonsVisible = lf.areScrollbarButtonsVisible();
|
||||
int buttonSize = 0;
|
||||
|
||||
if (buttonsVisible)
|
||||
{
|
||||
if (upButton == nullptr)
|
||||
{
|
||||
upButton .reset (new ScrollbarButton (vertical ? 0 : 3, *this));
|
||||
downButton.reset (new ScrollbarButton (vertical ? 2 : 1, *this));
|
||||
|
||||
addAndMakeVisible (upButton.get());
|
||||
addAndMakeVisible (downButton.get());
|
||||
|
||||
setButtonRepeatSpeed (initialDelayInMillisecs, repeatDelayInMillisecs, minimumDelayInMillisecs);
|
||||
}
|
||||
|
||||
buttonSize = jmin (lf.getScrollbarButtonSize (*this), length / 2);
|
||||
}
|
||||
else
|
||||
{
|
||||
upButton.reset();
|
||||
downButton.reset();
|
||||
}
|
||||
|
||||
if (length < 32 + lf.getMinimumScrollbarThumbSize (*this))
|
||||
{
|
||||
thumbAreaStart = length / 2;
|
||||
thumbAreaSize = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
thumbAreaStart = buttonSize;
|
||||
thumbAreaSize = length - 2 * buttonSize;
|
||||
}
|
||||
|
||||
if (upButton != nullptr)
|
||||
{
|
||||
auto r = getLocalBounds();
|
||||
|
||||
if (vertical)
|
||||
{
|
||||
upButton->setBounds (r.removeFromTop (buttonSize));
|
||||
downButton->setBounds (r.removeFromBottom (buttonSize));
|
||||
}
|
||||
else
|
||||
{
|
||||
upButton->setBounds (r.removeFromLeft (buttonSize));
|
||||
downButton->setBounds (r.removeFromRight (buttonSize));
|
||||
}
|
||||
}
|
||||
|
||||
updateThumbPosition();
|
||||
}
|
||||
|
||||
void ScrollBar::parentHierarchyChanged()
|
||||
{
|
||||
lookAndFeelChanged();
|
||||
}
|
||||
|
||||
void ScrollBar::mouseDown (const MouseEvent& e)
|
||||
{
|
||||
isDraggingThumb = false;
|
||||
lastMousePos = vertical ? e.y : e.x;
|
||||
dragStartMousePos = lastMousePos;
|
||||
dragStartRange = visibleRange.getStart();
|
||||
|
||||
if (dragStartMousePos < thumbStart)
|
||||
{
|
||||
moveScrollbarInPages (-1);
|
||||
startTimer (400);
|
||||
}
|
||||
else if (dragStartMousePos >= thumbStart + thumbSize)
|
||||
{
|
||||
moveScrollbarInPages (1);
|
||||
startTimer (400);
|
||||
}
|
||||
else
|
||||
{
|
||||
isDraggingThumb = (thumbAreaSize > getLookAndFeel().getMinimumScrollbarThumbSize (*this))
|
||||
&& (thumbAreaSize > thumbSize);
|
||||
}
|
||||
}
|
||||
|
||||
void ScrollBar::mouseDrag (const MouseEvent& e)
|
||||
{
|
||||
auto mousePos = vertical ? e.y : e.x;
|
||||
|
||||
if (isDraggingThumb && lastMousePos != mousePos && thumbAreaSize > thumbSize)
|
||||
{
|
||||
auto deltaPixels = mousePos - dragStartMousePos;
|
||||
|
||||
setCurrentRangeStart (dragStartRange
|
||||
+ deltaPixels * (totalRange.getLength() - visibleRange.getLength())
|
||||
/ (thumbAreaSize - thumbSize));
|
||||
}
|
||||
|
||||
lastMousePos = mousePos;
|
||||
}
|
||||
|
||||
void ScrollBar::mouseUp (const MouseEvent&)
|
||||
{
|
||||
isDraggingThumb = false;
|
||||
stopTimer();
|
||||
repaint();
|
||||
}
|
||||
|
||||
void ScrollBar::mouseWheelMove (const MouseEvent&, const MouseWheelDetails& wheel)
|
||||
{
|
||||
float increment = 10.0f * (vertical ? wheel.deltaY : wheel.deltaX);
|
||||
|
||||
if (increment < 0)
|
||||
increment = jmin (increment, -1.0f);
|
||||
else if (increment > 0)
|
||||
increment = jmax (increment, 1.0f);
|
||||
|
||||
setCurrentRange (visibleRange - singleStepSize * increment);
|
||||
}
|
||||
|
||||
void ScrollBar::timerCallback()
|
||||
{
|
||||
if (isMouseButtonDown())
|
||||
{
|
||||
startTimer (40);
|
||||
|
||||
if (lastMousePos < thumbStart)
|
||||
setCurrentRange (visibleRange - visibleRange.getLength());
|
||||
else if (lastMousePos > thumbStart + thumbSize)
|
||||
setCurrentRangeStart (visibleRange.getEnd());
|
||||
}
|
||||
else
|
||||
{
|
||||
stopTimer();
|
||||
}
|
||||
}
|
||||
|
||||
bool ScrollBar::keyPressed (const KeyPress& key)
|
||||
{
|
||||
if (isVisible())
|
||||
{
|
||||
if (key == KeyPress::upKey || key == KeyPress::leftKey) return moveScrollbarInSteps (-1);
|
||||
if (key == KeyPress::downKey || key == KeyPress::rightKey) return moveScrollbarInSteps (1);
|
||||
if (key == KeyPress::pageUpKey) return moveScrollbarInPages (-1);
|
||||
if (key == KeyPress::pageDownKey) return moveScrollbarInPages (1);
|
||||
if (key == KeyPress::homeKey) return scrollToTop();
|
||||
if (key == KeyPress::endKey) return scrollToBottom();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void ScrollBar::setVisible (bool shouldBeVisible)
|
||||
{
|
||||
if (userVisibilityFlag != shouldBeVisible)
|
||||
{
|
||||
userVisibilityFlag = shouldBeVisible;
|
||||
Component::setVisible (getVisibility());
|
||||
}
|
||||
}
|
||||
|
||||
bool ScrollBar::getVisibility() const noexcept
|
||||
{
|
||||
if (! userVisibilityFlag)
|
||||
return false;
|
||||
|
||||
return (! autohides) || (totalRange.getLength() > visibleRange.getLength()
|
||||
&& visibleRange.getLength() > 0.0);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
std::unique_ptr<AccessibilityHandler> ScrollBar::createAccessibilityHandler()
|
||||
{
|
||||
class ValueInterface : public AccessibilityRangedNumericValueInterface
|
||||
{
|
||||
public:
|
||||
explicit ValueInterface (ScrollBar& scrollBarToWrap) : scrollBar (scrollBarToWrap) {}
|
||||
|
||||
bool isReadOnly() const override { return false; }
|
||||
|
||||
double getCurrentValue() const override { return scrollBar.getCurrentRangeStart(); }
|
||||
void setValue (double newValue) override { scrollBar.setCurrentRangeStart (newValue); }
|
||||
|
||||
AccessibleValueRange getRange() const override
|
||||
{
|
||||
if (scrollBar.getRangeLimit().isEmpty())
|
||||
return {};
|
||||
|
||||
return { { scrollBar.getMinimumRangeLimit(), scrollBar.getMaximumRangeLimit() },
|
||||
scrollBar.getSingleStepSize() };
|
||||
}
|
||||
|
||||
private:
|
||||
ScrollBar& scrollBar;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ValueInterface)
|
||||
};
|
||||
|
||||
return std::make_unique<AccessibilityHandler> (*this,
|
||||
AccessibilityRole::scrollBar,
|
||||
AccessibilityActions{},
|
||||
AccessibilityHandler::Interfaces { std::make_unique<ValueInterface> (*this) });
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
class ScrollBar::ScrollbarButton : public Button
|
||||
{
|
||||
public:
|
||||
ScrollbarButton (int direc, ScrollBar& s)
|
||||
: Button (String()), direction (direc), owner (s)
|
||||
{
|
||||
setWantsKeyboardFocus (false);
|
||||
}
|
||||
|
||||
void paintButton (Graphics& g, bool over, bool down) override
|
||||
{
|
||||
getLookAndFeel().drawScrollbarButton (g, owner, getWidth(), getHeight(),
|
||||
direction, owner.isVertical(), over, down);
|
||||
}
|
||||
|
||||
void clicked() override
|
||||
{
|
||||
owner.moveScrollbarInSteps ((direction == 1 || direction == 2) ? 1 : -1);
|
||||
}
|
||||
|
||||
using Button::clicked;
|
||||
|
||||
int direction;
|
||||
|
||||
private:
|
||||
ScrollBar& owner;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ScrollbarButton)
|
||||
};
|
||||
|
||||
|
||||
//==============================================================================
|
||||
ScrollBar::ScrollBar (bool shouldBeVertical) : vertical (shouldBeVertical)
|
||||
{
|
||||
setRepaintsOnMouseActivity (true);
|
||||
setFocusContainerType (FocusContainerType::keyboardFocusContainer);
|
||||
}
|
||||
|
||||
ScrollBar::~ScrollBar()
|
||||
{
|
||||
upButton.reset();
|
||||
downButton.reset();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void ScrollBar::setRangeLimits (Range<double> newRangeLimit, NotificationType notification)
|
||||
{
|
||||
if (totalRange != newRangeLimit)
|
||||
{
|
||||
totalRange = newRangeLimit;
|
||||
setCurrentRange (visibleRange, notification);
|
||||
updateThumbPosition();
|
||||
}
|
||||
}
|
||||
|
||||
void ScrollBar::setRangeLimits (double newMinimum, double newMaximum, NotificationType notification)
|
||||
{
|
||||
jassert (newMaximum >= newMinimum); // these can't be the wrong way round!
|
||||
setRangeLimits (Range<double> (newMinimum, newMaximum), notification);
|
||||
}
|
||||
|
||||
bool ScrollBar::setCurrentRange (Range<double> newRange, NotificationType notification)
|
||||
{
|
||||
auto constrainedRange = totalRange.constrainRange (newRange);
|
||||
|
||||
if (visibleRange != constrainedRange)
|
||||
{
|
||||
visibleRange = constrainedRange;
|
||||
|
||||
updateThumbPosition();
|
||||
|
||||
if (notification != dontSendNotification)
|
||||
triggerAsyncUpdate();
|
||||
|
||||
if (notification == sendNotificationSync)
|
||||
handleUpdateNowIfNeeded();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void ScrollBar::setCurrentRange (double newStart, double newSize, NotificationType notification)
|
||||
{
|
||||
setCurrentRange (Range<double> (newStart, newStart + newSize), notification);
|
||||
}
|
||||
|
||||
void ScrollBar::setCurrentRangeStart (double newStart, NotificationType notification)
|
||||
{
|
||||
setCurrentRange (visibleRange.movedToStartAt (newStart), notification);
|
||||
}
|
||||
|
||||
void ScrollBar::setSingleStepSize (double newSingleStepSize) noexcept
|
||||
{
|
||||
singleStepSize = newSingleStepSize;
|
||||
}
|
||||
|
||||
bool ScrollBar::moveScrollbarInSteps (int howManySteps, NotificationType notification)
|
||||
{
|
||||
return setCurrentRange (visibleRange + howManySteps * singleStepSize, notification);
|
||||
}
|
||||
|
||||
bool ScrollBar::moveScrollbarInPages (int howManyPages, NotificationType notification)
|
||||
{
|
||||
return setCurrentRange (visibleRange + howManyPages * visibleRange.getLength(), notification);
|
||||
}
|
||||
|
||||
bool ScrollBar::scrollToTop (NotificationType notification)
|
||||
{
|
||||
return setCurrentRange (visibleRange.movedToStartAt (getMinimumRangeLimit()), notification);
|
||||
}
|
||||
|
||||
bool ScrollBar::scrollToBottom (NotificationType notification)
|
||||
{
|
||||
return setCurrentRange (visibleRange.movedToEndAt (getMaximumRangeLimit()), notification);
|
||||
}
|
||||
|
||||
void ScrollBar::setButtonRepeatSpeed (int newInitialDelay,
|
||||
int newRepeatDelay,
|
||||
int newMinimumDelay)
|
||||
{
|
||||
initialDelayInMillisecs = newInitialDelay;
|
||||
repeatDelayInMillisecs = newRepeatDelay;
|
||||
minimumDelayInMillisecs = newMinimumDelay;
|
||||
|
||||
if (upButton != nullptr)
|
||||
{
|
||||
upButton ->setRepeatSpeed (newInitialDelay, newRepeatDelay, newMinimumDelay);
|
||||
downButton->setRepeatSpeed (newInitialDelay, newRepeatDelay, newMinimumDelay);
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void ScrollBar::addListener (Listener* listener)
|
||||
{
|
||||
listeners.add (listener);
|
||||
}
|
||||
|
||||
void ScrollBar::removeListener (Listener* listener)
|
||||
{
|
||||
listeners.remove (listener);
|
||||
}
|
||||
|
||||
void ScrollBar::handleAsyncUpdate()
|
||||
{
|
||||
auto start = visibleRange.getStart(); // (need to use a temp variable for VC7 compatibility)
|
||||
listeners.call ([this, start] (Listener& l) { l.scrollBarMoved (this, start); });
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void ScrollBar::updateThumbPosition()
|
||||
{
|
||||
auto minimumScrollBarThumbSize = getLookAndFeel().getMinimumScrollbarThumbSize (*this);
|
||||
|
||||
int newThumbSize = roundToInt (totalRange.getLength() > 0 ? (visibleRange.getLength() * thumbAreaSize) / totalRange.getLength()
|
||||
: thumbAreaSize);
|
||||
|
||||
if (newThumbSize < minimumScrollBarThumbSize)
|
||||
newThumbSize = jmin (minimumScrollBarThumbSize, thumbAreaSize - 1);
|
||||
|
||||
if (newThumbSize > thumbAreaSize)
|
||||
newThumbSize = thumbAreaSize;
|
||||
|
||||
int newThumbStart = thumbAreaStart;
|
||||
|
||||
if (totalRange.getLength() > visibleRange.getLength())
|
||||
newThumbStart += roundToInt (((visibleRange.getStart() - totalRange.getStart()) * (thumbAreaSize - newThumbSize))
|
||||
/ (totalRange.getLength() - visibleRange.getLength()));
|
||||
|
||||
Component::setVisible (getVisibility());
|
||||
|
||||
if (thumbStart != newThumbStart || thumbSize != newThumbSize)
|
||||
{
|
||||
auto repaintStart = jmin (thumbStart, newThumbStart) - 4;
|
||||
auto repaintSize = jmax (thumbStart + thumbSize, newThumbStart + newThumbSize) + 8 - repaintStart;
|
||||
|
||||
if (vertical)
|
||||
repaint (0, repaintStart, getWidth(), repaintSize);
|
||||
else
|
||||
repaint (repaintStart, 0, repaintSize, getHeight());
|
||||
|
||||
thumbStart = newThumbStart;
|
||||
thumbSize = newThumbSize;
|
||||
}
|
||||
}
|
||||
|
||||
void ScrollBar::setOrientation (bool shouldBeVertical)
|
||||
{
|
||||
if (vertical != shouldBeVertical)
|
||||
{
|
||||
vertical = shouldBeVertical;
|
||||
|
||||
if (upButton != nullptr)
|
||||
{
|
||||
upButton->direction = vertical ? 0 : 3;
|
||||
downButton->direction = vertical ? 2 : 1;
|
||||
}
|
||||
|
||||
updateThumbPosition();
|
||||
}
|
||||
}
|
||||
|
||||
void ScrollBar::setAutoHide (bool shouldHideWhenFullRange)
|
||||
{
|
||||
autohides = shouldHideWhenFullRange;
|
||||
updateThumbPosition();
|
||||
}
|
||||
|
||||
bool ScrollBar::autoHides() const noexcept
|
||||
{
|
||||
return autohides;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void ScrollBar::paint (Graphics& g)
|
||||
{
|
||||
if (thumbAreaSize > 0)
|
||||
{
|
||||
auto& lf = getLookAndFeel();
|
||||
|
||||
auto thumb = (thumbAreaSize > lf.getMinimumScrollbarThumbSize (*this))
|
||||
? thumbSize : 0;
|
||||
|
||||
if (vertical)
|
||||
lf.drawScrollbar (g, *this, 0, thumbAreaStart, getWidth(), thumbAreaSize,
|
||||
vertical, thumbStart, thumb, isMouseOver(), isMouseButtonDown());
|
||||
else
|
||||
lf.drawScrollbar (g, *this, thumbAreaStart, 0, thumbAreaSize, getHeight(),
|
||||
vertical, thumbStart, thumb, isMouseOver(), isMouseButtonDown());
|
||||
}
|
||||
}
|
||||
|
||||
void ScrollBar::lookAndFeelChanged()
|
||||
{
|
||||
setComponentEffect (getLookAndFeel().getScrollbarEffect());
|
||||
|
||||
if (isVisible())
|
||||
resized();
|
||||
}
|
||||
|
||||
void ScrollBar::resized()
|
||||
{
|
||||
auto length = vertical ? getHeight() : getWidth();
|
||||
|
||||
auto& lf = getLookAndFeel();
|
||||
bool buttonsVisible = lf.areScrollbarButtonsVisible();
|
||||
int buttonSize = 0;
|
||||
|
||||
if (buttonsVisible)
|
||||
{
|
||||
if (upButton == nullptr)
|
||||
{
|
||||
upButton .reset (new ScrollbarButton (vertical ? 0 : 3, *this));
|
||||
downButton.reset (new ScrollbarButton (vertical ? 2 : 1, *this));
|
||||
|
||||
addAndMakeVisible (upButton.get());
|
||||
addAndMakeVisible (downButton.get());
|
||||
|
||||
setButtonRepeatSpeed (initialDelayInMillisecs, repeatDelayInMillisecs, minimumDelayInMillisecs);
|
||||
}
|
||||
|
||||
buttonSize = jmin (lf.getScrollbarButtonSize (*this), length / 2);
|
||||
}
|
||||
else
|
||||
{
|
||||
upButton.reset();
|
||||
downButton.reset();
|
||||
}
|
||||
|
||||
if (length < 32 + lf.getMinimumScrollbarThumbSize (*this))
|
||||
{
|
||||
thumbAreaStart = length / 2;
|
||||
thumbAreaSize = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
thumbAreaStart = buttonSize;
|
||||
thumbAreaSize = length - 2 * buttonSize;
|
||||
}
|
||||
|
||||
if (upButton != nullptr)
|
||||
{
|
||||
auto r = getLocalBounds();
|
||||
|
||||
if (vertical)
|
||||
{
|
||||
upButton->setBounds (r.removeFromTop (buttonSize));
|
||||
downButton->setBounds (r.removeFromBottom (buttonSize));
|
||||
}
|
||||
else
|
||||
{
|
||||
upButton->setBounds (r.removeFromLeft (buttonSize));
|
||||
downButton->setBounds (r.removeFromRight (buttonSize));
|
||||
}
|
||||
}
|
||||
|
||||
updateThumbPosition();
|
||||
}
|
||||
|
||||
void ScrollBar::parentHierarchyChanged()
|
||||
{
|
||||
lookAndFeelChanged();
|
||||
}
|
||||
|
||||
void ScrollBar::mouseDown (const MouseEvent& e)
|
||||
{
|
||||
isDraggingThumb = false;
|
||||
lastMousePos = vertical ? e.y : e.x;
|
||||
dragStartMousePos = lastMousePos;
|
||||
dragStartRange = visibleRange.getStart();
|
||||
|
||||
if (dragStartMousePos < thumbStart)
|
||||
{
|
||||
moveScrollbarInPages (-1);
|
||||
startTimer (400);
|
||||
}
|
||||
else if (dragStartMousePos >= thumbStart + thumbSize)
|
||||
{
|
||||
moveScrollbarInPages (1);
|
||||
startTimer (400);
|
||||
}
|
||||
else
|
||||
{
|
||||
isDraggingThumb = (thumbAreaSize > getLookAndFeel().getMinimumScrollbarThumbSize (*this))
|
||||
&& (thumbAreaSize > thumbSize);
|
||||
}
|
||||
}
|
||||
|
||||
void ScrollBar::mouseDrag (const MouseEvent& e)
|
||||
{
|
||||
auto mousePos = vertical ? e.y : e.x;
|
||||
|
||||
if (isDraggingThumb && lastMousePos != mousePos && thumbAreaSize > thumbSize)
|
||||
{
|
||||
auto deltaPixels = mousePos - dragStartMousePos;
|
||||
|
||||
setCurrentRangeStart (dragStartRange
|
||||
+ deltaPixels * (totalRange.getLength() - visibleRange.getLength())
|
||||
/ (thumbAreaSize - thumbSize));
|
||||
}
|
||||
|
||||
lastMousePos = mousePos;
|
||||
}
|
||||
|
||||
void ScrollBar::mouseUp (const MouseEvent&)
|
||||
{
|
||||
isDraggingThumb = false;
|
||||
stopTimer();
|
||||
repaint();
|
||||
}
|
||||
|
||||
void ScrollBar::mouseWheelMove (const MouseEvent&, const MouseWheelDetails& wheel)
|
||||
{
|
||||
float increment = 10.0f * (vertical ? wheel.deltaY : wheel.deltaX);
|
||||
|
||||
if (increment < 0)
|
||||
increment = jmin (increment, -1.0f);
|
||||
else if (increment > 0)
|
||||
increment = jmax (increment, 1.0f);
|
||||
|
||||
setCurrentRange (visibleRange - singleStepSize * increment);
|
||||
}
|
||||
|
||||
void ScrollBar::timerCallback()
|
||||
{
|
||||
if (isMouseButtonDown())
|
||||
{
|
||||
startTimer (40);
|
||||
|
||||
if (lastMousePos < thumbStart)
|
||||
setCurrentRange (visibleRange - visibleRange.getLength());
|
||||
else if (lastMousePos > thumbStart + thumbSize)
|
||||
setCurrentRangeStart (visibleRange.getEnd());
|
||||
}
|
||||
else
|
||||
{
|
||||
stopTimer();
|
||||
}
|
||||
}
|
||||
|
||||
bool ScrollBar::keyPressed (const KeyPress& key)
|
||||
{
|
||||
if (isVisible())
|
||||
{
|
||||
if (key == KeyPress::upKey || key == KeyPress::leftKey) return moveScrollbarInSteps (-1);
|
||||
if (key == KeyPress::downKey || key == KeyPress::rightKey) return moveScrollbarInSteps (1);
|
||||
if (key == KeyPress::pageUpKey) return moveScrollbarInPages (-1);
|
||||
if (key == KeyPress::pageDownKey) return moveScrollbarInPages (1);
|
||||
if (key == KeyPress::homeKey) return scrollToTop();
|
||||
if (key == KeyPress::endKey) return scrollToBottom();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void ScrollBar::setVisible (bool shouldBeVisible)
|
||||
{
|
||||
if (userVisibilityFlag != shouldBeVisible)
|
||||
{
|
||||
userVisibilityFlag = shouldBeVisible;
|
||||
Component::setVisible (getVisibility());
|
||||
}
|
||||
}
|
||||
|
||||
bool ScrollBar::getVisibility() const noexcept
|
||||
{
|
||||
if (! userVisibilityFlag)
|
||||
return false;
|
||||
|
||||
return (! autohides) || (totalRange.getLength() > visibleRange.getLength()
|
||||
&& visibleRange.getLength() > 0.0);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
std::unique_ptr<AccessibilityHandler> ScrollBar::createAccessibilityHandler()
|
||||
{
|
||||
class ValueInterface : public AccessibilityRangedNumericValueInterface
|
||||
{
|
||||
public:
|
||||
explicit ValueInterface (ScrollBar& scrollBarToWrap) : scrollBar (scrollBarToWrap) {}
|
||||
|
||||
bool isReadOnly() const override { return false; }
|
||||
|
||||
double getCurrentValue() const override { return scrollBar.getCurrentRangeStart(); }
|
||||
void setValue (double newValue) override { scrollBar.setCurrentRangeStart (newValue); }
|
||||
|
||||
AccessibleValueRange getRange() const override
|
||||
{
|
||||
if (scrollBar.getRangeLimit().isEmpty())
|
||||
return {};
|
||||
|
||||
return { { scrollBar.getMinimumRangeLimit(), scrollBar.getMaximumRangeLimit() },
|
||||
scrollBar.getSingleStepSize() };
|
||||
}
|
||||
|
||||
private:
|
||||
ScrollBar& scrollBar;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ValueInterface)
|
||||
};
|
||||
|
||||
return std::make_unique<AccessibilityHandler> (*this,
|
||||
AccessibilityRole::scrollBar,
|
||||
AccessibilityActions{},
|
||||
AccessibilityHandler::Interfaces { std::make_unique<ValueInterface> (*this) });
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
|
@ -1,440 +1,440 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A scrollbar component.
|
||||
|
||||
To use a scrollbar, set up its total range using the setRangeLimits() method - this
|
||||
sets the range of values it can represent. Then you can use setCurrentRange() to
|
||||
change the position and size of the scrollbar's 'thumb'.
|
||||
|
||||
Registering a ScrollBar::Listener with the scrollbar will allow you to find out when
|
||||
the user moves it, and you can use the getCurrentRangeStart() to find out where
|
||||
they moved it to.
|
||||
|
||||
The scrollbar will adjust its own visibility according to whether its thumb size
|
||||
allows it to actually be scrolled.
|
||||
|
||||
For most purposes, it's probably easier to use a Viewport or ListBox
|
||||
instead of handling a scrollbar directly.
|
||||
|
||||
@see ScrollBar::Listener
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API ScrollBar : public Component,
|
||||
public AsyncUpdater,
|
||||
private Timer
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates a Scrollbar.
|
||||
@param isVertical specifies whether the bar should be a vertical or horizontal
|
||||
*/
|
||||
ScrollBar (bool isVertical);
|
||||
|
||||
/** Destructor. */
|
||||
~ScrollBar() override;
|
||||
|
||||
//==============================================================================
|
||||
/** Returns true if the scrollbar is vertical, false if it's horizontal. */
|
||||
bool isVertical() const noexcept { return vertical; }
|
||||
|
||||
/** Changes the scrollbar's direction.
|
||||
|
||||
You'll also need to resize the bar appropriately - this just changes its internal
|
||||
layout.
|
||||
|
||||
@param shouldBeVertical true makes it vertical; false makes it horizontal.
|
||||
*/
|
||||
void setOrientation (bool shouldBeVertical);
|
||||
|
||||
/** Tells the scrollbar whether to make itself invisible when not needed.
|
||||
|
||||
The default behaviour is for a scrollbar to become invisible when the thumb
|
||||
fills the whole of its range (i.e. when it can't be moved). Setting this
|
||||
value to false forces the bar to always be visible.
|
||||
@see autoHides()
|
||||
*/
|
||||
void setAutoHide (bool shouldHideWhenFullRange);
|
||||
|
||||
/** Returns true if this scrollbar is set to auto-hide when its thumb is as big
|
||||
as its maximum range.
|
||||
@see setAutoHide
|
||||
*/
|
||||
bool autoHides() const noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** Sets the minimum and maximum values that the bar will move between.
|
||||
|
||||
The bar's thumb will always be constrained so that the entire thumb lies
|
||||
within this range.
|
||||
|
||||
@param newRangeLimit the new range.
|
||||
@param notification whether to send a notification of the change to listeners.
|
||||
A notification will only be sent if the range has changed.
|
||||
|
||||
@see setCurrentRange
|
||||
*/
|
||||
void setRangeLimits (Range<double> newRangeLimit,
|
||||
NotificationType notification = sendNotificationAsync);
|
||||
|
||||
/** Sets the minimum and maximum values that the bar will move between.
|
||||
|
||||
The bar's thumb will always be constrained so that the entire thumb lies
|
||||
within this range.
|
||||
|
||||
@param minimum the new range minimum.
|
||||
@param maximum the new range maximum.
|
||||
@param notification whether to send a notification of the change to listeners.
|
||||
A notification will only be sent if the range has changed.
|
||||
|
||||
@see setCurrentRange
|
||||
*/
|
||||
void setRangeLimits (double minimum, double maximum,
|
||||
NotificationType notification = sendNotificationAsync);
|
||||
|
||||
/** Returns the current limits on the thumb position.
|
||||
@see setRangeLimits
|
||||
*/
|
||||
Range<double> getRangeLimit() const noexcept { return totalRange; }
|
||||
|
||||
/** Returns the lower value that the thumb can be set to.
|
||||
|
||||
This is the value set by setRangeLimits().
|
||||
*/
|
||||
double getMinimumRangeLimit() const noexcept { return totalRange.getStart(); }
|
||||
|
||||
/** Returns the upper value that the thumb can be set to.
|
||||
|
||||
This is the value set by setRangeLimits().
|
||||
*/
|
||||
double getMaximumRangeLimit() const noexcept { return totalRange.getEnd(); }
|
||||
|
||||
//==============================================================================
|
||||
/** Changes the position of the scrollbar's 'thumb'.
|
||||
|
||||
This sets both the position and size of the thumb - to just set the position without
|
||||
changing the size, you can use setCurrentRangeStart().
|
||||
|
||||
If this method call actually changes the scrollbar's position, it will trigger an
|
||||
asynchronous call to ScrollBar::Listener::scrollBarMoved() for all the listeners that
|
||||
are registered.
|
||||
|
||||
The notification parameter can be used to optionally send or inhibit a callback to
|
||||
any scrollbar listeners.
|
||||
|
||||
@returns true if the range was changed, or false if nothing was changed.
|
||||
@see getCurrentRange. setCurrentRangeStart
|
||||
*/
|
||||
bool setCurrentRange (Range<double> newRange,
|
||||
NotificationType notification = sendNotificationAsync);
|
||||
|
||||
/** Changes the position of the scrollbar's 'thumb'.
|
||||
|
||||
This sets both the position and size of the thumb - to just set the position without
|
||||
changing the size, you can use setCurrentRangeStart().
|
||||
|
||||
@param newStart the top (or left) of the thumb, in the range
|
||||
getMinimumRangeLimit() <= newStart <= getMaximumRangeLimit(). If the
|
||||
value is beyond these limits, it will be clipped.
|
||||
@param newSize the size of the thumb, such that
|
||||
getMinimumRangeLimit() <= newStart + newSize <= getMaximumRangeLimit(). If the
|
||||
size is beyond these limits, it will be clipped.
|
||||
@param notification specifies if and how a callback should be made to any listeners
|
||||
if the range actually changes
|
||||
@see setCurrentRangeStart, getCurrentRangeStart, getCurrentRangeSize
|
||||
*/
|
||||
void setCurrentRange (double newStart, double newSize,
|
||||
NotificationType notification = sendNotificationAsync);
|
||||
|
||||
/** Moves the bar's thumb position.
|
||||
|
||||
This will move the thumb position without changing the thumb size. Note
|
||||
that the maximum thumb start position is (getMaximumRangeLimit() - getCurrentRangeSize()).
|
||||
|
||||
If this method call actually changes the scrollbar's position, it will trigger an
|
||||
asynchronous call to ScrollBar::Listener::scrollBarMoved() for all the listeners that
|
||||
are registered.
|
||||
|
||||
@see setCurrentRange
|
||||
*/
|
||||
void setCurrentRangeStart (double newStart,
|
||||
NotificationType notification = sendNotificationAsync);
|
||||
|
||||
/** Returns the current thumb range.
|
||||
@see getCurrentRange, setCurrentRange
|
||||
*/
|
||||
Range<double> getCurrentRange() const noexcept { return visibleRange; }
|
||||
|
||||
/** Returns the position of the top of the thumb.
|
||||
@see getCurrentRange, setCurrentRangeStart
|
||||
*/
|
||||
double getCurrentRangeStart() const noexcept { return visibleRange.getStart(); }
|
||||
|
||||
/** Returns the current size of the thumb.
|
||||
@see getCurrentRange, setCurrentRange
|
||||
*/
|
||||
double getCurrentRangeSize() const noexcept { return visibleRange.getLength(); }
|
||||
|
||||
//==============================================================================
|
||||
/** Sets the amount by which the up and down buttons will move the bar.
|
||||
|
||||
The value here is in terms of the total range, and is added or subtracted
|
||||
from the thumb position when the user clicks an up/down (or left/right) button.
|
||||
*/
|
||||
void setSingleStepSize (double newSingleStepSize) noexcept;
|
||||
|
||||
/** Returns the current step size.
|
||||
@see setSingleStepSize
|
||||
*/
|
||||
double getSingleStepSize() const noexcept { return singleStepSize; }
|
||||
|
||||
/** Moves the scrollbar by a number of single-steps.
|
||||
|
||||
This will move the bar by a multiple of its single-step interval (as
|
||||
specified using the setSingleStepSize() method).
|
||||
|
||||
A positive value here will move the bar down or to the right, a negative
|
||||
value moves it up or to the left.
|
||||
|
||||
@param howManySteps the number of steps to move the scrollbar
|
||||
@param notification whether to send a notification of the change to listeners.
|
||||
A notification will only be sent if the position has changed.
|
||||
|
||||
@returns true if the scrollbar's position actually changed.
|
||||
*/
|
||||
bool moveScrollbarInSteps (int howManySteps,
|
||||
NotificationType notification = sendNotificationAsync);
|
||||
|
||||
/** Moves the scroll bar up or down in pages.
|
||||
|
||||
This will move the bar by a multiple of its current thumb size, effectively
|
||||
doing a page-up or down.
|
||||
|
||||
A positive value here will move the bar down or to the right, a negative
|
||||
value moves it up or to the left.
|
||||
|
||||
@param howManyPages the number of pages to move the scrollbar
|
||||
@param notification whether to send a notification of the change to listeners.
|
||||
A notification will only be sent if the position has changed.
|
||||
|
||||
@returns true if the scrollbar's position actually changed.
|
||||
*/
|
||||
bool moveScrollbarInPages (int howManyPages,
|
||||
NotificationType notification = sendNotificationAsync);
|
||||
|
||||
/** Scrolls to the top (or left).
|
||||
This is the same as calling setCurrentRangeStart (getMinimumRangeLimit());
|
||||
|
||||
@param notification whether to send a notification of the change to listeners.
|
||||
A notification will only be sent if the position has changed.
|
||||
|
||||
@returns true if the scrollbar's position actually changed.
|
||||
*/
|
||||
bool scrollToTop (NotificationType notification = sendNotificationAsync);
|
||||
|
||||
/** Scrolls to the bottom (or right).
|
||||
This is the same as calling setCurrentRangeStart (getMaximumRangeLimit() - getCurrentRangeSize());
|
||||
|
||||
@param notification whether to send a notification of the change to listeners.
|
||||
A notification will only be sent if the position has changed.
|
||||
|
||||
@returns true if the scrollbar's position actually changed.
|
||||
*/
|
||||
bool scrollToBottom (NotificationType notification = sendNotificationAsync);
|
||||
|
||||
/** Changes the delay before the up and down buttons autorepeat when they are held
|
||||
down.
|
||||
|
||||
For an explanation of what the parameters are for, see Button::setRepeatSpeed().
|
||||
|
||||
@see Button::setRepeatSpeed
|
||||
*/
|
||||
void setButtonRepeatSpeed (int initialDelayInMillisecs,
|
||||
int repeatDelayInMillisecs,
|
||||
int minimumDelayInMillisecs = -1);
|
||||
|
||||
//==============================================================================
|
||||
/** A set of colour IDs to use to change the colour of various aspects of the component.
|
||||
|
||||
These constants can be used either via the Component::setColour(), or LookAndFeel::setColour()
|
||||
methods.
|
||||
|
||||
@see Component::setColour, Component::findColour, LookAndFeel::setColour, LookAndFeel::findColour
|
||||
*/
|
||||
enum ColourIds
|
||||
{
|
||||
backgroundColourId = 0x1000300, /**< The background colour of the scrollbar. */
|
||||
thumbColourId = 0x1000400, /**< A base colour to use for the thumb. The look and feel will probably use variations on this colour. */
|
||||
trackColourId = 0x1000401 /**< A base colour to use for the slot area of the bar. The look and feel will probably use variations on this colour. */
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A class for receiving events from a ScrollBar.
|
||||
|
||||
You can register a ScrollBar::Listener with a ScrollBar using the ScrollBar::addListener()
|
||||
method, and it will be called when the bar's position changes.
|
||||
|
||||
@see ScrollBar::addListener, ScrollBar::removeListener
|
||||
*/
|
||||
class JUCE_API Listener
|
||||
{
|
||||
public:
|
||||
/** Destructor. */
|
||||
virtual ~Listener() = default;
|
||||
|
||||
/** Called when a ScrollBar is moved.
|
||||
|
||||
@param scrollBarThatHasMoved the bar that has moved
|
||||
@param newRangeStart the new range start of this bar
|
||||
*/
|
||||
virtual void scrollBarMoved (ScrollBar* scrollBarThatHasMoved,
|
||||
double newRangeStart) = 0;
|
||||
};
|
||||
|
||||
/** Registers a listener that will be called when the scrollbar is moved. */
|
||||
void addListener (Listener* listener);
|
||||
|
||||
/** Deregisters a previously-registered listener. */
|
||||
void removeListener (Listener* listener);
|
||||
|
||||
//==============================================================================
|
||||
/** This abstract base class is implemented by LookAndFeel classes to provide
|
||||
scrollbar-drawing functionality.
|
||||
*/
|
||||
struct JUCE_API LookAndFeelMethods
|
||||
{
|
||||
virtual ~LookAndFeelMethods() = default;
|
||||
|
||||
virtual bool areScrollbarButtonsVisible() = 0;
|
||||
|
||||
/** Draws one of the buttons on a scrollbar.
|
||||
|
||||
@param g the context to draw into
|
||||
@param scrollbar the bar itself
|
||||
@param width the width of the button
|
||||
@param height the height of the button
|
||||
@param buttonDirection the direction of the button, where 0 = up, 1 = right, 2 = down, 3 = left
|
||||
@param isScrollbarVertical true if it's a vertical bar, false if horizontal
|
||||
@param isMouseOverButton whether the mouse is currently over the button (also true if it's held down)
|
||||
@param isButtonDown whether the mouse button's held down
|
||||
*/
|
||||
virtual void drawScrollbarButton (Graphics& g,
|
||||
ScrollBar& scrollbar,
|
||||
int width, int height,
|
||||
int buttonDirection,
|
||||
bool isScrollbarVertical,
|
||||
bool isMouseOverButton,
|
||||
bool isButtonDown) = 0;
|
||||
|
||||
/** Draws the thumb area of a scrollbar.
|
||||
|
||||
@param g the context to draw into
|
||||
@param scrollbar the bar itself
|
||||
@param x the x position of the left edge of the thumb area to draw in
|
||||
@param y the y position of the top edge of the thumb area to draw in
|
||||
@param width the width of the thumb area to draw in
|
||||
@param height the height of the thumb area to draw in
|
||||
@param isScrollbarVertical true if it's a vertical bar, false if horizontal
|
||||
@param thumbStartPosition for vertical bars, the y coordinate of the top of the
|
||||
thumb, or its x position for horizontal bars
|
||||
@param thumbSize for vertical bars, the height of the thumb, or its width for
|
||||
horizontal bars. This may be 0 if the thumb shouldn't be drawn.
|
||||
@param isMouseOver whether the mouse is over the thumb area, also true if the mouse is
|
||||
currently dragging the thumb
|
||||
@param isMouseDown whether the mouse is currently dragging the scrollbar
|
||||
*/
|
||||
virtual void drawScrollbar (Graphics& g, ScrollBar& scrollbar,
|
||||
int x, int y, int width, int height,
|
||||
bool isScrollbarVertical,
|
||||
int thumbStartPosition,
|
||||
int thumbSize,
|
||||
bool isMouseOver,
|
||||
bool isMouseDown) = 0;
|
||||
|
||||
/** Returns the component effect to use for a scrollbar */
|
||||
virtual ImageEffectFilter* getScrollbarEffect() = 0;
|
||||
|
||||
/** Returns the minimum length in pixels to use for a scrollbar thumb. */
|
||||
virtual int getMinimumScrollbarThumbSize (ScrollBar&) = 0;
|
||||
|
||||
/** Returns the default thickness to use for a scrollbar. */
|
||||
virtual int getDefaultScrollbarWidth() = 0;
|
||||
|
||||
/** Returns the length in pixels to use for a scrollbar button. */
|
||||
virtual int getScrollbarButtonSize (ScrollBar&) = 0;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** @internal */
|
||||
bool keyPressed (const KeyPress&) override;
|
||||
/** @internal */
|
||||
void mouseWheelMove (const MouseEvent&, const MouseWheelDetails&) override;
|
||||
/** @internal */
|
||||
void lookAndFeelChanged() override;
|
||||
/** @internal */
|
||||
void mouseDown (const MouseEvent&) override;
|
||||
/** @internal */
|
||||
void mouseDrag (const MouseEvent&) override;
|
||||
/** @internal */
|
||||
void mouseUp (const MouseEvent&) override;
|
||||
/** @internal */
|
||||
void paint (Graphics&) override;
|
||||
/** @internal */
|
||||
void resized() override;
|
||||
/** @internal */
|
||||
void parentHierarchyChanged() override;
|
||||
/** @internal */
|
||||
void setVisible (bool) override;
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
Range<double> totalRange { 0.0, 1.0 }, visibleRange { 0.0, 1.0 };
|
||||
double singleStepSize = 0.1, dragStartRange = 0;
|
||||
int thumbAreaStart = 0, thumbAreaSize = 0, thumbStart = 0, thumbSize = 0;
|
||||
int dragStartMousePos = 0, lastMousePos = 0;
|
||||
int initialDelayInMillisecs = 100, repeatDelayInMillisecs = 50, minimumDelayInMillisecs = 10;
|
||||
bool vertical, isDraggingThumb = false, autohides = true, userVisibilityFlag = false;
|
||||
class ScrollbarButton;
|
||||
std::unique_ptr<ScrollbarButton> upButton, downButton;
|
||||
ListenerList<Listener> listeners;
|
||||
|
||||
std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override;
|
||||
void handleAsyncUpdate() override;
|
||||
void updateThumbPosition();
|
||||
void timerCallback() override;
|
||||
bool getVisibility() const noexcept;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ScrollBar)
|
||||
};
|
||||
|
||||
|
||||
} // namespace juce
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A scrollbar component.
|
||||
|
||||
To use a scrollbar, set up its total range using the setRangeLimits() method - this
|
||||
sets the range of values it can represent. Then you can use setCurrentRange() to
|
||||
change the position and size of the scrollbar's 'thumb'.
|
||||
|
||||
Registering a ScrollBar::Listener with the scrollbar will allow you to find out when
|
||||
the user moves it, and you can use the getCurrentRangeStart() to find out where
|
||||
they moved it to.
|
||||
|
||||
The scrollbar will adjust its own visibility according to whether its thumb size
|
||||
allows it to actually be scrolled.
|
||||
|
||||
For most purposes, it's probably easier to use a Viewport or ListBox
|
||||
instead of handling a scrollbar directly.
|
||||
|
||||
@see ScrollBar::Listener
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API ScrollBar : public Component,
|
||||
public AsyncUpdater,
|
||||
private Timer
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates a Scrollbar.
|
||||
@param isVertical specifies whether the bar should be a vertical or horizontal
|
||||
*/
|
||||
ScrollBar (bool isVertical);
|
||||
|
||||
/** Destructor. */
|
||||
~ScrollBar() override;
|
||||
|
||||
//==============================================================================
|
||||
/** Returns true if the scrollbar is vertical, false if it's horizontal. */
|
||||
bool isVertical() const noexcept { return vertical; }
|
||||
|
||||
/** Changes the scrollbar's direction.
|
||||
|
||||
You'll also need to resize the bar appropriately - this just changes its internal
|
||||
layout.
|
||||
|
||||
@param shouldBeVertical true makes it vertical; false makes it horizontal.
|
||||
*/
|
||||
void setOrientation (bool shouldBeVertical);
|
||||
|
||||
/** Tells the scrollbar whether to make itself invisible when not needed.
|
||||
|
||||
The default behaviour is for a scrollbar to become invisible when the thumb
|
||||
fills the whole of its range (i.e. when it can't be moved). Setting this
|
||||
value to false forces the bar to always be visible.
|
||||
@see autoHides()
|
||||
*/
|
||||
void setAutoHide (bool shouldHideWhenFullRange);
|
||||
|
||||
/** Returns true if this scrollbar is set to auto-hide when its thumb is as big
|
||||
as its maximum range.
|
||||
@see setAutoHide
|
||||
*/
|
||||
bool autoHides() const noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** Sets the minimum and maximum values that the bar will move between.
|
||||
|
||||
The bar's thumb will always be constrained so that the entire thumb lies
|
||||
within this range.
|
||||
|
||||
@param newRangeLimit the new range.
|
||||
@param notification whether to send a notification of the change to listeners.
|
||||
A notification will only be sent if the range has changed.
|
||||
|
||||
@see setCurrentRange
|
||||
*/
|
||||
void setRangeLimits (Range<double> newRangeLimit,
|
||||
NotificationType notification = sendNotificationAsync);
|
||||
|
||||
/** Sets the minimum and maximum values that the bar will move between.
|
||||
|
||||
The bar's thumb will always be constrained so that the entire thumb lies
|
||||
within this range.
|
||||
|
||||
@param minimum the new range minimum.
|
||||
@param maximum the new range maximum.
|
||||
@param notification whether to send a notification of the change to listeners.
|
||||
A notification will only be sent if the range has changed.
|
||||
|
||||
@see setCurrentRange
|
||||
*/
|
||||
void setRangeLimits (double minimum, double maximum,
|
||||
NotificationType notification = sendNotificationAsync);
|
||||
|
||||
/** Returns the current limits on the thumb position.
|
||||
@see setRangeLimits
|
||||
*/
|
||||
Range<double> getRangeLimit() const noexcept { return totalRange; }
|
||||
|
||||
/** Returns the lower value that the thumb can be set to.
|
||||
|
||||
This is the value set by setRangeLimits().
|
||||
*/
|
||||
double getMinimumRangeLimit() const noexcept { return totalRange.getStart(); }
|
||||
|
||||
/** Returns the upper value that the thumb can be set to.
|
||||
|
||||
This is the value set by setRangeLimits().
|
||||
*/
|
||||
double getMaximumRangeLimit() const noexcept { return totalRange.getEnd(); }
|
||||
|
||||
//==============================================================================
|
||||
/** Changes the position of the scrollbar's 'thumb'.
|
||||
|
||||
This sets both the position and size of the thumb - to just set the position without
|
||||
changing the size, you can use setCurrentRangeStart().
|
||||
|
||||
If this method call actually changes the scrollbar's position, it will trigger an
|
||||
asynchronous call to ScrollBar::Listener::scrollBarMoved() for all the listeners that
|
||||
are registered.
|
||||
|
||||
The notification parameter can be used to optionally send or inhibit a callback to
|
||||
any scrollbar listeners.
|
||||
|
||||
@returns true if the range was changed, or false if nothing was changed.
|
||||
@see getCurrentRange. setCurrentRangeStart
|
||||
*/
|
||||
bool setCurrentRange (Range<double> newRange,
|
||||
NotificationType notification = sendNotificationAsync);
|
||||
|
||||
/** Changes the position of the scrollbar's 'thumb'.
|
||||
|
||||
This sets both the position and size of the thumb - to just set the position without
|
||||
changing the size, you can use setCurrentRangeStart().
|
||||
|
||||
@param newStart the top (or left) of the thumb, in the range
|
||||
getMinimumRangeLimit() <= newStart <= getMaximumRangeLimit(). If the
|
||||
value is beyond these limits, it will be clipped.
|
||||
@param newSize the size of the thumb, such that
|
||||
getMinimumRangeLimit() <= newStart + newSize <= getMaximumRangeLimit(). If the
|
||||
size is beyond these limits, it will be clipped.
|
||||
@param notification specifies if and how a callback should be made to any listeners
|
||||
if the range actually changes
|
||||
@see setCurrentRangeStart, getCurrentRangeStart, getCurrentRangeSize
|
||||
*/
|
||||
void setCurrentRange (double newStart, double newSize,
|
||||
NotificationType notification = sendNotificationAsync);
|
||||
|
||||
/** Moves the bar's thumb position.
|
||||
|
||||
This will move the thumb position without changing the thumb size. Note
|
||||
that the maximum thumb start position is (getMaximumRangeLimit() - getCurrentRangeSize()).
|
||||
|
||||
If this method call actually changes the scrollbar's position, it will trigger an
|
||||
asynchronous call to ScrollBar::Listener::scrollBarMoved() for all the listeners that
|
||||
are registered.
|
||||
|
||||
@see setCurrentRange
|
||||
*/
|
||||
void setCurrentRangeStart (double newStart,
|
||||
NotificationType notification = sendNotificationAsync);
|
||||
|
||||
/** Returns the current thumb range.
|
||||
@see getCurrentRange, setCurrentRange
|
||||
*/
|
||||
Range<double> getCurrentRange() const noexcept { return visibleRange; }
|
||||
|
||||
/** Returns the position of the top of the thumb.
|
||||
@see getCurrentRange, setCurrentRangeStart
|
||||
*/
|
||||
double getCurrentRangeStart() const noexcept { return visibleRange.getStart(); }
|
||||
|
||||
/** Returns the current size of the thumb.
|
||||
@see getCurrentRange, setCurrentRange
|
||||
*/
|
||||
double getCurrentRangeSize() const noexcept { return visibleRange.getLength(); }
|
||||
|
||||
//==============================================================================
|
||||
/** Sets the amount by which the up and down buttons will move the bar.
|
||||
|
||||
The value here is in terms of the total range, and is added or subtracted
|
||||
from the thumb position when the user clicks an up/down (or left/right) button.
|
||||
*/
|
||||
void setSingleStepSize (double newSingleStepSize) noexcept;
|
||||
|
||||
/** Returns the current step size.
|
||||
@see setSingleStepSize
|
||||
*/
|
||||
double getSingleStepSize() const noexcept { return singleStepSize; }
|
||||
|
||||
/** Moves the scrollbar by a number of single-steps.
|
||||
|
||||
This will move the bar by a multiple of its single-step interval (as
|
||||
specified using the setSingleStepSize() method).
|
||||
|
||||
A positive value here will move the bar down or to the right, a negative
|
||||
value moves it up or to the left.
|
||||
|
||||
@param howManySteps the number of steps to move the scrollbar
|
||||
@param notification whether to send a notification of the change to listeners.
|
||||
A notification will only be sent if the position has changed.
|
||||
|
||||
@returns true if the scrollbar's position actually changed.
|
||||
*/
|
||||
bool moveScrollbarInSteps (int howManySteps,
|
||||
NotificationType notification = sendNotificationAsync);
|
||||
|
||||
/** Moves the scroll bar up or down in pages.
|
||||
|
||||
This will move the bar by a multiple of its current thumb size, effectively
|
||||
doing a page-up or down.
|
||||
|
||||
A positive value here will move the bar down or to the right, a negative
|
||||
value moves it up or to the left.
|
||||
|
||||
@param howManyPages the number of pages to move the scrollbar
|
||||
@param notification whether to send a notification of the change to listeners.
|
||||
A notification will only be sent if the position has changed.
|
||||
|
||||
@returns true if the scrollbar's position actually changed.
|
||||
*/
|
||||
bool moveScrollbarInPages (int howManyPages,
|
||||
NotificationType notification = sendNotificationAsync);
|
||||
|
||||
/** Scrolls to the top (or left).
|
||||
This is the same as calling setCurrentRangeStart (getMinimumRangeLimit());
|
||||
|
||||
@param notification whether to send a notification of the change to listeners.
|
||||
A notification will only be sent if the position has changed.
|
||||
|
||||
@returns true if the scrollbar's position actually changed.
|
||||
*/
|
||||
bool scrollToTop (NotificationType notification = sendNotificationAsync);
|
||||
|
||||
/** Scrolls to the bottom (or right).
|
||||
This is the same as calling setCurrentRangeStart (getMaximumRangeLimit() - getCurrentRangeSize());
|
||||
|
||||
@param notification whether to send a notification of the change to listeners.
|
||||
A notification will only be sent if the position has changed.
|
||||
|
||||
@returns true if the scrollbar's position actually changed.
|
||||
*/
|
||||
bool scrollToBottom (NotificationType notification = sendNotificationAsync);
|
||||
|
||||
/** Changes the delay before the up and down buttons autorepeat when they are held
|
||||
down.
|
||||
|
||||
For an explanation of what the parameters are for, see Button::setRepeatSpeed().
|
||||
|
||||
@see Button::setRepeatSpeed
|
||||
*/
|
||||
void setButtonRepeatSpeed (int initialDelayInMillisecs,
|
||||
int repeatDelayInMillisecs,
|
||||
int minimumDelayInMillisecs = -1);
|
||||
|
||||
//==============================================================================
|
||||
/** A set of colour IDs to use to change the colour of various aspects of the component.
|
||||
|
||||
These constants can be used either via the Component::setColour(), or LookAndFeel::setColour()
|
||||
methods.
|
||||
|
||||
@see Component::setColour, Component::findColour, LookAndFeel::setColour, LookAndFeel::findColour
|
||||
*/
|
||||
enum ColourIds
|
||||
{
|
||||
backgroundColourId = 0x1000300, /**< The background colour of the scrollbar. */
|
||||
thumbColourId = 0x1000400, /**< A base colour to use for the thumb. The look and feel will probably use variations on this colour. */
|
||||
trackColourId = 0x1000401 /**< A base colour to use for the slot area of the bar. The look and feel will probably use variations on this colour. */
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A class for receiving events from a ScrollBar.
|
||||
|
||||
You can register a ScrollBar::Listener with a ScrollBar using the ScrollBar::addListener()
|
||||
method, and it will be called when the bar's position changes.
|
||||
|
||||
@see ScrollBar::addListener, ScrollBar::removeListener
|
||||
*/
|
||||
class JUCE_API Listener
|
||||
{
|
||||
public:
|
||||
/** Destructor. */
|
||||
virtual ~Listener() = default;
|
||||
|
||||
/** Called when a ScrollBar is moved.
|
||||
|
||||
@param scrollBarThatHasMoved the bar that has moved
|
||||
@param newRangeStart the new range start of this bar
|
||||
*/
|
||||
virtual void scrollBarMoved (ScrollBar* scrollBarThatHasMoved,
|
||||
double newRangeStart) = 0;
|
||||
};
|
||||
|
||||
/** Registers a listener that will be called when the scrollbar is moved. */
|
||||
void addListener (Listener* listener);
|
||||
|
||||
/** Deregisters a previously-registered listener. */
|
||||
void removeListener (Listener* listener);
|
||||
|
||||
//==============================================================================
|
||||
/** This abstract base class is implemented by LookAndFeel classes to provide
|
||||
scrollbar-drawing functionality.
|
||||
*/
|
||||
struct JUCE_API LookAndFeelMethods
|
||||
{
|
||||
virtual ~LookAndFeelMethods() = default;
|
||||
|
||||
virtual bool areScrollbarButtonsVisible() = 0;
|
||||
|
||||
/** Draws one of the buttons on a scrollbar.
|
||||
|
||||
@param g the context to draw into
|
||||
@param scrollbar the bar itself
|
||||
@param width the width of the button
|
||||
@param height the height of the button
|
||||
@param buttonDirection the direction of the button, where 0 = up, 1 = right, 2 = down, 3 = left
|
||||
@param isScrollbarVertical true if it's a vertical bar, false if horizontal
|
||||
@param isMouseOverButton whether the mouse is currently over the button (also true if it's held down)
|
||||
@param isButtonDown whether the mouse button's held down
|
||||
*/
|
||||
virtual void drawScrollbarButton (Graphics& g,
|
||||
ScrollBar& scrollbar,
|
||||
int width, int height,
|
||||
int buttonDirection,
|
||||
bool isScrollbarVertical,
|
||||
bool isMouseOverButton,
|
||||
bool isButtonDown) = 0;
|
||||
|
||||
/** Draws the thumb area of a scrollbar.
|
||||
|
||||
@param g the context to draw into
|
||||
@param scrollbar the bar itself
|
||||
@param x the x position of the left edge of the thumb area to draw in
|
||||
@param y the y position of the top edge of the thumb area to draw in
|
||||
@param width the width of the thumb area to draw in
|
||||
@param height the height of the thumb area to draw in
|
||||
@param isScrollbarVertical true if it's a vertical bar, false if horizontal
|
||||
@param thumbStartPosition for vertical bars, the y coordinate of the top of the
|
||||
thumb, or its x position for horizontal bars
|
||||
@param thumbSize for vertical bars, the height of the thumb, or its width for
|
||||
horizontal bars. This may be 0 if the thumb shouldn't be drawn.
|
||||
@param isMouseOver whether the mouse is over the thumb area, also true if the mouse is
|
||||
currently dragging the thumb
|
||||
@param isMouseDown whether the mouse is currently dragging the scrollbar
|
||||
*/
|
||||
virtual void drawScrollbar (Graphics& g, ScrollBar& scrollbar,
|
||||
int x, int y, int width, int height,
|
||||
bool isScrollbarVertical,
|
||||
int thumbStartPosition,
|
||||
int thumbSize,
|
||||
bool isMouseOver,
|
||||
bool isMouseDown) = 0;
|
||||
|
||||
/** Returns the component effect to use for a scrollbar */
|
||||
virtual ImageEffectFilter* getScrollbarEffect() = 0;
|
||||
|
||||
/** Returns the minimum length in pixels to use for a scrollbar thumb. */
|
||||
virtual int getMinimumScrollbarThumbSize (ScrollBar&) = 0;
|
||||
|
||||
/** Returns the default thickness to use for a scrollbar. */
|
||||
virtual int getDefaultScrollbarWidth() = 0;
|
||||
|
||||
/** Returns the length in pixels to use for a scrollbar button. */
|
||||
virtual int getScrollbarButtonSize (ScrollBar&) = 0;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** @internal */
|
||||
bool keyPressed (const KeyPress&) override;
|
||||
/** @internal */
|
||||
void mouseWheelMove (const MouseEvent&, const MouseWheelDetails&) override;
|
||||
/** @internal */
|
||||
void lookAndFeelChanged() override;
|
||||
/** @internal */
|
||||
void mouseDown (const MouseEvent&) override;
|
||||
/** @internal */
|
||||
void mouseDrag (const MouseEvent&) override;
|
||||
/** @internal */
|
||||
void mouseUp (const MouseEvent&) override;
|
||||
/** @internal */
|
||||
void paint (Graphics&) override;
|
||||
/** @internal */
|
||||
void resized() override;
|
||||
/** @internal */
|
||||
void parentHierarchyChanged() override;
|
||||
/** @internal */
|
||||
void setVisible (bool) override;
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
Range<double> totalRange { 0.0, 1.0 }, visibleRange { 0.0, 1.0 };
|
||||
double singleStepSize = 0.1, dragStartRange = 0;
|
||||
int thumbAreaStart = 0, thumbAreaSize = 0, thumbStart = 0, thumbSize = 0;
|
||||
int dragStartMousePos = 0, lastMousePos = 0;
|
||||
int initialDelayInMillisecs = 100, repeatDelayInMillisecs = 50, minimumDelayInMillisecs = 10;
|
||||
bool vertical, isDraggingThumb = false, autohides = true, userVisibilityFlag = false;
|
||||
class ScrollbarButton;
|
||||
std::unique_ptr<ScrollbarButton> upButton, downButton;
|
||||
ListenerList<Listener> listeners;
|
||||
|
||||
std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override;
|
||||
void handleAsyncUpdate() override;
|
||||
void updateThumbPosition();
|
||||
void timerCallback() override;
|
||||
bool getVisibility() const noexcept;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ScrollBar)
|
||||
};
|
||||
|
||||
|
||||
} // namespace juce
|
||||
|
@ -1,303 +1,303 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
SidePanel::SidePanel (StringRef title, int width, bool positionOnLeft,
|
||||
Component* contentToDisplay, bool deleteComponentWhenNoLongerNeeded)
|
||||
: titleLabel ("titleLabel", title),
|
||||
isOnLeft (positionOnLeft),
|
||||
panelWidth (width)
|
||||
{
|
||||
lookAndFeelChanged();
|
||||
|
||||
addAndMakeVisible (titleLabel);
|
||||
|
||||
dismissButton.onClick = [this] { showOrHide (false); };
|
||||
addAndMakeVisible (dismissButton);
|
||||
|
||||
auto& desktop = Desktop::getInstance();
|
||||
|
||||
desktop.addGlobalMouseListener (this);
|
||||
desktop.getAnimator().addChangeListener (this);
|
||||
|
||||
if (contentToDisplay != nullptr)
|
||||
setContent (contentToDisplay, deleteComponentWhenNoLongerNeeded);
|
||||
|
||||
setOpaque (false);
|
||||
setVisible (false);
|
||||
setAlwaysOnTop (true);
|
||||
}
|
||||
|
||||
SidePanel::~SidePanel()
|
||||
{
|
||||
auto& desktop = Desktop::getInstance();
|
||||
|
||||
desktop.removeGlobalMouseListener (this);
|
||||
desktop.getAnimator().removeChangeListener (this);
|
||||
|
||||
if (parent != nullptr)
|
||||
parent->removeComponentListener (this);
|
||||
}
|
||||
|
||||
void SidePanel::setContent (Component* newContent, bool deleteComponentWhenNoLongerNeeded)
|
||||
{
|
||||
if (contentComponent.get() != newContent)
|
||||
{
|
||||
if (deleteComponentWhenNoLongerNeeded)
|
||||
contentComponent.setOwned (newContent);
|
||||
else
|
||||
contentComponent.setNonOwned (newContent);
|
||||
|
||||
addAndMakeVisible (contentComponent);
|
||||
|
||||
resized();
|
||||
}
|
||||
}
|
||||
|
||||
void SidePanel::setTitleBarComponent (Component* titleBarComponentToUse,
|
||||
bool keepDismissButton,
|
||||
bool deleteComponentWhenNoLongerNeeded)
|
||||
{
|
||||
if (titleBarComponent.get() != titleBarComponentToUse)
|
||||
{
|
||||
if (deleteComponentWhenNoLongerNeeded)
|
||||
titleBarComponent.setOwned (titleBarComponentToUse);
|
||||
else
|
||||
titleBarComponent.setNonOwned (titleBarComponentToUse);
|
||||
|
||||
addAndMakeVisible (titleBarComponent);
|
||||
|
||||
resized();
|
||||
}
|
||||
|
||||
shouldShowDismissButton = keepDismissButton;
|
||||
}
|
||||
|
||||
void SidePanel::showOrHide (bool show)
|
||||
{
|
||||
if (parent != nullptr)
|
||||
{
|
||||
isShowing = show;
|
||||
|
||||
Desktop::getInstance().getAnimator().animateComponent (this, calculateBoundsInParent (*parent),
|
||||
1.0f, 250, true, 1.0, 0.0);
|
||||
|
||||
if (isShowing && ! isVisible())
|
||||
setVisible (true);
|
||||
}
|
||||
}
|
||||
|
||||
void SidePanel::moved()
|
||||
{
|
||||
if (onPanelMove != nullptr)
|
||||
onPanelMove();
|
||||
}
|
||||
|
||||
void SidePanel::resized()
|
||||
{
|
||||
auto bounds = getLocalBounds();
|
||||
|
||||
calculateAndRemoveShadowBounds (bounds);
|
||||
|
||||
auto titleBounds = bounds.removeFromTop (titleBarHeight);
|
||||
|
||||
if (titleBarComponent != nullptr)
|
||||
{
|
||||
if (shouldShowDismissButton)
|
||||
dismissButton.setBounds (isOnLeft ? titleBounds.removeFromRight (30).withTrimmedRight (10)
|
||||
: titleBounds.removeFromLeft (30).withTrimmedLeft (10));
|
||||
|
||||
titleBarComponent->setBounds (titleBounds);
|
||||
}
|
||||
else
|
||||
{
|
||||
dismissButton.setBounds (isOnLeft ? titleBounds.removeFromRight (30).withTrimmedRight (10)
|
||||
: titleBounds.removeFromLeft (30).withTrimmedLeft (10));
|
||||
|
||||
titleLabel.setBounds (isOnLeft ? titleBounds.withTrimmedRight (40)
|
||||
: titleBounds.withTrimmedLeft (40));
|
||||
}
|
||||
|
||||
if (contentComponent != nullptr)
|
||||
contentComponent->setBounds (bounds);
|
||||
}
|
||||
|
||||
void SidePanel::paint (Graphics& g)
|
||||
{
|
||||
auto& lf = getLookAndFeel();
|
||||
|
||||
auto bgColour = lf.findColour (SidePanel::backgroundColour);
|
||||
auto shadowColour = lf.findColour (SidePanel::shadowBaseColour);
|
||||
|
||||
g.setGradientFill (ColourGradient (shadowColour.withAlpha (0.7f), (isOnLeft ? shadowArea.getTopLeft()
|
||||
: shadowArea.getTopRight()).toFloat(),
|
||||
shadowColour.withAlpha (0.0f), (isOnLeft ? shadowArea.getTopRight()
|
||||
: shadowArea.getTopLeft()).toFloat(), false));
|
||||
g.fillRect (shadowArea);
|
||||
|
||||
g.excludeClipRegion (shadowArea);
|
||||
g.fillAll (bgColour);
|
||||
}
|
||||
|
||||
void SidePanel::parentHierarchyChanged()
|
||||
{
|
||||
auto* newParent = getParentComponent();
|
||||
|
||||
if ((newParent != nullptr) && (parent != newParent))
|
||||
{
|
||||
if (parent != nullptr)
|
||||
parent->removeComponentListener (this);
|
||||
|
||||
parent = newParent;
|
||||
parent->addComponentListener (this);
|
||||
}
|
||||
}
|
||||
|
||||
void SidePanel::mouseDrag (const MouseEvent& e)
|
||||
{
|
||||
if (shouldResize)
|
||||
{
|
||||
Point<int> convertedPoint;
|
||||
|
||||
if (getParentComponent() == nullptr)
|
||||
convertedPoint = e.eventComponent->localPointToGlobal (e.getPosition());
|
||||
else
|
||||
convertedPoint = getParentComponent()->getLocalPoint (e.eventComponent, e.getPosition());
|
||||
|
||||
auto currentMouseDragX = convertedPoint.x;
|
||||
|
||||
if (isOnLeft)
|
||||
{
|
||||
amountMoved = startingBounds.getRight() - currentMouseDragX;
|
||||
setBounds (getBounds().withX (startingBounds.getX() - jmax (amountMoved, 0)));
|
||||
}
|
||||
else
|
||||
{
|
||||
amountMoved = currentMouseDragX - startingBounds.getX();
|
||||
setBounds (getBounds().withX (startingBounds.getX() + jmax (amountMoved, 0)));
|
||||
}
|
||||
}
|
||||
else if (isShowing)
|
||||
{
|
||||
auto relativeMouseDownPosition = getLocalPoint (e.eventComponent, e.getMouseDownPosition());
|
||||
auto relativeMouseDragPosition = getLocalPoint (e.eventComponent, e.getPosition());
|
||||
|
||||
if (! getLocalBounds().contains (relativeMouseDownPosition)
|
||||
&& getLocalBounds().contains (relativeMouseDragPosition))
|
||||
{
|
||||
shouldResize = true;
|
||||
startingBounds = getBounds();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SidePanel::mouseUp (const MouseEvent&)
|
||||
{
|
||||
if (shouldResize)
|
||||
{
|
||||
showOrHide (amountMoved < (panelWidth / 2));
|
||||
|
||||
amountMoved = 0;
|
||||
shouldResize = false;
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void SidePanel::lookAndFeelChanged()
|
||||
{
|
||||
auto& lf = getLookAndFeel();
|
||||
|
||||
dismissButton.setShape (lf.getSidePanelDismissButtonShape (*this), false, true, false);
|
||||
|
||||
dismissButton.setColours (lf.findColour (SidePanel::dismissButtonNormalColour),
|
||||
lf.findColour (SidePanel::dismissButtonOverColour),
|
||||
lf.findColour (SidePanel::dismissButtonDownColour));
|
||||
|
||||
titleLabel.setFont (lf.getSidePanelTitleFont (*this));
|
||||
titleLabel.setColour (Label::textColourId, findColour (SidePanel::titleTextColour));
|
||||
titleLabel.setJustificationType (lf.getSidePanelTitleJustification (*this));
|
||||
}
|
||||
|
||||
void SidePanel::componentMovedOrResized (Component& component, bool wasMoved, bool wasResized)
|
||||
{
|
||||
ignoreUnused (wasMoved);
|
||||
|
||||
if (wasResized && (&component == parent))
|
||||
setBounds (calculateBoundsInParent (component));
|
||||
}
|
||||
|
||||
void SidePanel::changeListenerCallback (ChangeBroadcaster*)
|
||||
{
|
||||
if (! Desktop::getInstance().getAnimator().isAnimating (this))
|
||||
{
|
||||
if (onPanelShowHide != nullptr)
|
||||
onPanelShowHide (isShowing);
|
||||
|
||||
if (isVisible() && ! isShowing)
|
||||
setVisible (false);
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle<int> SidePanel::calculateBoundsInParent (Component& parentComp) const
|
||||
{
|
||||
auto parentBounds = parentComp.getLocalBounds();
|
||||
|
||||
if (isOnLeft)
|
||||
{
|
||||
return isShowing ? parentBounds.removeFromLeft (panelWidth)
|
||||
: parentBounds.withX (parentBounds.getX() - panelWidth).withWidth (panelWidth);
|
||||
}
|
||||
|
||||
return isShowing ? parentBounds.removeFromRight (panelWidth)
|
||||
: parentBounds.withX (parentBounds.getRight()).withWidth (panelWidth);
|
||||
}
|
||||
|
||||
void SidePanel::calculateAndRemoveShadowBounds (Rectangle<int>& bounds)
|
||||
{
|
||||
shadowArea = isOnLeft ? bounds.removeFromRight (shadowWidth)
|
||||
: bounds.removeFromLeft (shadowWidth);
|
||||
}
|
||||
|
||||
bool SidePanel::isMouseEventInThisOrChildren (Component* eventComponent)
|
||||
{
|
||||
if (eventComponent == this)
|
||||
return true;
|
||||
|
||||
for (auto& child : getChildren())
|
||||
if (eventComponent == child)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
std::unique_ptr<AccessibilityHandler> SidePanel::createAccessibilityHandler()
|
||||
{
|
||||
return std::make_unique<AccessibilityHandler> (*this, AccessibilityRole::group);
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
SidePanel::SidePanel (StringRef title, int width, bool positionOnLeft,
|
||||
Component* contentToDisplay, bool deleteComponentWhenNoLongerNeeded)
|
||||
: titleLabel ("titleLabel", title),
|
||||
isOnLeft (positionOnLeft),
|
||||
panelWidth (width)
|
||||
{
|
||||
lookAndFeelChanged();
|
||||
|
||||
addAndMakeVisible (titleLabel);
|
||||
|
||||
dismissButton.onClick = [this] { showOrHide (false); };
|
||||
addAndMakeVisible (dismissButton);
|
||||
|
||||
auto& desktop = Desktop::getInstance();
|
||||
|
||||
desktop.addGlobalMouseListener (this);
|
||||
desktop.getAnimator().addChangeListener (this);
|
||||
|
||||
if (contentToDisplay != nullptr)
|
||||
setContent (contentToDisplay, deleteComponentWhenNoLongerNeeded);
|
||||
|
||||
setOpaque (false);
|
||||
setVisible (false);
|
||||
setAlwaysOnTop (true);
|
||||
}
|
||||
|
||||
SidePanel::~SidePanel()
|
||||
{
|
||||
auto& desktop = Desktop::getInstance();
|
||||
|
||||
desktop.removeGlobalMouseListener (this);
|
||||
desktop.getAnimator().removeChangeListener (this);
|
||||
|
||||
if (parent != nullptr)
|
||||
parent->removeComponentListener (this);
|
||||
}
|
||||
|
||||
void SidePanel::setContent (Component* newContent, bool deleteComponentWhenNoLongerNeeded)
|
||||
{
|
||||
if (contentComponent.get() != newContent)
|
||||
{
|
||||
if (deleteComponentWhenNoLongerNeeded)
|
||||
contentComponent.setOwned (newContent);
|
||||
else
|
||||
contentComponent.setNonOwned (newContent);
|
||||
|
||||
addAndMakeVisible (contentComponent);
|
||||
|
||||
resized();
|
||||
}
|
||||
}
|
||||
|
||||
void SidePanel::setTitleBarComponent (Component* titleBarComponentToUse,
|
||||
bool keepDismissButton,
|
||||
bool deleteComponentWhenNoLongerNeeded)
|
||||
{
|
||||
if (titleBarComponent.get() != titleBarComponentToUse)
|
||||
{
|
||||
if (deleteComponentWhenNoLongerNeeded)
|
||||
titleBarComponent.setOwned (titleBarComponentToUse);
|
||||
else
|
||||
titleBarComponent.setNonOwned (titleBarComponentToUse);
|
||||
|
||||
addAndMakeVisible (titleBarComponent);
|
||||
|
||||
resized();
|
||||
}
|
||||
|
||||
shouldShowDismissButton = keepDismissButton;
|
||||
}
|
||||
|
||||
void SidePanel::showOrHide (bool show)
|
||||
{
|
||||
if (parent != nullptr)
|
||||
{
|
||||
isShowing = show;
|
||||
|
||||
Desktop::getInstance().getAnimator().animateComponent (this, calculateBoundsInParent (*parent),
|
||||
1.0f, 250, true, 1.0, 0.0);
|
||||
|
||||
if (isShowing && ! isVisible())
|
||||
setVisible (true);
|
||||
}
|
||||
}
|
||||
|
||||
void SidePanel::moved()
|
||||
{
|
||||
if (onPanelMove != nullptr)
|
||||
onPanelMove();
|
||||
}
|
||||
|
||||
void SidePanel::resized()
|
||||
{
|
||||
auto bounds = getLocalBounds();
|
||||
|
||||
calculateAndRemoveShadowBounds (bounds);
|
||||
|
||||
auto titleBounds = bounds.removeFromTop (titleBarHeight);
|
||||
|
||||
if (titleBarComponent != nullptr)
|
||||
{
|
||||
if (shouldShowDismissButton)
|
||||
dismissButton.setBounds (isOnLeft ? titleBounds.removeFromRight (30).withTrimmedRight (10)
|
||||
: titleBounds.removeFromLeft (30).withTrimmedLeft (10));
|
||||
|
||||
titleBarComponent->setBounds (titleBounds);
|
||||
}
|
||||
else
|
||||
{
|
||||
dismissButton.setBounds (isOnLeft ? titleBounds.removeFromRight (30).withTrimmedRight (10)
|
||||
: titleBounds.removeFromLeft (30).withTrimmedLeft (10));
|
||||
|
||||
titleLabel.setBounds (isOnLeft ? titleBounds.withTrimmedRight (40)
|
||||
: titleBounds.withTrimmedLeft (40));
|
||||
}
|
||||
|
||||
if (contentComponent != nullptr)
|
||||
contentComponent->setBounds (bounds);
|
||||
}
|
||||
|
||||
void SidePanel::paint (Graphics& g)
|
||||
{
|
||||
auto& lf = getLookAndFeel();
|
||||
|
||||
auto bgColour = lf.findColour (SidePanel::backgroundColour);
|
||||
auto shadowColour = lf.findColour (SidePanel::shadowBaseColour);
|
||||
|
||||
g.setGradientFill (ColourGradient (shadowColour.withAlpha (0.7f), (isOnLeft ? shadowArea.getTopLeft()
|
||||
: shadowArea.getTopRight()).toFloat(),
|
||||
shadowColour.withAlpha (0.0f), (isOnLeft ? shadowArea.getTopRight()
|
||||
: shadowArea.getTopLeft()).toFloat(), false));
|
||||
g.fillRect (shadowArea);
|
||||
|
||||
g.excludeClipRegion (shadowArea);
|
||||
g.fillAll (bgColour);
|
||||
}
|
||||
|
||||
void SidePanel::parentHierarchyChanged()
|
||||
{
|
||||
auto* newParent = getParentComponent();
|
||||
|
||||
if ((newParent != nullptr) && (parent != newParent))
|
||||
{
|
||||
if (parent != nullptr)
|
||||
parent->removeComponentListener (this);
|
||||
|
||||
parent = newParent;
|
||||
parent->addComponentListener (this);
|
||||
}
|
||||
}
|
||||
|
||||
void SidePanel::mouseDrag (const MouseEvent& e)
|
||||
{
|
||||
if (shouldResize)
|
||||
{
|
||||
Point<int> convertedPoint;
|
||||
|
||||
if (getParentComponent() == nullptr)
|
||||
convertedPoint = e.eventComponent->localPointToGlobal (e.getPosition());
|
||||
else
|
||||
convertedPoint = getParentComponent()->getLocalPoint (e.eventComponent, e.getPosition());
|
||||
|
||||
auto currentMouseDragX = convertedPoint.x;
|
||||
|
||||
if (isOnLeft)
|
||||
{
|
||||
amountMoved = startingBounds.getRight() - currentMouseDragX;
|
||||
setBounds (getBounds().withX (startingBounds.getX() - jmax (amountMoved, 0)));
|
||||
}
|
||||
else
|
||||
{
|
||||
amountMoved = currentMouseDragX - startingBounds.getX();
|
||||
setBounds (getBounds().withX (startingBounds.getX() + jmax (amountMoved, 0)));
|
||||
}
|
||||
}
|
||||
else if (isShowing)
|
||||
{
|
||||
auto relativeMouseDownPosition = getLocalPoint (e.eventComponent, e.getMouseDownPosition());
|
||||
auto relativeMouseDragPosition = getLocalPoint (e.eventComponent, e.getPosition());
|
||||
|
||||
if (! getLocalBounds().contains (relativeMouseDownPosition)
|
||||
&& getLocalBounds().contains (relativeMouseDragPosition))
|
||||
{
|
||||
shouldResize = true;
|
||||
startingBounds = getBounds();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SidePanel::mouseUp (const MouseEvent&)
|
||||
{
|
||||
if (shouldResize)
|
||||
{
|
||||
showOrHide (amountMoved < (panelWidth / 2));
|
||||
|
||||
amountMoved = 0;
|
||||
shouldResize = false;
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void SidePanel::lookAndFeelChanged()
|
||||
{
|
||||
auto& lf = getLookAndFeel();
|
||||
|
||||
dismissButton.setShape (lf.getSidePanelDismissButtonShape (*this), false, true, false);
|
||||
|
||||
dismissButton.setColours (lf.findColour (SidePanel::dismissButtonNormalColour),
|
||||
lf.findColour (SidePanel::dismissButtonOverColour),
|
||||
lf.findColour (SidePanel::dismissButtonDownColour));
|
||||
|
||||
titleLabel.setFont (lf.getSidePanelTitleFont (*this));
|
||||
titleLabel.setColour (Label::textColourId, findColour (SidePanel::titleTextColour));
|
||||
titleLabel.setJustificationType (lf.getSidePanelTitleJustification (*this));
|
||||
}
|
||||
|
||||
void SidePanel::componentMovedOrResized (Component& component, bool wasMoved, bool wasResized)
|
||||
{
|
||||
ignoreUnused (wasMoved);
|
||||
|
||||
if (wasResized && (&component == parent))
|
||||
setBounds (calculateBoundsInParent (component));
|
||||
}
|
||||
|
||||
void SidePanel::changeListenerCallback (ChangeBroadcaster*)
|
||||
{
|
||||
if (! Desktop::getInstance().getAnimator().isAnimating (this))
|
||||
{
|
||||
if (onPanelShowHide != nullptr)
|
||||
onPanelShowHide (isShowing);
|
||||
|
||||
if (isVisible() && ! isShowing)
|
||||
setVisible (false);
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle<int> SidePanel::calculateBoundsInParent (Component& parentComp) const
|
||||
{
|
||||
auto parentBounds = parentComp.getLocalBounds();
|
||||
|
||||
if (isOnLeft)
|
||||
{
|
||||
return isShowing ? parentBounds.removeFromLeft (panelWidth)
|
||||
: parentBounds.withX (parentBounds.getX() - panelWidth).withWidth (panelWidth);
|
||||
}
|
||||
|
||||
return isShowing ? parentBounds.removeFromRight (panelWidth)
|
||||
: parentBounds.withX (parentBounds.getRight()).withWidth (panelWidth);
|
||||
}
|
||||
|
||||
void SidePanel::calculateAndRemoveShadowBounds (Rectangle<int>& bounds)
|
||||
{
|
||||
shadowArea = isOnLeft ? bounds.removeFromRight (shadowWidth)
|
||||
: bounds.removeFromLeft (shadowWidth);
|
||||
}
|
||||
|
||||
bool SidePanel::isMouseEventInThisOrChildren (Component* eventComponent)
|
||||
{
|
||||
if (eventComponent == this)
|
||||
return true;
|
||||
|
||||
for (auto& child : getChildren())
|
||||
if (eventComponent == child)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
std::unique_ptr<AccessibilityHandler> SidePanel::createAccessibilityHandler()
|
||||
{
|
||||
return std::make_unique<AccessibilityHandler> (*this, AccessibilityRole::group);
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
|
@ -1,238 +1,238 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A component that is positioned on either the left- or right-hand side of its parent,
|
||||
containing a header and some content. This sort of component is typically used for
|
||||
navigation and forms in mobile applications.
|
||||
|
||||
When triggered with the showOrHide() method, the SidePanel will animate itself to its
|
||||
new position. This component also contains some logic to reactively resize and dismiss
|
||||
itself when the user drags it.
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class SidePanel : public Component,
|
||||
private ComponentListener,
|
||||
private ChangeListener
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates a SidePanel component.
|
||||
|
||||
@param title the text to use for the SidePanel's title bar
|
||||
@param width the width of the SidePanel
|
||||
@param positionOnLeft if true, the SidePanel will be positioned on the left of its parent component and
|
||||
if false, the SidePanel will be positioned on the right of its parent component
|
||||
@param contentComponent the component to add to this SidePanel - this content will take up the full
|
||||
size of the SidePanel, minus the height of the title bar. You can pass nullptr
|
||||
to this if you like and set the content component later using the setContent() method
|
||||
@param deleteComponentWhenNoLongerNeeded if true, the component will be deleted automatically when
|
||||
the SidePanel is deleted or when a different component is added. If false,
|
||||
the caller must manage the lifetime of the component
|
||||
*/
|
||||
SidePanel (StringRef title, int width, bool positionOnLeft,
|
||||
Component* contentComponent = nullptr,
|
||||
bool deleteComponentWhenNoLongerNeeded = true);
|
||||
|
||||
/** Destructor */
|
||||
~SidePanel() override;
|
||||
|
||||
//==============================================================================
|
||||
/** Sets the component that this SidePanel will contain.
|
||||
|
||||
This will add the given component to this SidePanel and position it below the title bar.
|
||||
|
||||
(Don't add or remove any child components directly using the normal
|
||||
Component::addChildComponent() methods).
|
||||
|
||||
@param newContentComponent the component to add to this SidePanel, or nullptr to remove
|
||||
the current component.
|
||||
@param deleteComponentWhenNoLongerNeeded if true, the component will be deleted automatically when
|
||||
the SidePanel is deleted or when a different component is added. If false,
|
||||
the caller must manage the lifetime of the component
|
||||
|
||||
@see getContent
|
||||
*/
|
||||
void setContent (Component* newContentComponent,
|
||||
bool deleteComponentWhenNoLongerNeeded = true);
|
||||
|
||||
/** Returns the component that's currently being used inside the SidePanel.
|
||||
|
||||
@see setViewedComponent
|
||||
*/
|
||||
Component* getContent() const noexcept { return contentComponent.get(); }
|
||||
|
||||
/** Sets a custom component to be used for the title bar of this SidePanel, replacing
|
||||
the default. You can pass a nullptr to revert to the default title bar.
|
||||
|
||||
@param titleBarComponentToUse the component to use as the title bar, or nullptr to use
|
||||
the default
|
||||
@param keepDismissButton if false the specified component will take up the full width of
|
||||
the title bar including the dismiss button but if true, the default
|
||||
dismiss button will be kept
|
||||
@param deleteComponentWhenNoLongerNeeded if true, the component will be deleted automatically when
|
||||
the SidePanel is deleted or when a different component is added. If false,
|
||||
the caller must manage the lifetime of the component
|
||||
|
||||
@see getTitleBarComponent
|
||||
*/
|
||||
void setTitleBarComponent (Component* titleBarComponentToUse,
|
||||
bool keepDismissButton,
|
||||
bool deleteComponentWhenNoLongerNeeded = true);
|
||||
|
||||
/** Returns the component that is currently being used as the title bar of the SidePanel.
|
||||
|
||||
@see setTitleBarComponent
|
||||
*/
|
||||
Component* getTitleBarComponent() const noexcept { return titleBarComponent.get(); }
|
||||
|
||||
/** Shows or hides the SidePanel.
|
||||
|
||||
This will animate the SidePanel to either its full width or to be hidden on the
|
||||
left- or right-hand side of its parent component depending on the value of positionOnLeft
|
||||
that was passed to the constructor.
|
||||
|
||||
@param show if true, this will show the SidePanel and if false the SidePanel will be hidden
|
||||
*/
|
||||
void showOrHide (bool show);
|
||||
|
||||
//==============================================================================
|
||||
/** Returns true if the SidePanel is currently showing. */
|
||||
bool isPanelShowing() const noexcept { return isShowing; }
|
||||
|
||||
/** Returns true if the SidePanel is positioned on the left of its parent. */
|
||||
bool isPanelOnLeft() const noexcept { return isOnLeft; }
|
||||
|
||||
/** Sets the width of the shadow that will be drawn on the side of the panel. */
|
||||
void setShadowWidth (int newWidth) noexcept { shadowWidth = newWidth; }
|
||||
|
||||
/** Returns the width of the shadow that will be drawn on the side of the panel. */
|
||||
int getShadowWidth() const noexcept { return shadowWidth; }
|
||||
|
||||
/** Sets the height of the title bar at the top of the SidePanel. */
|
||||
void setTitleBarHeight (int newHeight) noexcept { titleBarHeight = newHeight; }
|
||||
|
||||
/** Returns the height of the title bar at the top of the SidePanel. */
|
||||
int getTitleBarHeight() const noexcept { return titleBarHeight; }
|
||||
|
||||
/** Returns the text that is displayed in the title bar at the top of the SidePanel. */
|
||||
String getTitleText() const noexcept { return titleLabel.getText(); }
|
||||
|
||||
//==============================================================================
|
||||
/** This abstract base class is implemented by LookAndFeel classes to provide
|
||||
SidePanel drawing functionality.
|
||||
*/
|
||||
struct JUCE_API LookAndFeelMethods
|
||||
{
|
||||
virtual ~LookAndFeelMethods() = default;
|
||||
|
||||
virtual Font getSidePanelTitleFont (SidePanel&) = 0;
|
||||
virtual Justification getSidePanelTitleJustification (SidePanel&) = 0;
|
||||
virtual Path getSidePanelDismissButtonShape (SidePanel&) = 0;
|
||||
};
|
||||
|
||||
/** A set of colour IDs to use to change the colour of various aspects of the SidePanel.
|
||||
|
||||
These constants can be used either via the Component::setColour(), or LookAndFeel::setColour()
|
||||
methods.
|
||||
|
||||
@see Component::setColour, Component::findColour, LookAndFeel::setColour, LookAndFeel::findColour
|
||||
*/
|
||||
enum ColourIds
|
||||
{
|
||||
backgroundColour = 0x100f001,
|
||||
titleTextColour = 0x100f002,
|
||||
shadowBaseColour = 0x100f003,
|
||||
dismissButtonNormalColour = 0x100f004,
|
||||
dismissButtonOverColour = 0x100f005,
|
||||
dismissButtonDownColour = 0x100f006
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** You can assign a lambda to this callback object and it will be called when the panel is moved. */
|
||||
std::function<void()> onPanelMove;
|
||||
|
||||
/** You can assign a lambda to this callback object and it will be called when the panel is shown or hidden. */
|
||||
std::function<void (bool)> onPanelShowHide;
|
||||
|
||||
//==============================================================================
|
||||
/** @internal */
|
||||
void moved() override;
|
||||
/** @internal */
|
||||
void resized() override;
|
||||
/** @internal */
|
||||
void paint (Graphics& g) override;
|
||||
/** @internal */
|
||||
void parentHierarchyChanged() override;
|
||||
/** @internal */
|
||||
void mouseDrag (const MouseEvent&) override;
|
||||
/** @internal */
|
||||
void mouseUp (const MouseEvent&) override;
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
Component* parent = nullptr;
|
||||
OptionalScopedPointer<Component> contentComponent;
|
||||
OptionalScopedPointer<Component> titleBarComponent;
|
||||
|
||||
Label titleLabel;
|
||||
ShapeButton dismissButton { "dismissButton", Colours::lightgrey, Colours::lightgrey, Colours::white };
|
||||
|
||||
Rectangle<int> shadowArea;
|
||||
|
||||
bool isOnLeft = false;
|
||||
bool isShowing = false;
|
||||
|
||||
int panelWidth = 0;
|
||||
int shadowWidth = 15;
|
||||
int titleBarHeight = 40;
|
||||
|
||||
Rectangle<int> startingBounds;
|
||||
bool shouldResize = false;
|
||||
int amountMoved = 0;
|
||||
|
||||
bool shouldShowDismissButton = true;
|
||||
|
||||
//==============================================================================
|
||||
std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override;
|
||||
void lookAndFeelChanged() override;
|
||||
void componentMovedOrResized (Component&, bool wasMoved, bool wasResized) override;
|
||||
void changeListenerCallback (ChangeBroadcaster*) override;
|
||||
|
||||
Rectangle<int> calculateBoundsInParent (Component&) const;
|
||||
void calculateAndRemoveShadowBounds (Rectangle<int>& bounds);
|
||||
|
||||
bool isMouseEventInThisOrChildren (Component*);
|
||||
|
||||
//==============================================================================
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SidePanel)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A component that is positioned on either the left- or right-hand side of its parent,
|
||||
containing a header and some content. This sort of component is typically used for
|
||||
navigation and forms in mobile applications.
|
||||
|
||||
When triggered with the showOrHide() method, the SidePanel will animate itself to its
|
||||
new position. This component also contains some logic to reactively resize and dismiss
|
||||
itself when the user drags it.
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class SidePanel : public Component,
|
||||
private ComponentListener,
|
||||
private ChangeListener
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates a SidePanel component.
|
||||
|
||||
@param title the text to use for the SidePanel's title bar
|
||||
@param width the width of the SidePanel
|
||||
@param positionOnLeft if true, the SidePanel will be positioned on the left of its parent component and
|
||||
if false, the SidePanel will be positioned on the right of its parent component
|
||||
@param contentComponent the component to add to this SidePanel - this content will take up the full
|
||||
size of the SidePanel, minus the height of the title bar. You can pass nullptr
|
||||
to this if you like and set the content component later using the setContent() method
|
||||
@param deleteComponentWhenNoLongerNeeded if true, the component will be deleted automatically when
|
||||
the SidePanel is deleted or when a different component is added. If false,
|
||||
the caller must manage the lifetime of the component
|
||||
*/
|
||||
SidePanel (StringRef title, int width, bool positionOnLeft,
|
||||
Component* contentComponent = nullptr,
|
||||
bool deleteComponentWhenNoLongerNeeded = true);
|
||||
|
||||
/** Destructor */
|
||||
~SidePanel() override;
|
||||
|
||||
//==============================================================================
|
||||
/** Sets the component that this SidePanel will contain.
|
||||
|
||||
This will add the given component to this SidePanel and position it below the title bar.
|
||||
|
||||
(Don't add or remove any child components directly using the normal
|
||||
Component::addChildComponent() methods).
|
||||
|
||||
@param newContentComponent the component to add to this SidePanel, or nullptr to remove
|
||||
the current component.
|
||||
@param deleteComponentWhenNoLongerNeeded if true, the component will be deleted automatically when
|
||||
the SidePanel is deleted or when a different component is added. If false,
|
||||
the caller must manage the lifetime of the component
|
||||
|
||||
@see getContent
|
||||
*/
|
||||
void setContent (Component* newContentComponent,
|
||||
bool deleteComponentWhenNoLongerNeeded = true);
|
||||
|
||||
/** Returns the component that's currently being used inside the SidePanel.
|
||||
|
||||
@see setViewedComponent
|
||||
*/
|
||||
Component* getContent() const noexcept { return contentComponent.get(); }
|
||||
|
||||
/** Sets a custom component to be used for the title bar of this SidePanel, replacing
|
||||
the default. You can pass a nullptr to revert to the default title bar.
|
||||
|
||||
@param titleBarComponentToUse the component to use as the title bar, or nullptr to use
|
||||
the default
|
||||
@param keepDismissButton if false the specified component will take up the full width of
|
||||
the title bar including the dismiss button but if true, the default
|
||||
dismiss button will be kept
|
||||
@param deleteComponentWhenNoLongerNeeded if true, the component will be deleted automatically when
|
||||
the SidePanel is deleted or when a different component is added. If false,
|
||||
the caller must manage the lifetime of the component
|
||||
|
||||
@see getTitleBarComponent
|
||||
*/
|
||||
void setTitleBarComponent (Component* titleBarComponentToUse,
|
||||
bool keepDismissButton,
|
||||
bool deleteComponentWhenNoLongerNeeded = true);
|
||||
|
||||
/** Returns the component that is currently being used as the title bar of the SidePanel.
|
||||
|
||||
@see setTitleBarComponent
|
||||
*/
|
||||
Component* getTitleBarComponent() const noexcept { return titleBarComponent.get(); }
|
||||
|
||||
/** Shows or hides the SidePanel.
|
||||
|
||||
This will animate the SidePanel to either its full width or to be hidden on the
|
||||
left- or right-hand side of its parent component depending on the value of positionOnLeft
|
||||
that was passed to the constructor.
|
||||
|
||||
@param show if true, this will show the SidePanel and if false the SidePanel will be hidden
|
||||
*/
|
||||
void showOrHide (bool show);
|
||||
|
||||
//==============================================================================
|
||||
/** Returns true if the SidePanel is currently showing. */
|
||||
bool isPanelShowing() const noexcept { return isShowing; }
|
||||
|
||||
/** Returns true if the SidePanel is positioned on the left of its parent. */
|
||||
bool isPanelOnLeft() const noexcept { return isOnLeft; }
|
||||
|
||||
/** Sets the width of the shadow that will be drawn on the side of the panel. */
|
||||
void setShadowWidth (int newWidth) noexcept { shadowWidth = newWidth; }
|
||||
|
||||
/** Returns the width of the shadow that will be drawn on the side of the panel. */
|
||||
int getShadowWidth() const noexcept { return shadowWidth; }
|
||||
|
||||
/** Sets the height of the title bar at the top of the SidePanel. */
|
||||
void setTitleBarHeight (int newHeight) noexcept { titleBarHeight = newHeight; }
|
||||
|
||||
/** Returns the height of the title bar at the top of the SidePanel. */
|
||||
int getTitleBarHeight() const noexcept { return titleBarHeight; }
|
||||
|
||||
/** Returns the text that is displayed in the title bar at the top of the SidePanel. */
|
||||
String getTitleText() const noexcept { return titleLabel.getText(); }
|
||||
|
||||
//==============================================================================
|
||||
/** This abstract base class is implemented by LookAndFeel classes to provide
|
||||
SidePanel drawing functionality.
|
||||
*/
|
||||
struct JUCE_API LookAndFeelMethods
|
||||
{
|
||||
virtual ~LookAndFeelMethods() = default;
|
||||
|
||||
virtual Font getSidePanelTitleFont (SidePanel&) = 0;
|
||||
virtual Justification getSidePanelTitleJustification (SidePanel&) = 0;
|
||||
virtual Path getSidePanelDismissButtonShape (SidePanel&) = 0;
|
||||
};
|
||||
|
||||
/** A set of colour IDs to use to change the colour of various aspects of the SidePanel.
|
||||
|
||||
These constants can be used either via the Component::setColour(), or LookAndFeel::setColour()
|
||||
methods.
|
||||
|
||||
@see Component::setColour, Component::findColour, LookAndFeel::setColour, LookAndFeel::findColour
|
||||
*/
|
||||
enum ColourIds
|
||||
{
|
||||
backgroundColour = 0x100f001,
|
||||
titleTextColour = 0x100f002,
|
||||
shadowBaseColour = 0x100f003,
|
||||
dismissButtonNormalColour = 0x100f004,
|
||||
dismissButtonOverColour = 0x100f005,
|
||||
dismissButtonDownColour = 0x100f006
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** You can assign a lambda to this callback object and it will be called when the panel is moved. */
|
||||
std::function<void()> onPanelMove;
|
||||
|
||||
/** You can assign a lambda to this callback object and it will be called when the panel is shown or hidden. */
|
||||
std::function<void (bool)> onPanelShowHide;
|
||||
|
||||
//==============================================================================
|
||||
/** @internal */
|
||||
void moved() override;
|
||||
/** @internal */
|
||||
void resized() override;
|
||||
/** @internal */
|
||||
void paint (Graphics& g) override;
|
||||
/** @internal */
|
||||
void parentHierarchyChanged() override;
|
||||
/** @internal */
|
||||
void mouseDrag (const MouseEvent&) override;
|
||||
/** @internal */
|
||||
void mouseUp (const MouseEvent&) override;
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
Component* parent = nullptr;
|
||||
OptionalScopedPointer<Component> contentComponent;
|
||||
OptionalScopedPointer<Component> titleBarComponent;
|
||||
|
||||
Label titleLabel;
|
||||
ShapeButton dismissButton { "dismissButton", Colours::lightgrey, Colours::lightgrey, Colours::white };
|
||||
|
||||
Rectangle<int> shadowArea;
|
||||
|
||||
bool isOnLeft = false;
|
||||
bool isShowing = false;
|
||||
|
||||
int panelWidth = 0;
|
||||
int shadowWidth = 15;
|
||||
int titleBarHeight = 40;
|
||||
|
||||
Rectangle<int> startingBounds;
|
||||
bool shouldResize = false;
|
||||
int amountMoved = 0;
|
||||
|
||||
bool shouldShowDismissButton = true;
|
||||
|
||||
//==============================================================================
|
||||
std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override;
|
||||
void lookAndFeelChanged() override;
|
||||
void componentMovedOrResized (Component&, bool wasMoved, bool wasResized) override;
|
||||
void changeListenerCallback (ChangeBroadcaster*) override;
|
||||
|
||||
Rectangle<int> calculateBoundsInParent (Component&) const;
|
||||
void calculateAndRemoveShadowBounds (Rectangle<int>& bounds);
|
||||
|
||||
bool isMouseEventInThisOrChildren (Component*);
|
||||
|
||||
//==============================================================================
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SidePanel)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
|
@ -1,342 +1,342 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
StretchableLayoutManager::StretchableLayoutManager() {}
|
||||
StretchableLayoutManager::~StretchableLayoutManager() {}
|
||||
|
||||
//==============================================================================
|
||||
void StretchableLayoutManager::clearAllItems()
|
||||
{
|
||||
items.clear();
|
||||
totalSize = 0;
|
||||
}
|
||||
|
||||
void StretchableLayoutManager::setItemLayout (const int itemIndex,
|
||||
const double minimumSize,
|
||||
const double maximumSize,
|
||||
const double preferredSize)
|
||||
{
|
||||
auto* layout = getInfoFor (itemIndex);
|
||||
|
||||
if (layout == nullptr)
|
||||
{
|
||||
layout = new ItemLayoutProperties();
|
||||
layout->itemIndex = itemIndex;
|
||||
|
||||
int i;
|
||||
for (i = 0; i < items.size(); ++i)
|
||||
if (items.getUnchecked (i)->itemIndex > itemIndex)
|
||||
break;
|
||||
|
||||
items.insert (i, layout);
|
||||
}
|
||||
|
||||
layout->minSize = minimumSize;
|
||||
layout->maxSize = maximumSize;
|
||||
layout->preferredSize = preferredSize;
|
||||
layout->currentSize = 0;
|
||||
}
|
||||
|
||||
bool StretchableLayoutManager::getItemLayout (const int itemIndex,
|
||||
double& minimumSize,
|
||||
double& maximumSize,
|
||||
double& preferredSize) const
|
||||
{
|
||||
if (auto* layout = getInfoFor (itemIndex))
|
||||
{
|
||||
minimumSize = layout->minSize;
|
||||
maximumSize = layout->maxSize;
|
||||
preferredSize = layout->preferredSize;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void StretchableLayoutManager::setTotalSize (const int newTotalSize)
|
||||
{
|
||||
totalSize = newTotalSize;
|
||||
|
||||
fitComponentsIntoSpace (0, items.size(), totalSize, 0);
|
||||
}
|
||||
|
||||
int StretchableLayoutManager::getItemCurrentPosition (const int itemIndex) const
|
||||
{
|
||||
int pos = 0;
|
||||
|
||||
for (int i = 0; i < itemIndex; ++i)
|
||||
if (auto* layout = getInfoFor (i))
|
||||
pos += layout->currentSize;
|
||||
|
||||
return pos;
|
||||
}
|
||||
|
||||
int StretchableLayoutManager::getItemCurrentAbsoluteSize (const int itemIndex) const
|
||||
{
|
||||
if (auto* layout = getInfoFor (itemIndex))
|
||||
return layout->currentSize;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
double StretchableLayoutManager::getItemCurrentRelativeSize (const int itemIndex) const
|
||||
{
|
||||
if (auto* layout = getInfoFor (itemIndex))
|
||||
return -layout->currentSize / (double) totalSize;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void StretchableLayoutManager::setItemPosition (const int itemIndex,
|
||||
int newPosition)
|
||||
{
|
||||
for (int i = items.size(); --i >= 0;)
|
||||
{
|
||||
auto* layout = items.getUnchecked(i);
|
||||
|
||||
if (layout->itemIndex == itemIndex)
|
||||
{
|
||||
auto realTotalSize = jmax (totalSize, getMinimumSizeOfItems (0, items.size()));
|
||||
auto minSizeAfterThisComp = getMinimumSizeOfItems (i, items.size());
|
||||
auto maxSizeAfterThisComp = getMaximumSizeOfItems (i + 1, items.size());
|
||||
|
||||
newPosition = jmax (newPosition, totalSize - maxSizeAfterThisComp - layout->currentSize);
|
||||
newPosition = jmin (newPosition, realTotalSize - minSizeAfterThisComp);
|
||||
|
||||
auto endPos = fitComponentsIntoSpace (0, i, newPosition, 0);
|
||||
|
||||
endPos += layout->currentSize;
|
||||
|
||||
fitComponentsIntoSpace (i + 1, items.size(), totalSize - endPos, endPos);
|
||||
updatePrefSizesToMatchCurrentPositions();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void StretchableLayoutManager::layOutComponents (Component** const components,
|
||||
int numComponents,
|
||||
int x, int y, int w, int h,
|
||||
const bool vertically,
|
||||
const bool resizeOtherDimension)
|
||||
{
|
||||
setTotalSize (vertically ? h : w);
|
||||
int pos = vertically ? y : x;
|
||||
|
||||
for (int i = 0; i < numComponents; ++i)
|
||||
{
|
||||
if (auto* layout = getInfoFor (i))
|
||||
{
|
||||
if (auto* c = components[i])
|
||||
{
|
||||
if (i == numComponents - 1)
|
||||
{
|
||||
// if it's the last item, crop it to exactly fit the available space..
|
||||
if (resizeOtherDimension)
|
||||
{
|
||||
if (vertically)
|
||||
c->setBounds (x, pos, w, jmax (layout->currentSize, h - pos));
|
||||
else
|
||||
c->setBounds (pos, y, jmax (layout->currentSize, w - pos), h);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (vertically)
|
||||
c->setBounds (c->getX(), pos, c->getWidth(), jmax (layout->currentSize, h - pos));
|
||||
else
|
||||
c->setBounds (pos, c->getY(), jmax (layout->currentSize, w - pos), c->getHeight());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (resizeOtherDimension)
|
||||
{
|
||||
if (vertically)
|
||||
c->setBounds (x, pos, w, layout->currentSize);
|
||||
else
|
||||
c->setBounds (pos, y, layout->currentSize, h);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (vertically)
|
||||
c->setBounds (c->getX(), pos, c->getWidth(), layout->currentSize);
|
||||
else
|
||||
c->setBounds (pos, c->getY(), layout->currentSize, c->getHeight());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pos += layout->currentSize;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//==============================================================================
|
||||
StretchableLayoutManager::ItemLayoutProperties* StretchableLayoutManager::getInfoFor (const int itemIndex) const
|
||||
{
|
||||
for (auto* i : items)
|
||||
if (i->itemIndex == itemIndex)
|
||||
return i;
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int StretchableLayoutManager::fitComponentsIntoSpace (const int startIndex,
|
||||
const int endIndex,
|
||||
const int availableSpace,
|
||||
int startPos)
|
||||
{
|
||||
// calculate the total sizes
|
||||
double totalIdealSize = 0.0;
|
||||
int totalMinimums = 0;
|
||||
|
||||
for (int i = startIndex; i < endIndex; ++i)
|
||||
{
|
||||
auto* layout = items.getUnchecked (i);
|
||||
|
||||
layout->currentSize = sizeToRealSize (layout->minSize, totalSize);
|
||||
|
||||
totalMinimums += layout->currentSize;
|
||||
totalIdealSize += sizeToRealSize (layout->preferredSize, totalSize);
|
||||
}
|
||||
|
||||
if (totalIdealSize <= 0)
|
||||
totalIdealSize = 1.0;
|
||||
|
||||
// now calc the best sizes..
|
||||
int extraSpace = availableSpace - totalMinimums;
|
||||
|
||||
while (extraSpace > 0)
|
||||
{
|
||||
int numWantingMoreSpace = 0;
|
||||
int numHavingTakenExtraSpace = 0;
|
||||
|
||||
// first figure out how many comps want a slice of the extra space..
|
||||
for (int i = startIndex; i < endIndex; ++i)
|
||||
{
|
||||
auto* layout = items.getUnchecked (i);
|
||||
|
||||
auto sizeWanted = sizeToRealSize (layout->preferredSize, totalSize);
|
||||
|
||||
auto bestSize = jlimit (layout->currentSize,
|
||||
jmax (layout->currentSize,
|
||||
sizeToRealSize (layout->maxSize, totalSize)),
|
||||
roundToInt (sizeWanted * availableSpace / totalIdealSize));
|
||||
|
||||
if (bestSize > layout->currentSize)
|
||||
++numWantingMoreSpace;
|
||||
}
|
||||
|
||||
// ..share out the extra space..
|
||||
for (int i = startIndex; i < endIndex; ++i)
|
||||
{
|
||||
auto* layout = items.getUnchecked (i);
|
||||
|
||||
auto sizeWanted = sizeToRealSize (layout->preferredSize, totalSize);
|
||||
|
||||
auto bestSize = jlimit (layout->currentSize,
|
||||
jmax (layout->currentSize, sizeToRealSize (layout->maxSize, totalSize)),
|
||||
roundToInt (sizeWanted * availableSpace / totalIdealSize));
|
||||
|
||||
auto extraWanted = bestSize - layout->currentSize;
|
||||
|
||||
if (extraWanted > 0)
|
||||
{
|
||||
auto extraAllowed = jmin (extraWanted,
|
||||
extraSpace / jmax (1, numWantingMoreSpace));
|
||||
|
||||
if (extraAllowed > 0)
|
||||
{
|
||||
++numHavingTakenExtraSpace;
|
||||
--numWantingMoreSpace;
|
||||
|
||||
layout->currentSize += extraAllowed;
|
||||
extraSpace -= extraAllowed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (numHavingTakenExtraSpace <= 0)
|
||||
break;
|
||||
}
|
||||
|
||||
// ..and calculate the end position
|
||||
for (int i = startIndex; i < endIndex; ++i)
|
||||
{
|
||||
auto* layout = items.getUnchecked(i);
|
||||
startPos += layout->currentSize;
|
||||
}
|
||||
|
||||
return startPos;
|
||||
}
|
||||
|
||||
int StretchableLayoutManager::getMinimumSizeOfItems (const int startIndex,
|
||||
const int endIndex) const
|
||||
{
|
||||
int totalMinimums = 0;
|
||||
|
||||
for (int i = startIndex; i < endIndex; ++i)
|
||||
totalMinimums += sizeToRealSize (items.getUnchecked (i)->minSize, totalSize);
|
||||
|
||||
return totalMinimums;
|
||||
}
|
||||
|
||||
int StretchableLayoutManager::getMaximumSizeOfItems (const int startIndex, const int endIndex) const
|
||||
{
|
||||
int totalMaximums = 0;
|
||||
|
||||
for (int i = startIndex; i < endIndex; ++i)
|
||||
totalMaximums += sizeToRealSize (items.getUnchecked (i)->maxSize, totalSize);
|
||||
|
||||
return totalMaximums;
|
||||
}
|
||||
|
||||
void StretchableLayoutManager::updatePrefSizesToMatchCurrentPositions()
|
||||
{
|
||||
for (int i = 0; i < items.size(); ++i)
|
||||
{
|
||||
auto* layout = items.getUnchecked (i);
|
||||
|
||||
layout->preferredSize
|
||||
= (layout->preferredSize < 0) ? getItemCurrentRelativeSize (i)
|
||||
: getItemCurrentAbsoluteSize (i);
|
||||
}
|
||||
}
|
||||
|
||||
int StretchableLayoutManager::sizeToRealSize (double size, int totalSpace)
|
||||
{
|
||||
if (size < 0)
|
||||
size *= -totalSpace;
|
||||
|
||||
return roundToInt (size);
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
StretchableLayoutManager::StretchableLayoutManager() {}
|
||||
StretchableLayoutManager::~StretchableLayoutManager() {}
|
||||
|
||||
//==============================================================================
|
||||
void StretchableLayoutManager::clearAllItems()
|
||||
{
|
||||
items.clear();
|
||||
totalSize = 0;
|
||||
}
|
||||
|
||||
void StretchableLayoutManager::setItemLayout (const int itemIndex,
|
||||
const double minimumSize,
|
||||
const double maximumSize,
|
||||
const double preferredSize)
|
||||
{
|
||||
auto* layout = getInfoFor (itemIndex);
|
||||
|
||||
if (layout == nullptr)
|
||||
{
|
||||
layout = new ItemLayoutProperties();
|
||||
layout->itemIndex = itemIndex;
|
||||
|
||||
int i;
|
||||
for (i = 0; i < items.size(); ++i)
|
||||
if (items.getUnchecked (i)->itemIndex > itemIndex)
|
||||
break;
|
||||
|
||||
items.insert (i, layout);
|
||||
}
|
||||
|
||||
layout->minSize = minimumSize;
|
||||
layout->maxSize = maximumSize;
|
||||
layout->preferredSize = preferredSize;
|
||||
layout->currentSize = 0;
|
||||
}
|
||||
|
||||
bool StretchableLayoutManager::getItemLayout (const int itemIndex,
|
||||
double& minimumSize,
|
||||
double& maximumSize,
|
||||
double& preferredSize) const
|
||||
{
|
||||
if (auto* layout = getInfoFor (itemIndex))
|
||||
{
|
||||
minimumSize = layout->minSize;
|
||||
maximumSize = layout->maxSize;
|
||||
preferredSize = layout->preferredSize;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void StretchableLayoutManager::setTotalSize (const int newTotalSize)
|
||||
{
|
||||
totalSize = newTotalSize;
|
||||
|
||||
fitComponentsIntoSpace (0, items.size(), totalSize, 0);
|
||||
}
|
||||
|
||||
int StretchableLayoutManager::getItemCurrentPosition (const int itemIndex) const
|
||||
{
|
||||
int pos = 0;
|
||||
|
||||
for (int i = 0; i < itemIndex; ++i)
|
||||
if (auto* layout = getInfoFor (i))
|
||||
pos += layout->currentSize;
|
||||
|
||||
return pos;
|
||||
}
|
||||
|
||||
int StretchableLayoutManager::getItemCurrentAbsoluteSize (const int itemIndex) const
|
||||
{
|
||||
if (auto* layout = getInfoFor (itemIndex))
|
||||
return layout->currentSize;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
double StretchableLayoutManager::getItemCurrentRelativeSize (const int itemIndex) const
|
||||
{
|
||||
if (auto* layout = getInfoFor (itemIndex))
|
||||
return -layout->currentSize / (double) totalSize;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void StretchableLayoutManager::setItemPosition (const int itemIndex,
|
||||
int newPosition)
|
||||
{
|
||||
for (int i = items.size(); --i >= 0;)
|
||||
{
|
||||
auto* layout = items.getUnchecked(i);
|
||||
|
||||
if (layout->itemIndex == itemIndex)
|
||||
{
|
||||
auto realTotalSize = jmax (totalSize, getMinimumSizeOfItems (0, items.size()));
|
||||
auto minSizeAfterThisComp = getMinimumSizeOfItems (i, items.size());
|
||||
auto maxSizeAfterThisComp = getMaximumSizeOfItems (i + 1, items.size());
|
||||
|
||||
newPosition = jmax (newPosition, totalSize - maxSizeAfterThisComp - layout->currentSize);
|
||||
newPosition = jmin (newPosition, realTotalSize - minSizeAfterThisComp);
|
||||
|
||||
auto endPos = fitComponentsIntoSpace (0, i, newPosition, 0);
|
||||
|
||||
endPos += layout->currentSize;
|
||||
|
||||
fitComponentsIntoSpace (i + 1, items.size(), totalSize - endPos, endPos);
|
||||
updatePrefSizesToMatchCurrentPositions();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void StretchableLayoutManager::layOutComponents (Component** const components,
|
||||
int numComponents,
|
||||
int x, int y, int w, int h,
|
||||
const bool vertically,
|
||||
const bool resizeOtherDimension)
|
||||
{
|
||||
setTotalSize (vertically ? h : w);
|
||||
int pos = vertically ? y : x;
|
||||
|
||||
for (int i = 0; i < numComponents; ++i)
|
||||
{
|
||||
if (auto* layout = getInfoFor (i))
|
||||
{
|
||||
if (auto* c = components[i])
|
||||
{
|
||||
if (i == numComponents - 1)
|
||||
{
|
||||
// if it's the last item, crop it to exactly fit the available space..
|
||||
if (resizeOtherDimension)
|
||||
{
|
||||
if (vertically)
|
||||
c->setBounds (x, pos, w, jmax (layout->currentSize, h - pos));
|
||||
else
|
||||
c->setBounds (pos, y, jmax (layout->currentSize, w - pos), h);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (vertically)
|
||||
c->setBounds (c->getX(), pos, c->getWidth(), jmax (layout->currentSize, h - pos));
|
||||
else
|
||||
c->setBounds (pos, c->getY(), jmax (layout->currentSize, w - pos), c->getHeight());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (resizeOtherDimension)
|
||||
{
|
||||
if (vertically)
|
||||
c->setBounds (x, pos, w, layout->currentSize);
|
||||
else
|
||||
c->setBounds (pos, y, layout->currentSize, h);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (vertically)
|
||||
c->setBounds (c->getX(), pos, c->getWidth(), layout->currentSize);
|
||||
else
|
||||
c->setBounds (pos, c->getY(), layout->currentSize, c->getHeight());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pos += layout->currentSize;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//==============================================================================
|
||||
StretchableLayoutManager::ItemLayoutProperties* StretchableLayoutManager::getInfoFor (const int itemIndex) const
|
||||
{
|
||||
for (auto* i : items)
|
||||
if (i->itemIndex == itemIndex)
|
||||
return i;
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int StretchableLayoutManager::fitComponentsIntoSpace (const int startIndex,
|
||||
const int endIndex,
|
||||
const int availableSpace,
|
||||
int startPos)
|
||||
{
|
||||
// calculate the total sizes
|
||||
double totalIdealSize = 0.0;
|
||||
int totalMinimums = 0;
|
||||
|
||||
for (int i = startIndex; i < endIndex; ++i)
|
||||
{
|
||||
auto* layout = items.getUnchecked (i);
|
||||
|
||||
layout->currentSize = sizeToRealSize (layout->minSize, totalSize);
|
||||
|
||||
totalMinimums += layout->currentSize;
|
||||
totalIdealSize += sizeToRealSize (layout->preferredSize, totalSize);
|
||||
}
|
||||
|
||||
if (totalIdealSize <= 0)
|
||||
totalIdealSize = 1.0;
|
||||
|
||||
// now calc the best sizes..
|
||||
int extraSpace = availableSpace - totalMinimums;
|
||||
|
||||
while (extraSpace > 0)
|
||||
{
|
||||
int numWantingMoreSpace = 0;
|
||||
int numHavingTakenExtraSpace = 0;
|
||||
|
||||
// first figure out how many comps want a slice of the extra space..
|
||||
for (int i = startIndex; i < endIndex; ++i)
|
||||
{
|
||||
auto* layout = items.getUnchecked (i);
|
||||
|
||||
auto sizeWanted = sizeToRealSize (layout->preferredSize, totalSize);
|
||||
|
||||
auto bestSize = jlimit (layout->currentSize,
|
||||
jmax (layout->currentSize,
|
||||
sizeToRealSize (layout->maxSize, totalSize)),
|
||||
roundToInt (sizeWanted * availableSpace / totalIdealSize));
|
||||
|
||||
if (bestSize > layout->currentSize)
|
||||
++numWantingMoreSpace;
|
||||
}
|
||||
|
||||
// ..share out the extra space..
|
||||
for (int i = startIndex; i < endIndex; ++i)
|
||||
{
|
||||
auto* layout = items.getUnchecked (i);
|
||||
|
||||
auto sizeWanted = sizeToRealSize (layout->preferredSize, totalSize);
|
||||
|
||||
auto bestSize = jlimit (layout->currentSize,
|
||||
jmax (layout->currentSize, sizeToRealSize (layout->maxSize, totalSize)),
|
||||
roundToInt (sizeWanted * availableSpace / totalIdealSize));
|
||||
|
||||
auto extraWanted = bestSize - layout->currentSize;
|
||||
|
||||
if (extraWanted > 0)
|
||||
{
|
||||
auto extraAllowed = jmin (extraWanted,
|
||||
extraSpace / jmax (1, numWantingMoreSpace));
|
||||
|
||||
if (extraAllowed > 0)
|
||||
{
|
||||
++numHavingTakenExtraSpace;
|
||||
--numWantingMoreSpace;
|
||||
|
||||
layout->currentSize += extraAllowed;
|
||||
extraSpace -= extraAllowed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (numHavingTakenExtraSpace <= 0)
|
||||
break;
|
||||
}
|
||||
|
||||
// ..and calculate the end position
|
||||
for (int i = startIndex; i < endIndex; ++i)
|
||||
{
|
||||
auto* layout = items.getUnchecked(i);
|
||||
startPos += layout->currentSize;
|
||||
}
|
||||
|
||||
return startPos;
|
||||
}
|
||||
|
||||
int StretchableLayoutManager::getMinimumSizeOfItems (const int startIndex,
|
||||
const int endIndex) const
|
||||
{
|
||||
int totalMinimums = 0;
|
||||
|
||||
for (int i = startIndex; i < endIndex; ++i)
|
||||
totalMinimums += sizeToRealSize (items.getUnchecked (i)->minSize, totalSize);
|
||||
|
||||
return totalMinimums;
|
||||
}
|
||||
|
||||
int StretchableLayoutManager::getMaximumSizeOfItems (const int startIndex, const int endIndex) const
|
||||
{
|
||||
int totalMaximums = 0;
|
||||
|
||||
for (int i = startIndex; i < endIndex; ++i)
|
||||
totalMaximums += sizeToRealSize (items.getUnchecked (i)->maxSize, totalSize);
|
||||
|
||||
return totalMaximums;
|
||||
}
|
||||
|
||||
void StretchableLayoutManager::updatePrefSizesToMatchCurrentPositions()
|
||||
{
|
||||
for (int i = 0; i < items.size(); ++i)
|
||||
{
|
||||
auto* layout = items.getUnchecked (i);
|
||||
|
||||
layout->preferredSize
|
||||
= (layout->preferredSize < 0) ? getItemCurrentRelativeSize (i)
|
||||
: getItemCurrentAbsoluteSize (i);
|
||||
}
|
||||
}
|
||||
|
||||
int StretchableLayoutManager::sizeToRealSize (double size, int totalSpace)
|
||||
{
|
||||
if (size < 0)
|
||||
size *= -totalSpace;
|
||||
|
||||
return roundToInt (size);
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
|
@ -1,261 +1,261 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
For laying out a set of components, where the components have preferred sizes
|
||||
and size limits, but where they are allowed to stretch to fill the available
|
||||
space.
|
||||
|
||||
For example, if you have a component containing several other components, and
|
||||
each one should be given a share of the total size, you could use one of these
|
||||
to resize the child components when the parent component is resized. Then
|
||||
you could add a StretchableLayoutResizerBar to easily let the user rescale them.
|
||||
|
||||
A StretchableLayoutManager operates only in one dimension, so if you have a set
|
||||
of components stacked vertically on top of each other, you'd use one to manage their
|
||||
heights. To build up complex arrangements of components, e.g. for applications
|
||||
with multiple nested panels, you would use more than one StretchableLayoutManager.
|
||||
E.g. by using two (one vertical, one horizontal), you could create a resizable
|
||||
spreadsheet-style table.
|
||||
|
||||
E.g.
|
||||
@code
|
||||
class MyComp : public Component
|
||||
{
|
||||
StretchableLayoutManager myLayout;
|
||||
|
||||
MyComp()
|
||||
{
|
||||
myLayout.setItemLayout (0, // for item 0
|
||||
50, 100, // must be between 50 and 100 pixels in size
|
||||
-0.6); // and its preferred size is 60% of the total available space
|
||||
|
||||
myLayout.setItemLayout (1, // for item 1
|
||||
-0.2, -0.6, // size must be between 20% and 60% of the available space
|
||||
50); // and its preferred size is 50 pixels
|
||||
}
|
||||
|
||||
void resized()
|
||||
{
|
||||
// make a list of two of our child components that we want to reposition
|
||||
Component* comps[] = { myComp1, myComp2 };
|
||||
|
||||
// this will position the 2 components, one above the other, to fit
|
||||
// vertically into the rectangle provided.
|
||||
myLayout.layOutComponents (comps, 2,
|
||||
0, 0, getWidth(), getHeight(),
|
||||
true);
|
||||
}
|
||||
};
|
||||
@endcode
|
||||
|
||||
@see StretchableLayoutResizerBar
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API StretchableLayoutManager
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates an empty layout.
|
||||
|
||||
You'll need to add some item properties to the layout before it can be used
|
||||
to resize things - see setItemLayout().
|
||||
*/
|
||||
StretchableLayoutManager();
|
||||
|
||||
/** Destructor. */
|
||||
~StretchableLayoutManager();
|
||||
|
||||
//==============================================================================
|
||||
/** For a numbered item, this sets its size limits and preferred size.
|
||||
|
||||
@param itemIndex the index of the item to change.
|
||||
@param minimumSize the minimum size that this item is allowed to be - a positive number
|
||||
indicates an absolute size in pixels. A negative number indicates a
|
||||
proportion of the available space (e.g -0.5 is 50%)
|
||||
@param maximumSize the maximum size that this item is allowed to be - a positive number
|
||||
indicates an absolute size in pixels. A negative number indicates a
|
||||
proportion of the available space
|
||||
@param preferredSize the size that this item would like to be, if there's enough room. A
|
||||
positive number indicates an absolute size in pixels. A negative number
|
||||
indicates a proportion of the available space
|
||||
@see getItemLayout
|
||||
*/
|
||||
void setItemLayout (int itemIndex,
|
||||
double minimumSize,
|
||||
double maximumSize,
|
||||
double preferredSize);
|
||||
|
||||
/** For a numbered item, this returns its size limits and preferred size.
|
||||
|
||||
@param itemIndex the index of the item.
|
||||
@param minimumSize the minimum size that this item is allowed to be - a positive number
|
||||
indicates an absolute size in pixels. A negative number indicates a
|
||||
proportion of the available space (e.g -0.5 is 50%)
|
||||
@param maximumSize the maximum size that this item is allowed to be - a positive number
|
||||
indicates an absolute size in pixels. A negative number indicates a
|
||||
proportion of the available space
|
||||
@param preferredSize the size that this item would like to be, if there's enough room. A
|
||||
positive number indicates an absolute size in pixels. A negative number
|
||||
indicates a proportion of the available space
|
||||
@returns false if the item's properties hadn't been set
|
||||
@see setItemLayout
|
||||
*/
|
||||
bool getItemLayout (int itemIndex,
|
||||
double& minimumSize,
|
||||
double& maximumSize,
|
||||
double& preferredSize) const;
|
||||
|
||||
/** Clears all the properties that have been set with setItemLayout() and resets
|
||||
this object to its initial state.
|
||||
*/
|
||||
void clearAllItems();
|
||||
|
||||
//==============================================================================
|
||||
/** Takes a set of components that correspond to the layout's items, and positions
|
||||
them to fill a space.
|
||||
|
||||
This will try to give each item its preferred size, whether that's a relative size
|
||||
or an absolute one.
|
||||
|
||||
@param components an array of components that correspond to each of the
|
||||
numbered items that the StretchableLayoutManager object
|
||||
has been told about with setItemLayout()
|
||||
@param numComponents the number of components in the array that is passed-in. This
|
||||
should be the same as the number of items this object has been
|
||||
told about.
|
||||
@param x the left of the rectangle in which the components should
|
||||
be laid out
|
||||
@param y the top of the rectangle in which the components should
|
||||
be laid out
|
||||
@param width the width of the rectangle in which the components should
|
||||
be laid out
|
||||
@param height the height of the rectangle in which the components should
|
||||
be laid out
|
||||
@param vertically if true, the components will be positioned in a vertical stack,
|
||||
so that they fill the height of the rectangle. If false, they
|
||||
will be placed side-by-side in a horizontal line, filling the
|
||||
available width
|
||||
@param resizeOtherDimension if true, this means that the components will have their
|
||||
other dimension resized to fit the space - i.e. if the 'vertically'
|
||||
parameter is true, their x-positions and widths are adjusted to fit
|
||||
the x and width parameters; if 'vertically' is false, their y-positions
|
||||
and heights are adjusted to fit the y and height parameters.
|
||||
*/
|
||||
void layOutComponents (Component** components,
|
||||
int numComponents,
|
||||
int x, int y, int width, int height,
|
||||
bool vertically,
|
||||
bool resizeOtherDimension);
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the current position of one of the items.
|
||||
|
||||
This is only a valid call after layOutComponents() has been called, as it
|
||||
returns the last position that this item was placed at. If the layout was
|
||||
vertical, the value returned will be the y position of the top of the item,
|
||||
relative to the top of the rectangle in which the items were placed (so for
|
||||
example, item 0 will always have position of 0, even in the rectangle passed
|
||||
in to layOutComponents() wasn't at y = 0). If the layout was done horizontally,
|
||||
the position returned is the item's left-hand position, again relative to the
|
||||
x position of the rectangle used.
|
||||
|
||||
@see getItemCurrentSize, setItemPosition
|
||||
*/
|
||||
int getItemCurrentPosition (int itemIndex) const;
|
||||
|
||||
/** Returns the current size of one of the items.
|
||||
|
||||
This is only meaningful after layOutComponents() has been called, as it
|
||||
returns the last size that this item was given. If the layout was done
|
||||
vertically, it'll return the item's height in pixels; if it was horizontal,
|
||||
it'll return its width.
|
||||
|
||||
@see getItemCurrentRelativeSize
|
||||
*/
|
||||
int getItemCurrentAbsoluteSize (int itemIndex) const;
|
||||
|
||||
/** Returns the current size of one of the items.
|
||||
|
||||
This is only meaningful after layOutComponents() has been called, as it
|
||||
returns the last size that this item was given. If the layout was done
|
||||
vertically, it'll return a negative value representing the item's height relative
|
||||
to the last size used for laying the components out; if the layout was done
|
||||
horizontally it'll be the proportion of its width.
|
||||
|
||||
@see getItemCurrentAbsoluteSize
|
||||
*/
|
||||
double getItemCurrentRelativeSize (int itemIndex) const;
|
||||
|
||||
//==============================================================================
|
||||
/** Moves one of the items, shifting along any other items as necessary in
|
||||
order to get it to the desired position.
|
||||
|
||||
Calling this method will also update the preferred sizes of the items it
|
||||
shuffles along, so that they reflect their new positions.
|
||||
|
||||
(This is the method that a StretchableLayoutResizerBar uses to shift the items
|
||||
about when it's dragged).
|
||||
|
||||
@param itemIndex the item to move
|
||||
@param newPosition the absolute position that you'd like this item to move
|
||||
to. The item might not be able to always reach exactly this position,
|
||||
because other items may have minimum sizes that constrain how
|
||||
far it can go
|
||||
*/
|
||||
void setItemPosition (int itemIndex,
|
||||
int newPosition);
|
||||
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
struct ItemLayoutProperties
|
||||
{
|
||||
int itemIndex;
|
||||
int currentSize;
|
||||
double minSize, maxSize, preferredSize;
|
||||
};
|
||||
|
||||
OwnedArray<ItemLayoutProperties> items;
|
||||
int totalSize = 0;
|
||||
|
||||
//==============================================================================
|
||||
static int sizeToRealSize (double size, int totalSpace);
|
||||
ItemLayoutProperties* getInfoFor (int itemIndex) const;
|
||||
void setTotalSize (int newTotalSize);
|
||||
int fitComponentsIntoSpace (int startIndex, int endIndex, int availableSpace, int startPos);
|
||||
int getMinimumSizeOfItems (int startIndex, int endIndex) const;
|
||||
int getMaximumSizeOfItems (int startIndex, int endIndex) const;
|
||||
void updatePrefSizesToMatchCurrentPositions();
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (StretchableLayoutManager)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
For laying out a set of components, where the components have preferred sizes
|
||||
and size limits, but where they are allowed to stretch to fill the available
|
||||
space.
|
||||
|
||||
For example, if you have a component containing several other components, and
|
||||
each one should be given a share of the total size, you could use one of these
|
||||
to resize the child components when the parent component is resized. Then
|
||||
you could add a StretchableLayoutResizerBar to easily let the user rescale them.
|
||||
|
||||
A StretchableLayoutManager operates only in one dimension, so if you have a set
|
||||
of components stacked vertically on top of each other, you'd use one to manage their
|
||||
heights. To build up complex arrangements of components, e.g. for applications
|
||||
with multiple nested panels, you would use more than one StretchableLayoutManager.
|
||||
E.g. by using two (one vertical, one horizontal), you could create a resizable
|
||||
spreadsheet-style table.
|
||||
|
||||
E.g.
|
||||
@code
|
||||
class MyComp : public Component
|
||||
{
|
||||
StretchableLayoutManager myLayout;
|
||||
|
||||
MyComp()
|
||||
{
|
||||
myLayout.setItemLayout (0, // for item 0
|
||||
50, 100, // must be between 50 and 100 pixels in size
|
||||
-0.6); // and its preferred size is 60% of the total available space
|
||||
|
||||
myLayout.setItemLayout (1, // for item 1
|
||||
-0.2, -0.6, // size must be between 20% and 60% of the available space
|
||||
50); // and its preferred size is 50 pixels
|
||||
}
|
||||
|
||||
void resized()
|
||||
{
|
||||
// make a list of two of our child components that we want to reposition
|
||||
Component* comps[] = { myComp1, myComp2 };
|
||||
|
||||
// this will position the 2 components, one above the other, to fit
|
||||
// vertically into the rectangle provided.
|
||||
myLayout.layOutComponents (comps, 2,
|
||||
0, 0, getWidth(), getHeight(),
|
||||
true);
|
||||
}
|
||||
};
|
||||
@endcode
|
||||
|
||||
@see StretchableLayoutResizerBar
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API StretchableLayoutManager
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates an empty layout.
|
||||
|
||||
You'll need to add some item properties to the layout before it can be used
|
||||
to resize things - see setItemLayout().
|
||||
*/
|
||||
StretchableLayoutManager();
|
||||
|
||||
/** Destructor. */
|
||||
~StretchableLayoutManager();
|
||||
|
||||
//==============================================================================
|
||||
/** For a numbered item, this sets its size limits and preferred size.
|
||||
|
||||
@param itemIndex the index of the item to change.
|
||||
@param minimumSize the minimum size that this item is allowed to be - a positive number
|
||||
indicates an absolute size in pixels. A negative number indicates a
|
||||
proportion of the available space (e.g -0.5 is 50%)
|
||||
@param maximumSize the maximum size that this item is allowed to be - a positive number
|
||||
indicates an absolute size in pixels. A negative number indicates a
|
||||
proportion of the available space
|
||||
@param preferredSize the size that this item would like to be, if there's enough room. A
|
||||
positive number indicates an absolute size in pixels. A negative number
|
||||
indicates a proportion of the available space
|
||||
@see getItemLayout
|
||||
*/
|
||||
void setItemLayout (int itemIndex,
|
||||
double minimumSize,
|
||||
double maximumSize,
|
||||
double preferredSize);
|
||||
|
||||
/** For a numbered item, this returns its size limits and preferred size.
|
||||
|
||||
@param itemIndex the index of the item.
|
||||
@param minimumSize the minimum size that this item is allowed to be - a positive number
|
||||
indicates an absolute size in pixels. A negative number indicates a
|
||||
proportion of the available space (e.g -0.5 is 50%)
|
||||
@param maximumSize the maximum size that this item is allowed to be - a positive number
|
||||
indicates an absolute size in pixels. A negative number indicates a
|
||||
proportion of the available space
|
||||
@param preferredSize the size that this item would like to be, if there's enough room. A
|
||||
positive number indicates an absolute size in pixels. A negative number
|
||||
indicates a proportion of the available space
|
||||
@returns false if the item's properties hadn't been set
|
||||
@see setItemLayout
|
||||
*/
|
||||
bool getItemLayout (int itemIndex,
|
||||
double& minimumSize,
|
||||
double& maximumSize,
|
||||
double& preferredSize) const;
|
||||
|
||||
/** Clears all the properties that have been set with setItemLayout() and resets
|
||||
this object to its initial state.
|
||||
*/
|
||||
void clearAllItems();
|
||||
|
||||
//==============================================================================
|
||||
/** Takes a set of components that correspond to the layout's items, and positions
|
||||
them to fill a space.
|
||||
|
||||
This will try to give each item its preferred size, whether that's a relative size
|
||||
or an absolute one.
|
||||
|
||||
@param components an array of components that correspond to each of the
|
||||
numbered items that the StretchableLayoutManager object
|
||||
has been told about with setItemLayout()
|
||||
@param numComponents the number of components in the array that is passed-in. This
|
||||
should be the same as the number of items this object has been
|
||||
told about.
|
||||
@param x the left of the rectangle in which the components should
|
||||
be laid out
|
||||
@param y the top of the rectangle in which the components should
|
||||
be laid out
|
||||
@param width the width of the rectangle in which the components should
|
||||
be laid out
|
||||
@param height the height of the rectangle in which the components should
|
||||
be laid out
|
||||
@param vertically if true, the components will be positioned in a vertical stack,
|
||||
so that they fill the height of the rectangle. If false, they
|
||||
will be placed side-by-side in a horizontal line, filling the
|
||||
available width
|
||||
@param resizeOtherDimension if true, this means that the components will have their
|
||||
other dimension resized to fit the space - i.e. if the 'vertically'
|
||||
parameter is true, their x-positions and widths are adjusted to fit
|
||||
the x and width parameters; if 'vertically' is false, their y-positions
|
||||
and heights are adjusted to fit the y and height parameters.
|
||||
*/
|
||||
void layOutComponents (Component** components,
|
||||
int numComponents,
|
||||
int x, int y, int width, int height,
|
||||
bool vertically,
|
||||
bool resizeOtherDimension);
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the current position of one of the items.
|
||||
|
||||
This is only a valid call after layOutComponents() has been called, as it
|
||||
returns the last position that this item was placed at. If the layout was
|
||||
vertical, the value returned will be the y position of the top of the item,
|
||||
relative to the top of the rectangle in which the items were placed (so for
|
||||
example, item 0 will always have position of 0, even in the rectangle passed
|
||||
in to layOutComponents() wasn't at y = 0). If the layout was done horizontally,
|
||||
the position returned is the item's left-hand position, again relative to the
|
||||
x position of the rectangle used.
|
||||
|
||||
@see getItemCurrentSize, setItemPosition
|
||||
*/
|
||||
int getItemCurrentPosition (int itemIndex) const;
|
||||
|
||||
/** Returns the current size of one of the items.
|
||||
|
||||
This is only meaningful after layOutComponents() has been called, as it
|
||||
returns the last size that this item was given. If the layout was done
|
||||
vertically, it'll return the item's height in pixels; if it was horizontal,
|
||||
it'll return its width.
|
||||
|
||||
@see getItemCurrentRelativeSize
|
||||
*/
|
||||
int getItemCurrentAbsoluteSize (int itemIndex) const;
|
||||
|
||||
/** Returns the current size of one of the items.
|
||||
|
||||
This is only meaningful after layOutComponents() has been called, as it
|
||||
returns the last size that this item was given. If the layout was done
|
||||
vertically, it'll return a negative value representing the item's height relative
|
||||
to the last size used for laying the components out; if the layout was done
|
||||
horizontally it'll be the proportion of its width.
|
||||
|
||||
@see getItemCurrentAbsoluteSize
|
||||
*/
|
||||
double getItemCurrentRelativeSize (int itemIndex) const;
|
||||
|
||||
//==============================================================================
|
||||
/** Moves one of the items, shifting along any other items as necessary in
|
||||
order to get it to the desired position.
|
||||
|
||||
Calling this method will also update the preferred sizes of the items it
|
||||
shuffles along, so that they reflect their new positions.
|
||||
|
||||
(This is the method that a StretchableLayoutResizerBar uses to shift the items
|
||||
about when it's dragged).
|
||||
|
||||
@param itemIndex the item to move
|
||||
@param newPosition the absolute position that you'd like this item to move
|
||||
to. The item might not be able to always reach exactly this position,
|
||||
because other items may have minimum sizes that constrain how
|
||||
far it can go
|
||||
*/
|
||||
void setItemPosition (int itemIndex,
|
||||
int newPosition);
|
||||
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
struct ItemLayoutProperties
|
||||
{
|
||||
int itemIndex;
|
||||
int currentSize;
|
||||
double minSize, maxSize, preferredSize;
|
||||
};
|
||||
|
||||
OwnedArray<ItemLayoutProperties> items;
|
||||
int totalSize = 0;
|
||||
|
||||
//==============================================================================
|
||||
static int sizeToRealSize (double size, int totalSpace);
|
||||
ItemLayoutProperties* getInfoFor (int itemIndex) const;
|
||||
void setTotalSize (int newTotalSize);
|
||||
int fitComponentsIntoSpace (int startIndex, int endIndex, int availableSpace, int startPos);
|
||||
int getMinimumSizeOfItems (int startIndex, int endIndex) const;
|
||||
int getMaximumSizeOfItems (int startIndex, int endIndex) const;
|
||||
void updatePrefSizesToMatchCurrentPositions();
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (StretchableLayoutManager)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
|
@ -1,79 +1,79 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
StretchableLayoutResizerBar::StretchableLayoutResizerBar (StretchableLayoutManager* layout_,
|
||||
const int index,
|
||||
const bool vertical)
|
||||
: layout (layout_),
|
||||
itemIndex (index),
|
||||
isVertical (vertical)
|
||||
{
|
||||
setRepaintsOnMouseActivity (true);
|
||||
setMouseCursor (vertical ? MouseCursor::LeftRightResizeCursor
|
||||
: MouseCursor::UpDownResizeCursor);
|
||||
}
|
||||
|
||||
StretchableLayoutResizerBar::~StretchableLayoutResizerBar()
|
||||
{
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void StretchableLayoutResizerBar::paint (Graphics& g)
|
||||
{
|
||||
getLookAndFeel().drawStretchableLayoutResizerBar (g,
|
||||
getWidth(), getHeight(),
|
||||
isVertical,
|
||||
isMouseOver(),
|
||||
isMouseButtonDown());
|
||||
}
|
||||
|
||||
void StretchableLayoutResizerBar::mouseDown (const MouseEvent&)
|
||||
{
|
||||
mouseDownPos = layout->getItemCurrentPosition (itemIndex);
|
||||
}
|
||||
|
||||
void StretchableLayoutResizerBar::mouseDrag (const MouseEvent& e)
|
||||
{
|
||||
const int desiredPos = mouseDownPos + (isVertical ? e.getDistanceFromDragStartX()
|
||||
: e.getDistanceFromDragStartY());
|
||||
|
||||
|
||||
if (layout->getItemCurrentPosition (itemIndex) != desiredPos)
|
||||
{
|
||||
layout->setItemPosition (itemIndex, desiredPos);
|
||||
hasBeenMoved();
|
||||
}
|
||||
}
|
||||
|
||||
void StretchableLayoutResizerBar::hasBeenMoved()
|
||||
{
|
||||
if (Component* parent = getParentComponent())
|
||||
parent->resized();
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
StretchableLayoutResizerBar::StretchableLayoutResizerBar (StretchableLayoutManager* layout_,
|
||||
const int index,
|
||||
const bool vertical)
|
||||
: layout (layout_),
|
||||
itemIndex (index),
|
||||
isVertical (vertical)
|
||||
{
|
||||
setRepaintsOnMouseActivity (true);
|
||||
setMouseCursor (vertical ? MouseCursor::LeftRightResizeCursor
|
||||
: MouseCursor::UpDownResizeCursor);
|
||||
}
|
||||
|
||||
StretchableLayoutResizerBar::~StretchableLayoutResizerBar()
|
||||
{
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void StretchableLayoutResizerBar::paint (Graphics& g)
|
||||
{
|
||||
getLookAndFeel().drawStretchableLayoutResizerBar (g,
|
||||
getWidth(), getHeight(),
|
||||
isVertical,
|
||||
isMouseOver(),
|
||||
isMouseButtonDown());
|
||||
}
|
||||
|
||||
void StretchableLayoutResizerBar::mouseDown (const MouseEvent&)
|
||||
{
|
||||
mouseDownPos = layout->getItemCurrentPosition (itemIndex);
|
||||
}
|
||||
|
||||
void StretchableLayoutResizerBar::mouseDrag (const MouseEvent& e)
|
||||
{
|
||||
const int desiredPos = mouseDownPos + (isVertical ? e.getDistanceFromDragStartX()
|
||||
: e.getDistanceFromDragStartY());
|
||||
|
||||
|
||||
if (layout->getItemCurrentPosition (itemIndex) != desiredPos)
|
||||
{
|
||||
layout->setItemPosition (itemIndex, desiredPos);
|
||||
hasBeenMoved();
|
||||
}
|
||||
}
|
||||
|
||||
void StretchableLayoutResizerBar::hasBeenMoved()
|
||||
{
|
||||
if (Component* parent = getParentComponent())
|
||||
parent->resized();
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
|
@ -1,105 +1,105 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A component that acts as one of the vertical or horizontal bars you see being
|
||||
used to resize panels in a window.
|
||||
|
||||
One of these acts with a StretchableLayoutManager to resize the other components.
|
||||
|
||||
@see StretchableLayoutManager
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API StretchableLayoutResizerBar : public Component
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates a resizer bar for use on a specified layout.
|
||||
|
||||
@param layoutToUse the layout that will be affected when this bar
|
||||
is dragged
|
||||
@param itemIndexInLayout the item index in the layout that corresponds to
|
||||
this bar component. You'll need to set up the item
|
||||
properties in a suitable way for a divider bar, e.g.
|
||||
for an 8-pixel wide bar which, you could call
|
||||
myLayout->setItemLayout (barIndex, 8, 8, 8)
|
||||
@param isBarVertical true if it's an upright bar that you drag left and
|
||||
right; false for a horizontal one that you drag up and
|
||||
down
|
||||
*/
|
||||
StretchableLayoutResizerBar (StretchableLayoutManager* layoutToUse,
|
||||
int itemIndexInLayout,
|
||||
bool isBarVertical);
|
||||
|
||||
/** Destructor. */
|
||||
~StretchableLayoutResizerBar() override;
|
||||
|
||||
//==============================================================================
|
||||
/** This is called when the bar is dragged.
|
||||
|
||||
This method must update the positions of any components whose position is
|
||||
determined by the StretchableLayoutManager, because they might have just
|
||||
moved.
|
||||
|
||||
The default implementation calls the resized() method of this component's
|
||||
parent component, because that's often where you're likely to apply the
|
||||
layout, but it can be overridden for more specific needs.
|
||||
*/
|
||||
virtual void hasBeenMoved();
|
||||
|
||||
//==============================================================================
|
||||
/** This abstract base class is implemented by LookAndFeel classes. */
|
||||
struct JUCE_API LookAndFeelMethods
|
||||
{
|
||||
virtual ~LookAndFeelMethods() = default;
|
||||
|
||||
virtual void drawStretchableLayoutResizerBar (Graphics&, int w, int h,
|
||||
bool isVerticalBar, bool isMouseOver, bool isMouseDragging) = 0;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** @internal */
|
||||
void paint (Graphics&) override;
|
||||
/** @internal */
|
||||
void mouseDown (const MouseEvent&) override;
|
||||
/** @internal */
|
||||
void mouseDrag (const MouseEvent&) override;
|
||||
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
StretchableLayoutManager* layout;
|
||||
int itemIndex, mouseDownPos;
|
||||
bool isVertical;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (StretchableLayoutResizerBar)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A component that acts as one of the vertical or horizontal bars you see being
|
||||
used to resize panels in a window.
|
||||
|
||||
One of these acts with a StretchableLayoutManager to resize the other components.
|
||||
|
||||
@see StretchableLayoutManager
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API StretchableLayoutResizerBar : public Component
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates a resizer bar for use on a specified layout.
|
||||
|
||||
@param layoutToUse the layout that will be affected when this bar
|
||||
is dragged
|
||||
@param itemIndexInLayout the item index in the layout that corresponds to
|
||||
this bar component. You'll need to set up the item
|
||||
properties in a suitable way for a divider bar, e.g.
|
||||
for an 8-pixel wide bar which, you could call
|
||||
myLayout->setItemLayout (barIndex, 8, 8, 8)
|
||||
@param isBarVertical true if it's an upright bar that you drag left and
|
||||
right; false for a horizontal one that you drag up and
|
||||
down
|
||||
*/
|
||||
StretchableLayoutResizerBar (StretchableLayoutManager* layoutToUse,
|
||||
int itemIndexInLayout,
|
||||
bool isBarVertical);
|
||||
|
||||
/** Destructor. */
|
||||
~StretchableLayoutResizerBar() override;
|
||||
|
||||
//==============================================================================
|
||||
/** This is called when the bar is dragged.
|
||||
|
||||
This method must update the positions of any components whose position is
|
||||
determined by the StretchableLayoutManager, because they might have just
|
||||
moved.
|
||||
|
||||
The default implementation calls the resized() method of this component's
|
||||
parent component, because that's often where you're likely to apply the
|
||||
layout, but it can be overridden for more specific needs.
|
||||
*/
|
||||
virtual void hasBeenMoved();
|
||||
|
||||
//==============================================================================
|
||||
/** This abstract base class is implemented by LookAndFeel classes. */
|
||||
struct JUCE_API LookAndFeelMethods
|
||||
{
|
||||
virtual ~LookAndFeelMethods() = default;
|
||||
|
||||
virtual void drawStretchableLayoutResizerBar (Graphics&, int w, int h,
|
||||
bool isVerticalBar, bool isMouseOver, bool isMouseDragging) = 0;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** @internal */
|
||||
void paint (Graphics&) override;
|
||||
/** @internal */
|
||||
void mouseDown (const MouseEvent&) override;
|
||||
/** @internal */
|
||||
void mouseDrag (const MouseEvent&) override;
|
||||
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
StretchableLayoutManager* layout;
|
||||
int itemIndex, mouseDownPos;
|
||||
bool isVertical;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (StretchableLayoutResizerBar)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
|
@ -1,122 +1,122 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
StretchableObjectResizer::StretchableObjectResizer() {}
|
||||
StretchableObjectResizer::~StretchableObjectResizer() {}
|
||||
|
||||
void StretchableObjectResizer::addItem (const double size,
|
||||
const double minSize, const double maxSize,
|
||||
const int order)
|
||||
{
|
||||
// the order must be >= 0 but less than the maximum integer value.
|
||||
jassert (order >= 0 && order < std::numeric_limits<int>::max());
|
||||
jassert (maxSize >= minSize);
|
||||
|
||||
Item item;
|
||||
item.size = size;
|
||||
item.minSize = minSize;
|
||||
item.maxSize = maxSize;
|
||||
item.order = order;
|
||||
items.add (item);
|
||||
}
|
||||
|
||||
double StretchableObjectResizer::getItemSize (const int index) const noexcept
|
||||
{
|
||||
return isPositiveAndBelow (index, items.size()) ? items.getReference (index).size
|
||||
: 0.0;
|
||||
}
|
||||
|
||||
void StretchableObjectResizer::resizeToFit (const double targetSize)
|
||||
{
|
||||
int order = 0;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
double currentSize = 0;
|
||||
double minSize = 0;
|
||||
double maxSize = 0;
|
||||
|
||||
int nextHighestOrder = std::numeric_limits<int>::max();
|
||||
|
||||
for (int i = 0; i < items.size(); ++i)
|
||||
{
|
||||
const Item& it = items.getReference(i);
|
||||
currentSize += it.size;
|
||||
|
||||
if (it.order <= order)
|
||||
{
|
||||
minSize += it.minSize;
|
||||
maxSize += it.maxSize;
|
||||
}
|
||||
else
|
||||
{
|
||||
minSize += it.size;
|
||||
maxSize += it.size;
|
||||
nextHighestOrder = jmin (nextHighestOrder, it.order);
|
||||
}
|
||||
}
|
||||
|
||||
const double thisIterationTarget = jlimit (minSize, maxSize, targetSize);
|
||||
|
||||
if (thisIterationTarget >= currentSize)
|
||||
{
|
||||
const double availableExtraSpace = maxSize - currentSize;
|
||||
const double targetAmountOfExtraSpace = thisIterationTarget - currentSize;
|
||||
const double scale = availableExtraSpace > 0 ? targetAmountOfExtraSpace / availableExtraSpace : 1.0;
|
||||
|
||||
for (int i = 0; i < items.size(); ++i)
|
||||
{
|
||||
Item& it = items.getReference(i);
|
||||
|
||||
if (it.order <= order)
|
||||
it.size = jlimit (it.minSize, it.maxSize, it.size + (it.maxSize - it.size) * scale);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
const double amountOfSlack = currentSize - minSize;
|
||||
const double targetAmountOfSlack = thisIterationTarget - minSize;
|
||||
const double scale = targetAmountOfSlack / amountOfSlack;
|
||||
|
||||
for (int i = 0; i < items.size(); ++i)
|
||||
{
|
||||
Item& it = items.getReference(i);
|
||||
|
||||
if (it.order <= order)
|
||||
it.size = jmax (it.minSize, it.minSize + (it.size - it.minSize) * scale);
|
||||
}
|
||||
}
|
||||
|
||||
if (nextHighestOrder < std::numeric_limits<int>::max())
|
||||
order = nextHighestOrder;
|
||||
else
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
StretchableObjectResizer::StretchableObjectResizer() {}
|
||||
StretchableObjectResizer::~StretchableObjectResizer() {}
|
||||
|
||||
void StretchableObjectResizer::addItem (const double size,
|
||||
const double minSize, const double maxSize,
|
||||
const int order)
|
||||
{
|
||||
// the order must be >= 0 but less than the maximum integer value.
|
||||
jassert (order >= 0 && order < std::numeric_limits<int>::max());
|
||||
jassert (maxSize >= minSize);
|
||||
|
||||
Item item;
|
||||
item.size = size;
|
||||
item.minSize = minSize;
|
||||
item.maxSize = maxSize;
|
||||
item.order = order;
|
||||
items.add (item);
|
||||
}
|
||||
|
||||
double StretchableObjectResizer::getItemSize (const int index) const noexcept
|
||||
{
|
||||
return isPositiveAndBelow (index, items.size()) ? items.getReference (index).size
|
||||
: 0.0;
|
||||
}
|
||||
|
||||
void StretchableObjectResizer::resizeToFit (const double targetSize)
|
||||
{
|
||||
int order = 0;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
double currentSize = 0;
|
||||
double minSize = 0;
|
||||
double maxSize = 0;
|
||||
|
||||
int nextHighestOrder = std::numeric_limits<int>::max();
|
||||
|
||||
for (int i = 0; i < items.size(); ++i)
|
||||
{
|
||||
const Item& it = items.getReference(i);
|
||||
currentSize += it.size;
|
||||
|
||||
if (it.order <= order)
|
||||
{
|
||||
minSize += it.minSize;
|
||||
maxSize += it.maxSize;
|
||||
}
|
||||
else
|
||||
{
|
||||
minSize += it.size;
|
||||
maxSize += it.size;
|
||||
nextHighestOrder = jmin (nextHighestOrder, it.order);
|
||||
}
|
||||
}
|
||||
|
||||
const double thisIterationTarget = jlimit (minSize, maxSize, targetSize);
|
||||
|
||||
if (thisIterationTarget >= currentSize)
|
||||
{
|
||||
const double availableExtraSpace = maxSize - currentSize;
|
||||
const double targetAmountOfExtraSpace = thisIterationTarget - currentSize;
|
||||
const double scale = availableExtraSpace > 0 ? targetAmountOfExtraSpace / availableExtraSpace : 1.0;
|
||||
|
||||
for (int i = 0; i < items.size(); ++i)
|
||||
{
|
||||
Item& it = items.getReference(i);
|
||||
|
||||
if (it.order <= order)
|
||||
it.size = jlimit (it.minSize, it.maxSize, it.size + (it.maxSize - it.size) * scale);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
const double amountOfSlack = currentSize - minSize;
|
||||
const double targetAmountOfSlack = thisIterationTarget - minSize;
|
||||
const double scale = targetAmountOfSlack / amountOfSlack;
|
||||
|
||||
for (int i = 0; i < items.size(); ++i)
|
||||
{
|
||||
Item& it = items.getReference(i);
|
||||
|
||||
if (it.order <= order)
|
||||
it.size = jmax (it.minSize, it.minSize + (it.size - it.minSize) * scale);
|
||||
}
|
||||
}
|
||||
|
||||
if (nextHighestOrder < std::numeric_limits<int>::max())
|
||||
order = nextHighestOrder;
|
||||
else
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
|
@ -1,102 +1,102 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A utility class for fitting a set of objects whose sizes can vary between
|
||||
a minimum and maximum size, into a space.
|
||||
|
||||
This is a trickier algorithm than it would first seem, so I've put it in this
|
||||
class to allow it to be shared by various bits of code.
|
||||
|
||||
To use it, create one of these objects, call addItem() to add the list of items
|
||||
you need, then call resizeToFit(), which will change all their sizes. You can
|
||||
then retrieve the new sizes with getItemSize() and getNumItems().
|
||||
|
||||
It's currently used by the TableHeaderComponent for stretching out the table
|
||||
headings to fill the table's width.
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class StretchableObjectResizer
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates an empty object resizer. */
|
||||
StretchableObjectResizer();
|
||||
|
||||
/** Destructor. */
|
||||
~StretchableObjectResizer();
|
||||
|
||||
//==============================================================================
|
||||
/** Adds an item to the list.
|
||||
|
||||
The order parameter lets you specify groups of items that are resized first when some
|
||||
space needs to be found. Those items with an order of 0 will be the first ones to be
|
||||
resized, and if that doesn't provide enough space to meet the requirements, the algorithm
|
||||
will then try resizing the items with an order of 1, then 2, and so on.
|
||||
*/
|
||||
void addItem (double currentSize,
|
||||
double minSize,
|
||||
double maxSize,
|
||||
int order = 0);
|
||||
|
||||
/** Resizes all the items to fit this amount of space.
|
||||
|
||||
This will attempt to fit them in without exceeding each item's minimum and
|
||||
maximum sizes. In cases where none of the items can be expanded or enlarged any
|
||||
further, the final size may be greater or less than the size passed in.
|
||||
|
||||
After calling this method, you can retrieve the new sizes with the getItemSize()
|
||||
method.
|
||||
*/
|
||||
void resizeToFit (double targetSize);
|
||||
|
||||
/** Returns the number of items that have been added. */
|
||||
int getNumItems() const noexcept { return items.size(); }
|
||||
|
||||
/** Returns the size of one of the items. */
|
||||
double getItemSize (int index) const noexcept;
|
||||
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
struct Item
|
||||
{
|
||||
double size;
|
||||
double minSize;
|
||||
double maxSize;
|
||||
int order;
|
||||
};
|
||||
|
||||
Array<Item> items;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (StretchableObjectResizer)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A utility class for fitting a set of objects whose sizes can vary between
|
||||
a minimum and maximum size, into a space.
|
||||
|
||||
This is a trickier algorithm than it would first seem, so I've put it in this
|
||||
class to allow it to be shared by various bits of code.
|
||||
|
||||
To use it, create one of these objects, call addItem() to add the list of items
|
||||
you need, then call resizeToFit(), which will change all their sizes. You can
|
||||
then retrieve the new sizes with getItemSize() and getNumItems().
|
||||
|
||||
It's currently used by the TableHeaderComponent for stretching out the table
|
||||
headings to fill the table's width.
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class StretchableObjectResizer
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates an empty object resizer. */
|
||||
StretchableObjectResizer();
|
||||
|
||||
/** Destructor. */
|
||||
~StretchableObjectResizer();
|
||||
|
||||
//==============================================================================
|
||||
/** Adds an item to the list.
|
||||
|
||||
The order parameter lets you specify groups of items that are resized first when some
|
||||
space needs to be found. Those items with an order of 0 will be the first ones to be
|
||||
resized, and if that doesn't provide enough space to meet the requirements, the algorithm
|
||||
will then try resizing the items with an order of 1, then 2, and so on.
|
||||
*/
|
||||
void addItem (double currentSize,
|
||||
double minSize,
|
||||
double maxSize,
|
||||
int order = 0);
|
||||
|
||||
/** Resizes all the items to fit this amount of space.
|
||||
|
||||
This will attempt to fit them in without exceeding each item's minimum and
|
||||
maximum sizes. In cases where none of the items can be expanded or enlarged any
|
||||
further, the final size may be greater or less than the size passed in.
|
||||
|
||||
After calling this method, you can retrieve the new sizes with the getItemSize()
|
||||
method.
|
||||
*/
|
||||
void resizeToFit (double targetSize);
|
||||
|
||||
/** Returns the number of items that have been added. */
|
||||
int getNumItems() const noexcept { return items.size(); }
|
||||
|
||||
/** Returns the size of one of the items. */
|
||||
double getItemSize (int index) const noexcept;
|
||||
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
struct Item
|
||||
{
|
||||
double size;
|
||||
double minSize;
|
||||
double maxSize;
|
||||
int order;
|
||||
};
|
||||
|
||||
Array<Item> items;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (StretchableObjectResizer)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,374 +1,372 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
class TabbedButtonBar;
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/** In a TabbedButtonBar, this component is used for each of the buttons.
|
||||
|
||||
If you want to create a TabbedButtonBar with custom tab components, derive
|
||||
your component from this class, and override the TabbedButtonBar::createTabButton()
|
||||
method to create it instead of the default one.
|
||||
|
||||
@see TabbedButtonBar
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API TabBarButton : public Button
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates the tab button. */
|
||||
TabBarButton (const String& name, TabbedButtonBar& ownerBar);
|
||||
|
||||
/** Destructor. */
|
||||
~TabBarButton() override;
|
||||
|
||||
/** Returns the bar that contains this button. */
|
||||
TabbedButtonBar& getTabbedButtonBar() const { return owner; }
|
||||
|
||||
//==============================================================================
|
||||
/** When adding an extra component to the tab, this indicates which side of
|
||||
the text it should be placed on. */
|
||||
enum ExtraComponentPlacement
|
||||
{
|
||||
beforeText,
|
||||
afterText,
|
||||
aboveText,
|
||||
belowText
|
||||
};
|
||||
|
||||
/** Sets an extra component that will be shown in the tab.
|
||||
|
||||
This optional component will be positioned inside the tab, either to the left or right
|
||||
of the text. You could use this to implement things like a close button or a graphical
|
||||
status indicator. If a non-null component is passed-in, the TabbedButtonBar will take
|
||||
ownership of it and delete it when required.
|
||||
*/
|
||||
void setExtraComponent (Component* extraTabComponent,
|
||||
ExtraComponentPlacement extraComponentPlacement);
|
||||
|
||||
/** Returns the custom component, if there is one. */
|
||||
Component* getExtraComponent() const noexcept { return extraComponent.get(); }
|
||||
|
||||
/** Returns the placement of the custom component, if there is one. */
|
||||
ExtraComponentPlacement getExtraComponentPlacement() const noexcept { return extraCompPlacement; }
|
||||
|
||||
/** Returns an area of the component that's safe to draw in.
|
||||
|
||||
This deals with the orientation of the tabs, which affects which side is
|
||||
touching the tabbed box's content component.
|
||||
*/
|
||||
Rectangle<int> getActiveArea() const;
|
||||
|
||||
/** Returns the area of the component that should contain its text. */
|
||||
Rectangle<int> getTextArea() const;
|
||||
|
||||
/** Returns this tab's index in its tab bar. */
|
||||
int getIndex() const;
|
||||
|
||||
/** Returns the colour of the tab. */
|
||||
Colour getTabBackgroundColour() const;
|
||||
|
||||
/** Returns true if this is the frontmost (selected) tab. */
|
||||
bool isFrontTab() const;
|
||||
|
||||
//==============================================================================
|
||||
/** Chooses the best length for the tab, given the specified depth.
|
||||
|
||||
If the tab is horizontal, this should return its width, and the depth
|
||||
specifies its height. If it's vertical, it should return the height, and
|
||||
the depth is actually its width.
|
||||
*/
|
||||
virtual int getBestTabLength (int depth);
|
||||
|
||||
//==============================================================================
|
||||
/** @internal */
|
||||
void paintButton (Graphics&, bool, bool) override;
|
||||
/** @internal */
|
||||
void clicked (const ModifierKeys&) override;
|
||||
/** @internal */
|
||||
bool hitTest (int x, int y) override;
|
||||
/** @internal */
|
||||
void resized() override;
|
||||
/** @internal */
|
||||
void childBoundsChanged (Component*) override;
|
||||
|
||||
protected:
|
||||
friend class TabbedButtonBar;
|
||||
TabbedButtonBar& owner;
|
||||
int overlapPixels = 0;
|
||||
|
||||
std::unique_ptr<Component> extraComponent;
|
||||
ExtraComponentPlacement extraCompPlacement = afterText;
|
||||
|
||||
private:
|
||||
using Button::clicked;
|
||||
void calcAreas (Rectangle<int>&, Rectangle<int>&) const;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TabBarButton)
|
||||
};
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A vertical or horizontal bar containing tabs that you can select.
|
||||
|
||||
You can use one of these to generate things like a dialog box that has
|
||||
tabbed pages you can flip between. Attach a ChangeListener to the
|
||||
button bar to be told when the user changes the page.
|
||||
|
||||
An easier method than doing this is to use a TabbedComponent, which
|
||||
contains its own TabbedButtonBar and which takes care of the layout
|
||||
and other housekeeping.
|
||||
|
||||
@see TabbedComponent
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API TabbedButtonBar : public Component,
|
||||
public ChangeBroadcaster
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** The placement of the tab-bar
|
||||
@see setOrientation, getOrientation
|
||||
*/
|
||||
enum Orientation
|
||||
{
|
||||
TabsAtTop,
|
||||
TabsAtBottom,
|
||||
TabsAtLeft,
|
||||
TabsAtRight
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** Creates a TabbedButtonBar with a given orientation.
|
||||
You can change the orientation later if you need to.
|
||||
*/
|
||||
TabbedButtonBar (Orientation orientation);
|
||||
|
||||
/** Destructor. */
|
||||
~TabbedButtonBar() override;
|
||||
|
||||
//==============================================================================
|
||||
/** Changes the bar's orientation.
|
||||
|
||||
This won't change the bar's actual size - you'll need to do that yourself,
|
||||
but this determines which direction the tabs go in, and which side they're
|
||||
stuck to.
|
||||
*/
|
||||
void setOrientation (Orientation orientation);
|
||||
|
||||
/** Returns the bar's current orientation.
|
||||
@see setOrientation
|
||||
*/
|
||||
Orientation getOrientation() const noexcept { return orientation; }
|
||||
|
||||
/** Returns true if the orientation is TabsAtLeft or TabsAtRight. */
|
||||
bool isVertical() const noexcept { return orientation == TabsAtLeft || orientation == TabsAtRight; }
|
||||
|
||||
/** Returns the thickness of the bar, which may be its width or height, depending on the orientation. */
|
||||
int getThickness() const noexcept { return isVertical() ? getWidth() : getHeight(); }
|
||||
|
||||
/** Changes the minimum scale factor to which the tabs can be compressed when trying to
|
||||
fit a lot of tabs on-screen.
|
||||
*/
|
||||
void setMinimumTabScaleFactor (double newMinimumScale);
|
||||
|
||||
//==============================================================================
|
||||
/** Deletes all the tabs from the bar.
|
||||
@see addTab
|
||||
*/
|
||||
void clearTabs();
|
||||
|
||||
/** Adds a tab to the bar.
|
||||
Tabs are added in left-to-right reading order.
|
||||
If this is the first tab added, it'll also be automatically selected.
|
||||
*/
|
||||
void addTab (const String& tabName,
|
||||
Colour tabBackgroundColour,
|
||||
int insertIndex);
|
||||
|
||||
/** Changes the name of one of the tabs. */
|
||||
void setTabName (int tabIndex, const String& newName);
|
||||
|
||||
/** Gets rid of one of the tabs. */
|
||||
void removeTab (int tabIndex, bool animate = false);
|
||||
|
||||
/** Moves a tab to a new index in the list.
|
||||
Pass -1 as the index to move it to the end of the list.
|
||||
*/
|
||||
void moveTab (int currentIndex, int newIndex, bool animate = false);
|
||||
|
||||
/** Returns the number of tabs in the bar. */
|
||||
int getNumTabs() const;
|
||||
|
||||
/** Returns a list of all the tab names in the bar. */
|
||||
StringArray getTabNames() const;
|
||||
|
||||
/** Changes the currently selected tab.
|
||||
This will send a change message and cause a synchronous callback to
|
||||
the currentTabChanged() method. (But if the given tab is already selected,
|
||||
nothing will be done).
|
||||
|
||||
To deselect all the tabs, use an index of -1.
|
||||
*/
|
||||
void setCurrentTabIndex (int newTabIndex, bool sendChangeMessage = true);
|
||||
|
||||
/** Returns the name of the currently selected tab.
|
||||
This could be an empty string if none are selected.
|
||||
*/
|
||||
String getCurrentTabName() const;
|
||||
|
||||
/** Returns the index of the currently selected tab.
|
||||
This could return -1 if none are selected.
|
||||
*/
|
||||
int getCurrentTabIndex() const noexcept { return currentTabIndex; }
|
||||
|
||||
/** Returns the button for a specific tab.
|
||||
The button that is returned may be deleted later by this component, so don't hang
|
||||
on to the pointer that is returned. A null pointer may be returned if the index is
|
||||
out of range.
|
||||
*/
|
||||
TabBarButton* getTabButton (int index) const;
|
||||
|
||||
/** Returns the index of a TabBarButton if it belongs to this bar. */
|
||||
int indexOfTabButton (const TabBarButton* button) const;
|
||||
|
||||
/** Returns the final bounds of this button if it is currently being animated. */
|
||||
Rectangle<int> getTargetBounds (TabBarButton* button) const;
|
||||
|
||||
//==============================================================================
|
||||
/** Callback method to indicate the selected tab has been changed.
|
||||
@see setCurrentTabIndex
|
||||
*/
|
||||
virtual void currentTabChanged (int newCurrentTabIndex,
|
||||
const String& newCurrentTabName);
|
||||
|
||||
/** Callback method to indicate that the user has right-clicked on a tab. */
|
||||
virtual void popupMenuClickOnTab (int tabIndex, const String& tabName);
|
||||
|
||||
/** Returns the colour of a tab.
|
||||
This is the colour that was specified in addTab().
|
||||
*/
|
||||
Colour getTabBackgroundColour (int tabIndex);
|
||||
|
||||
/** Changes the background colour of a tab.
|
||||
@see addTab, getTabBackgroundColour
|
||||
*/
|
||||
void setTabBackgroundColour (int tabIndex, Colour newColour);
|
||||
|
||||
//==============================================================================
|
||||
/** A set of colour IDs to use to change the colour of various aspects of the component.
|
||||
|
||||
These constants can be used either via the Component::setColour(), or LookAndFeel::setColour()
|
||||
methods.
|
||||
|
||||
@see Component::setColour, Component::findColour, LookAndFeel::setColour, LookAndFeel::findColour
|
||||
*/
|
||||
enum ColourIds
|
||||
{
|
||||
tabOutlineColourId = 0x1005812, /**< The colour to use to draw an outline around the tabs. */
|
||||
tabTextColourId = 0x1005813, /**< The colour to use to draw the tab names. If this isn't specified,
|
||||
the look and feel will choose an appropriate colour. */
|
||||
frontOutlineColourId = 0x1005814, /**< The colour to use to draw an outline around the currently-selected tab. */
|
||||
frontTextColourId = 0x1005815, /**< The colour to use to draw the currently-selected tab name. If
|
||||
this isn't specified, the look and feel will choose an appropriate
|
||||
colour. */
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** This abstract base class is implemented by LookAndFeel classes to provide
|
||||
window drawing functionality.
|
||||
*/
|
||||
struct JUCE_API LookAndFeelMethods
|
||||
{
|
||||
virtual ~LookAndFeelMethods() = default;
|
||||
|
||||
virtual int getTabButtonSpaceAroundImage() = 0;
|
||||
virtual int getTabButtonOverlap (int tabDepth) = 0;
|
||||
virtual int getTabButtonBestWidth (TabBarButton&, int tabDepth) = 0;
|
||||
virtual Rectangle<int> getTabButtonExtraComponentBounds (const TabBarButton&, Rectangle<int>& textArea, Component& extraComp) = 0;
|
||||
|
||||
virtual void drawTabButton (TabBarButton&, Graphics&, bool isMouseOver, bool isMouseDown) = 0;
|
||||
virtual Font getTabButtonFont (TabBarButton&, float height) = 0;
|
||||
virtual void drawTabButtonText (TabBarButton&, Graphics&, bool isMouseOver, bool isMouseDown) = 0;
|
||||
virtual void drawTabbedButtonBarBackground (TabbedButtonBar&, Graphics&) = 0;
|
||||
virtual void drawTabAreaBehindFrontButton (TabbedButtonBar&, Graphics&, int w, int h) = 0;
|
||||
|
||||
virtual void createTabButtonShape (TabBarButton&, Path& path, bool isMouseOver, bool isMouseDown) = 0;
|
||||
virtual void fillTabButtonShape (TabBarButton&, Graphics&, const Path& path, bool isMouseOver, bool isMouseDown) = 0;
|
||||
|
||||
virtual Button* createTabBarExtrasButton() = 0;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** @internal */
|
||||
void paint (Graphics&) override;
|
||||
/** @internal */
|
||||
void resized() override;
|
||||
/** @internal */
|
||||
void lookAndFeelChanged() override;
|
||||
|
||||
protected:
|
||||
//==============================================================================
|
||||
/** This creates one of the tabs.
|
||||
|
||||
If you need to use custom tab components, you can override this method and
|
||||
return your own class instead of the default.
|
||||
*/
|
||||
virtual TabBarButton* createTabButton (const String& tabName, int tabIndex);
|
||||
|
||||
private:
|
||||
struct TabInfo
|
||||
{
|
||||
std::unique_ptr<TabBarButton> button;
|
||||
String name;
|
||||
Colour colour;
|
||||
};
|
||||
|
||||
OwnedArray<TabInfo> tabs;
|
||||
|
||||
Orientation orientation;
|
||||
double minimumScale = 0.7;
|
||||
int currentTabIndex = -1;
|
||||
|
||||
class BehindFrontTabComp;
|
||||
std::unique_ptr<BehindFrontTabComp> behindFrontTab;
|
||||
std::unique_ptr<Button> extraTabsButton;
|
||||
|
||||
std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override;
|
||||
void showExtraItemsMenu();
|
||||
void updateTabPositions (bool animate);
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TabbedButtonBar)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
class TabbedButtonBar;
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/** In a TabbedButtonBar, this component is used for each of the buttons.
|
||||
|
||||
If you want to create a TabbedButtonBar with custom tab components, derive
|
||||
your component from this class, and override the TabbedButtonBar::createTabButton()
|
||||
method to create it instead of the default one.
|
||||
|
||||
@see TabbedButtonBar
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API TabBarButton : public Button
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates the tab button. */
|
||||
TabBarButton (const String& name, TabbedButtonBar& ownerBar);
|
||||
|
||||
/** Destructor. */
|
||||
~TabBarButton() override;
|
||||
|
||||
/** Returns the bar that contains this button. */
|
||||
TabbedButtonBar& getTabbedButtonBar() const { return owner; }
|
||||
|
||||
//==============================================================================
|
||||
/** When adding an extra component to the tab, this indicates which side of
|
||||
the text it should be placed on. */
|
||||
enum ExtraComponentPlacement
|
||||
{
|
||||
beforeText,
|
||||
afterText
|
||||
};
|
||||
|
||||
/** Sets an extra component that will be shown in the tab.
|
||||
|
||||
This optional component will be positioned inside the tab, either to the left or right
|
||||
of the text. You could use this to implement things like a close button or a graphical
|
||||
status indicator. If a non-null component is passed-in, the TabbedButtonBar will take
|
||||
ownership of it and delete it when required.
|
||||
*/
|
||||
void setExtraComponent (Component* extraTabComponent,
|
||||
ExtraComponentPlacement extraComponentPlacement);
|
||||
|
||||
/** Returns the custom component, if there is one. */
|
||||
Component* getExtraComponent() const noexcept { return extraComponent.get(); }
|
||||
|
||||
/** Returns the placement of the custom component, if there is one. */
|
||||
ExtraComponentPlacement getExtraComponentPlacement() const noexcept { return extraCompPlacement; }
|
||||
|
||||
/** Returns an area of the component that's safe to draw in.
|
||||
|
||||
This deals with the orientation of the tabs, which affects which side is
|
||||
touching the tabbed box's content component.
|
||||
*/
|
||||
Rectangle<int> getActiveArea() const;
|
||||
|
||||
/** Returns the area of the component that should contain its text. */
|
||||
Rectangle<int> getTextArea() const;
|
||||
|
||||
/** Returns this tab's index in its tab bar. */
|
||||
int getIndex() const;
|
||||
|
||||
/** Returns the colour of the tab. */
|
||||
Colour getTabBackgroundColour() const;
|
||||
|
||||
/** Returns true if this is the frontmost (selected) tab. */
|
||||
bool isFrontTab() const;
|
||||
|
||||
//==============================================================================
|
||||
/** Chooses the best length for the tab, given the specified depth.
|
||||
|
||||
If the tab is horizontal, this should return its width, and the depth
|
||||
specifies its height. If it's vertical, it should return the height, and
|
||||
the depth is actually its width.
|
||||
*/
|
||||
virtual int getBestTabLength (int depth);
|
||||
|
||||
//==============================================================================
|
||||
/** @internal */
|
||||
void paintButton (Graphics&, bool, bool) override;
|
||||
/** @internal */
|
||||
void clicked (const ModifierKeys&) override;
|
||||
/** @internal */
|
||||
bool hitTest (int x, int y) override;
|
||||
/** @internal */
|
||||
void resized() override;
|
||||
/** @internal */
|
||||
void childBoundsChanged (Component*) override;
|
||||
|
||||
protected:
|
||||
friend class TabbedButtonBar;
|
||||
TabbedButtonBar& owner;
|
||||
int overlapPixels = 0;
|
||||
|
||||
std::unique_ptr<Component> extraComponent;
|
||||
ExtraComponentPlacement extraCompPlacement = afterText;
|
||||
|
||||
private:
|
||||
using Button::clicked;
|
||||
void calcAreas (Rectangle<int>&, Rectangle<int>&) const;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TabBarButton)
|
||||
};
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A vertical or horizontal bar containing tabs that you can select.
|
||||
|
||||
You can use one of these to generate things like a dialog box that has
|
||||
tabbed pages you can flip between. Attach a ChangeListener to the
|
||||
button bar to be told when the user changes the page.
|
||||
|
||||
An easier method than doing this is to use a TabbedComponent, which
|
||||
contains its own TabbedButtonBar and which takes care of the layout
|
||||
and other housekeeping.
|
||||
|
||||
@see TabbedComponent
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API TabbedButtonBar : public Component,
|
||||
public ChangeBroadcaster
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** The placement of the tab-bar
|
||||
@see setOrientation, getOrientation
|
||||
*/
|
||||
enum Orientation
|
||||
{
|
||||
TabsAtTop,
|
||||
TabsAtBottom,
|
||||
TabsAtLeft,
|
||||
TabsAtRight
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** Creates a TabbedButtonBar with a given orientation.
|
||||
You can change the orientation later if you need to.
|
||||
*/
|
||||
TabbedButtonBar (Orientation orientation);
|
||||
|
||||
/** Destructor. */
|
||||
~TabbedButtonBar() override;
|
||||
|
||||
//==============================================================================
|
||||
/** Changes the bar's orientation.
|
||||
|
||||
This won't change the bar's actual size - you'll need to do that yourself,
|
||||
but this determines which direction the tabs go in, and which side they're
|
||||
stuck to.
|
||||
*/
|
||||
void setOrientation (Orientation orientation);
|
||||
|
||||
/** Returns the bar's current orientation.
|
||||
@see setOrientation
|
||||
*/
|
||||
Orientation getOrientation() const noexcept { return orientation; }
|
||||
|
||||
/** Returns true if the orientation is TabsAtLeft or TabsAtRight. */
|
||||
bool isVertical() const noexcept { return orientation == TabsAtLeft || orientation == TabsAtRight; }
|
||||
|
||||
/** Returns the thickness of the bar, which may be its width or height, depending on the orientation. */
|
||||
int getThickness() const noexcept { return isVertical() ? getWidth() : getHeight(); }
|
||||
|
||||
/** Changes the minimum scale factor to which the tabs can be compressed when trying to
|
||||
fit a lot of tabs on-screen.
|
||||
*/
|
||||
void setMinimumTabScaleFactor (double newMinimumScale);
|
||||
|
||||
//==============================================================================
|
||||
/** Deletes all the tabs from the bar.
|
||||
@see addTab
|
||||
*/
|
||||
void clearTabs();
|
||||
|
||||
/** Adds a tab to the bar.
|
||||
Tabs are added in left-to-right reading order.
|
||||
If this is the first tab added, it'll also be automatically selected.
|
||||
*/
|
||||
void addTab (const String& tabName,
|
||||
Colour tabBackgroundColour,
|
||||
int insertIndex);
|
||||
|
||||
/** Changes the name of one of the tabs. */
|
||||
void setTabName (int tabIndex, const String& newName);
|
||||
|
||||
/** Gets rid of one of the tabs. */
|
||||
void removeTab (int tabIndex, bool animate = false);
|
||||
|
||||
/** Moves a tab to a new index in the list.
|
||||
Pass -1 as the index to move it to the end of the list.
|
||||
*/
|
||||
void moveTab (int currentIndex, int newIndex, bool animate = false);
|
||||
|
||||
/** Returns the number of tabs in the bar. */
|
||||
int getNumTabs() const;
|
||||
|
||||
/** Returns a list of all the tab names in the bar. */
|
||||
StringArray getTabNames() const;
|
||||
|
||||
/** Changes the currently selected tab.
|
||||
This will send a change message and cause a synchronous callback to
|
||||
the currentTabChanged() method. (But if the given tab is already selected,
|
||||
nothing will be done).
|
||||
|
||||
To deselect all the tabs, use an index of -1.
|
||||
*/
|
||||
void setCurrentTabIndex (int newTabIndex, bool sendChangeMessage = true);
|
||||
|
||||
/** Returns the name of the currently selected tab.
|
||||
This could be an empty string if none are selected.
|
||||
*/
|
||||
String getCurrentTabName() const;
|
||||
|
||||
/** Returns the index of the currently selected tab.
|
||||
This could return -1 if none are selected.
|
||||
*/
|
||||
int getCurrentTabIndex() const noexcept { return currentTabIndex; }
|
||||
|
||||
/** Returns the button for a specific tab.
|
||||
The button that is returned may be deleted later by this component, so don't hang
|
||||
on to the pointer that is returned. A null pointer may be returned if the index is
|
||||
out of range.
|
||||
*/
|
||||
TabBarButton* getTabButton (int index) const;
|
||||
|
||||
/** Returns the index of a TabBarButton if it belongs to this bar. */
|
||||
int indexOfTabButton (const TabBarButton* button) const;
|
||||
|
||||
/** Returns the final bounds of this button if it is currently being animated. */
|
||||
Rectangle<int> getTargetBounds (TabBarButton* button) const;
|
||||
|
||||
//==============================================================================
|
||||
/** Callback method to indicate the selected tab has been changed.
|
||||
@see setCurrentTabIndex
|
||||
*/
|
||||
virtual void currentTabChanged (int newCurrentTabIndex,
|
||||
const String& newCurrentTabName);
|
||||
|
||||
/** Callback method to indicate that the user has right-clicked on a tab. */
|
||||
virtual void popupMenuClickOnTab (int tabIndex, const String& tabName);
|
||||
|
||||
/** Returns the colour of a tab.
|
||||
This is the colour that was specified in addTab().
|
||||
*/
|
||||
Colour getTabBackgroundColour (int tabIndex);
|
||||
|
||||
/** Changes the background colour of a tab.
|
||||
@see addTab, getTabBackgroundColour
|
||||
*/
|
||||
void setTabBackgroundColour (int tabIndex, Colour newColour);
|
||||
|
||||
//==============================================================================
|
||||
/** A set of colour IDs to use to change the colour of various aspects of the component.
|
||||
|
||||
These constants can be used either via the Component::setColour(), or LookAndFeel::setColour()
|
||||
methods.
|
||||
|
||||
@see Component::setColour, Component::findColour, LookAndFeel::setColour, LookAndFeel::findColour
|
||||
*/
|
||||
enum ColourIds
|
||||
{
|
||||
tabOutlineColourId = 0x1005812, /**< The colour to use to draw an outline around the tabs. */
|
||||
tabTextColourId = 0x1005813, /**< The colour to use to draw the tab names. If this isn't specified,
|
||||
the look and feel will choose an appropriate colour. */
|
||||
frontOutlineColourId = 0x1005814, /**< The colour to use to draw an outline around the currently-selected tab. */
|
||||
frontTextColourId = 0x1005815, /**< The colour to use to draw the currently-selected tab name. If
|
||||
this isn't specified, the look and feel will choose an appropriate
|
||||
colour. */
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** This abstract base class is implemented by LookAndFeel classes to provide
|
||||
window drawing functionality.
|
||||
*/
|
||||
struct JUCE_API LookAndFeelMethods
|
||||
{
|
||||
virtual ~LookAndFeelMethods() = default;
|
||||
|
||||
virtual int getTabButtonSpaceAroundImage() = 0;
|
||||
virtual int getTabButtonOverlap (int tabDepth) = 0;
|
||||
virtual int getTabButtonBestWidth (TabBarButton&, int tabDepth) = 0;
|
||||
virtual Rectangle<int> getTabButtonExtraComponentBounds (const TabBarButton&, Rectangle<int>& textArea, Component& extraComp) = 0;
|
||||
|
||||
virtual void drawTabButton (TabBarButton&, Graphics&, bool isMouseOver, bool isMouseDown) = 0;
|
||||
virtual Font getTabButtonFont (TabBarButton&, float height) = 0;
|
||||
virtual void drawTabButtonText (TabBarButton&, Graphics&, bool isMouseOver, bool isMouseDown) = 0;
|
||||
virtual void drawTabbedButtonBarBackground (TabbedButtonBar&, Graphics&) = 0;
|
||||
virtual void drawTabAreaBehindFrontButton (TabbedButtonBar&, Graphics&, int w, int h) = 0;
|
||||
|
||||
virtual void createTabButtonShape (TabBarButton&, Path& path, bool isMouseOver, bool isMouseDown) = 0;
|
||||
virtual void fillTabButtonShape (TabBarButton&, Graphics&, const Path& path, bool isMouseOver, bool isMouseDown) = 0;
|
||||
|
||||
virtual Button* createTabBarExtrasButton() = 0;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** @internal */
|
||||
void paint (Graphics&) override;
|
||||
/** @internal */
|
||||
void resized() override;
|
||||
/** @internal */
|
||||
void lookAndFeelChanged() override;
|
||||
|
||||
protected:
|
||||
//==============================================================================
|
||||
/** This creates one of the tabs.
|
||||
|
||||
If you need to use custom tab components, you can override this method and
|
||||
return your own class instead of the default.
|
||||
*/
|
||||
virtual TabBarButton* createTabButton (const String& tabName, int tabIndex);
|
||||
|
||||
private:
|
||||
struct TabInfo
|
||||
{
|
||||
std::unique_ptr<TabBarButton> button;
|
||||
String name;
|
||||
Colour colour;
|
||||
};
|
||||
|
||||
OwnedArray<TabInfo> tabs;
|
||||
|
||||
Orientation orientation;
|
||||
double minimumScale = 0.7;
|
||||
int currentTabIndex = -1;
|
||||
|
||||
class BehindFrontTabComp;
|
||||
std::unique_ptr<BehindFrontTabComp> behindFrontTab;
|
||||
std::unique_ptr<Button> extraTabsButton;
|
||||
|
||||
std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override;
|
||||
void showExtraItemsMenu();
|
||||
void updateTabPositions (bool animate);
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TabbedButtonBar)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
|
@ -1,324 +1,319 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
namespace TabbedComponentHelpers
|
||||
{
|
||||
const Identifier deleteComponentId ("deleteByTabComp_");
|
||||
|
||||
static void deleteIfNecessary (Component* comp)
|
||||
{
|
||||
if (comp != nullptr && (bool) comp->getProperties() [deleteComponentId])
|
||||
delete comp;
|
||||
}
|
||||
|
||||
static Rectangle<int> getTabArea (Rectangle<int>& content, BorderSize<int>& outline,
|
||||
TabbedButtonBar::Orientation orientation, int tabDepth)
|
||||
{
|
||||
switch (orientation)
|
||||
{
|
||||
case TabbedButtonBar::TabsAtTop: outline.setTop (0); return content.removeFromTop (tabDepth);
|
||||
case TabbedButtonBar::TabsAtBottom: outline.setBottom (0); return content.removeFromBottom (tabDepth);
|
||||
case TabbedButtonBar::TabsAtLeft: outline.setLeft (0); return content.removeFromLeft (tabDepth);
|
||||
case TabbedButtonBar::TabsAtRight: outline.setRight (0); return content.removeFromRight (tabDepth);
|
||||
default: jassertfalse; break;
|
||||
}
|
||||
|
||||
return Rectangle<int>();
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
struct TabbedComponent::ButtonBar : public TabbedButtonBar
|
||||
{
|
||||
ButtonBar (TabbedComponent& tabComp, TabbedButtonBar::Orientation o)
|
||||
: TabbedButtonBar (o), owner (tabComp)
|
||||
{
|
||||
}
|
||||
|
||||
void currentTabChanged (int newCurrentTabIndex, const String& newTabName)
|
||||
{
|
||||
owner.changeCallback (newCurrentTabIndex, newTabName);
|
||||
}
|
||||
|
||||
void popupMenuClickOnTab (int tabIndex, const String& tabName)
|
||||
{
|
||||
owner.popupMenuClickOnTab (tabIndex, tabName);
|
||||
}
|
||||
|
||||
Colour getTabBackgroundColour (int tabIndex)
|
||||
{
|
||||
return owner.tabs->getTabBackgroundColour (tabIndex);
|
||||
}
|
||||
|
||||
TabBarButton* createTabButton (const String& tabName, int tabIndex)
|
||||
{
|
||||
return owner.createTabButton (tabName, tabIndex);
|
||||
}
|
||||
|
||||
TabbedComponent& owner;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ButtonBar)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
TabbedComponent::TabbedComponent (TabbedButtonBar::Orientation orientation)
|
||||
{
|
||||
tabs.reset (new ButtonBar (*this, orientation));
|
||||
addAndMakeVisible (tabs.get());
|
||||
}
|
||||
|
||||
TabbedComponent::~TabbedComponent()
|
||||
{
|
||||
clearTabs();
|
||||
tabs.reset();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void TabbedComponent::setOrientation (TabbedButtonBar::Orientation orientation)
|
||||
{
|
||||
tabs->setOrientation (orientation);
|
||||
resized();
|
||||
}
|
||||
|
||||
TabbedButtonBar::Orientation TabbedComponent::getOrientation() const noexcept
|
||||
{
|
||||
return tabs->getOrientation();
|
||||
}
|
||||
|
||||
void TabbedComponent::setTabBarDepth (int newDepth)
|
||||
{
|
||||
if (tabDepth != newDepth)
|
||||
{
|
||||
tabDepth = newDepth;
|
||||
resized();
|
||||
}
|
||||
}
|
||||
|
||||
TabBarButton* TabbedComponent::createTabButton (const String& tabName, int /*tabIndex*/)
|
||||
{
|
||||
return new TabBarButton (tabName, *tabs);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void TabbedComponent::clearTabs()
|
||||
{
|
||||
if (panelComponent != nullptr)
|
||||
{
|
||||
panelComponent->setVisible (false);
|
||||
removeChildComponent (panelComponent.get());
|
||||
panelComponent = nullptr;
|
||||
}
|
||||
|
||||
tabs->clearTabs();
|
||||
|
||||
for (int i = contentComponents.size(); --i >= 0;)
|
||||
TabbedComponentHelpers::deleteIfNecessary (contentComponents.getReference (i));
|
||||
|
||||
contentComponents.clear();
|
||||
}
|
||||
|
||||
void TabbedComponent::addTab (const String& tabName,
|
||||
Colour tabBackgroundColour,
|
||||
Component* contentComponent,
|
||||
bool deleteComponentWhenNotNeeded,
|
||||
int insertIndex)
|
||||
{
|
||||
contentComponents.insert (insertIndex, WeakReference<Component> (contentComponent));
|
||||
|
||||
if (deleteComponentWhenNotNeeded && contentComponent != nullptr)
|
||||
contentComponent->getProperties().set (TabbedComponentHelpers::deleteComponentId, true);
|
||||
|
||||
tabs->addTab (tabName, tabBackgroundColour, insertIndex);
|
||||
resized();
|
||||
}
|
||||
|
||||
void TabbedComponent::setTabDeleteComponentWhenNotNeeded (int tabIndex, bool flag)
|
||||
{
|
||||
contentComponents [tabIndex]->getProperties().set (TabbedComponentHelpers::deleteComponentId, flag);
|
||||
}
|
||||
|
||||
void TabbedComponent::setTabName (int tabIndex, const String& newName)
|
||||
{
|
||||
tabs->setTabName (tabIndex, newName);
|
||||
}
|
||||
|
||||
void TabbedComponent::removeTab (int tabIndex)
|
||||
{
|
||||
if (isPositiveAndBelow (tabIndex, contentComponents.size()))
|
||||
{
|
||||
TabbedComponentHelpers::deleteIfNecessary (contentComponents.getReference (tabIndex).get());
|
||||
contentComponents.remove (tabIndex);
|
||||
tabs->removeTab (tabIndex);
|
||||
}
|
||||
}
|
||||
|
||||
void TabbedComponent::moveTab (int currentIndex, int newIndex, bool animate)
|
||||
{
|
||||
contentComponents.move (currentIndex, newIndex);
|
||||
tabs->moveTab (currentIndex, newIndex, animate);
|
||||
}
|
||||
|
||||
int TabbedComponent::getNumTabs() const
|
||||
{
|
||||
return tabs->getNumTabs();
|
||||
}
|
||||
|
||||
StringArray TabbedComponent::getTabNames() const
|
||||
{
|
||||
return tabs->getTabNames();
|
||||
}
|
||||
|
||||
Component* TabbedComponent::getTabContentComponent (int tabIndex) const noexcept
|
||||
{
|
||||
return contentComponents[tabIndex].get();
|
||||
}
|
||||
|
||||
Colour TabbedComponent::getTabBackgroundColour (int tabIndex) const noexcept
|
||||
{
|
||||
return tabs->getTabBackgroundColour (tabIndex);
|
||||
}
|
||||
|
||||
void TabbedComponent::setTabBackgroundColour (int tabIndex, Colour newColour)
|
||||
{
|
||||
tabs->setTabBackgroundColour (tabIndex, newColour);
|
||||
|
||||
if (getCurrentTabIndex() == tabIndex)
|
||||
repaint();
|
||||
}
|
||||
|
||||
void TabbedComponent::setCurrentTabIndex (int newTabIndex, bool sendChangeMessage)
|
||||
{
|
||||
tabs->setCurrentTabIndex (newTabIndex, sendChangeMessage);
|
||||
}
|
||||
|
||||
int TabbedComponent::getCurrentTabIndex() const
|
||||
{
|
||||
return tabs->getCurrentTabIndex();
|
||||
}
|
||||
|
||||
String TabbedComponent::getCurrentTabName() const
|
||||
{
|
||||
return tabs->getCurrentTabName();
|
||||
}
|
||||
|
||||
void TabbedComponent::setOutline (int thickness)
|
||||
{
|
||||
outlineThickness = thickness;
|
||||
resized();
|
||||
repaint();
|
||||
}
|
||||
|
||||
void TabbedComponent::setIndent (int indentThickness)
|
||||
{
|
||||
edgeIndent = indentThickness;
|
||||
resized();
|
||||
repaint();
|
||||
}
|
||||
|
||||
void TabbedComponent::paint (Graphics& g)
|
||||
{
|
||||
g.fillAll (findColour (backgroundColourId));
|
||||
|
||||
auto content = getLocalBounds();
|
||||
BorderSize<int> outline (outlineThickness);
|
||||
TabbedComponentHelpers::getTabArea (content, outline, getOrientation(), tabDepth);
|
||||
|
||||
g.reduceClipRegion (content);
|
||||
g.fillAll (tabs->getTabBackgroundColour (getCurrentTabIndex()));
|
||||
|
||||
if (outlineThickness > 0)
|
||||
{
|
||||
RectangleList<int> rl (content);
|
||||
rl.subtract (outline.subtractedFrom (content));
|
||||
|
||||
g.reduceClipRegion (rl);
|
||||
g.fillAll (findColour (outlineColourId));
|
||||
}
|
||||
}
|
||||
|
||||
void TabbedComponent::resized()
|
||||
{
|
||||
auto content = getLocalBounds();
|
||||
BorderSize<int> outline (outlineThickness);
|
||||
|
||||
tabs->setBounds (TabbedComponentHelpers::getTabArea (content, outline, getOrientation(), tabDepth));
|
||||
content = BorderSize<int> (edgeIndent).subtractedFrom (outline.subtractedFrom (content));
|
||||
|
||||
for (auto& c : contentComponents)
|
||||
if (auto comp = c.get())
|
||||
comp->setBounds (content);
|
||||
}
|
||||
|
||||
void TabbedComponent::lookAndFeelChanged()
|
||||
{
|
||||
for (auto& c : contentComponents)
|
||||
if (auto comp = c.get())
|
||||
comp->lookAndFeelChanged();
|
||||
}
|
||||
|
||||
void TabbedComponent::changeCallback (int newCurrentTabIndex, const String& newTabName)
|
||||
{
|
||||
auto* newPanelComp = getTabContentComponent (getCurrentTabIndex());
|
||||
|
||||
if (newPanelComp != panelComponent)
|
||||
{
|
||||
if (panelComponent != nullptr)
|
||||
{
|
||||
panelComponent->setVisible (false);
|
||||
removeChildComponent (panelComponent);
|
||||
}
|
||||
|
||||
panelComponent = newPanelComp;
|
||||
|
||||
if (panelComponent != nullptr)
|
||||
{
|
||||
// do these ops as two stages instead of addAndMakeVisible() so that the
|
||||
// component has always got a parent when it gets the visibilityChanged() callback
|
||||
addChildComponent (panelComponent);
|
||||
panelComponent->sendLookAndFeelChange();
|
||||
panelComponent->setVisible (true);
|
||||
panelComponent->toFront (true);
|
||||
}
|
||||
|
||||
repaint();
|
||||
}
|
||||
|
||||
resized();
|
||||
currentTabChanged (newCurrentTabIndex, newTabName);
|
||||
}
|
||||
|
||||
void TabbedComponent::currentTabChanged (int, const String&) {}
|
||||
void TabbedComponent::popupMenuClickOnTab (int, const String&) {}
|
||||
|
||||
//==============================================================================
|
||||
std::unique_ptr<AccessibilityHandler> TabbedComponent::createAccessibilityHandler()
|
||||
{
|
||||
return std::make_unique<AccessibilityHandler> (*this, AccessibilityRole::group);
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
namespace TabbedComponentHelpers
|
||||
{
|
||||
const Identifier deleteComponentId ("deleteByTabComp_");
|
||||
|
||||
static void deleteIfNecessary (Component* comp)
|
||||
{
|
||||
if (comp != nullptr && (bool) comp->getProperties() [deleteComponentId])
|
||||
delete comp;
|
||||
}
|
||||
|
||||
static Rectangle<int> getTabArea (Rectangle<int>& content, BorderSize<int>& outline,
|
||||
TabbedButtonBar::Orientation orientation, int tabDepth)
|
||||
{
|
||||
switch (orientation)
|
||||
{
|
||||
case TabbedButtonBar::TabsAtTop: outline.setTop (0); return content.removeFromTop (tabDepth);
|
||||
case TabbedButtonBar::TabsAtBottom: outline.setBottom (0); return content.removeFromBottom (tabDepth);
|
||||
case TabbedButtonBar::TabsAtLeft: outline.setLeft (0); return content.removeFromLeft (tabDepth);
|
||||
case TabbedButtonBar::TabsAtRight: outline.setRight (0); return content.removeFromRight (tabDepth);
|
||||
default: jassertfalse; break;
|
||||
}
|
||||
|
||||
return Rectangle<int>();
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
struct TabbedComponent::ButtonBar : public TabbedButtonBar
|
||||
{
|
||||
ButtonBar (TabbedComponent& tabComp, TabbedButtonBar::Orientation o)
|
||||
: TabbedButtonBar (o), owner (tabComp)
|
||||
{
|
||||
}
|
||||
|
||||
void currentTabChanged (int newCurrentTabIndex, const String& newTabName)
|
||||
{
|
||||
owner.changeCallback (newCurrentTabIndex, newTabName);
|
||||
}
|
||||
|
||||
void popupMenuClickOnTab (int tabIndex, const String& tabName)
|
||||
{
|
||||
owner.popupMenuClickOnTab (tabIndex, tabName);
|
||||
}
|
||||
|
||||
Colour getTabBackgroundColour (int tabIndex)
|
||||
{
|
||||
return owner.tabs->getTabBackgroundColour (tabIndex);
|
||||
}
|
||||
|
||||
TabBarButton* createTabButton (const String& tabName, int tabIndex)
|
||||
{
|
||||
return owner.createTabButton (tabName, tabIndex);
|
||||
}
|
||||
|
||||
TabbedComponent& owner;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ButtonBar)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
TabbedComponent::TabbedComponent (TabbedButtonBar::Orientation orientation)
|
||||
{
|
||||
tabs.reset (new ButtonBar (*this, orientation));
|
||||
addAndMakeVisible (tabs.get());
|
||||
}
|
||||
|
||||
TabbedComponent::~TabbedComponent()
|
||||
{
|
||||
clearTabs();
|
||||
tabs.reset();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void TabbedComponent::setOrientation (TabbedButtonBar::Orientation orientation)
|
||||
{
|
||||
tabs->setOrientation (orientation);
|
||||
resized();
|
||||
}
|
||||
|
||||
TabbedButtonBar::Orientation TabbedComponent::getOrientation() const noexcept
|
||||
{
|
||||
return tabs->getOrientation();
|
||||
}
|
||||
|
||||
void TabbedComponent::setTabBarDepth (int newDepth)
|
||||
{
|
||||
if (tabDepth != newDepth)
|
||||
{
|
||||
tabDepth = newDepth;
|
||||
resized();
|
||||
}
|
||||
}
|
||||
|
||||
TabBarButton* TabbedComponent::createTabButton (const String& tabName, int /*tabIndex*/)
|
||||
{
|
||||
return new TabBarButton (tabName, *tabs);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void TabbedComponent::clearTabs()
|
||||
{
|
||||
if (panelComponent != nullptr)
|
||||
{
|
||||
panelComponent->setVisible (false);
|
||||
removeChildComponent (panelComponent.get());
|
||||
panelComponent = nullptr;
|
||||
}
|
||||
|
||||
tabs->clearTabs();
|
||||
|
||||
for (int i = contentComponents.size(); --i >= 0;)
|
||||
TabbedComponentHelpers::deleteIfNecessary (contentComponents.getReference (i));
|
||||
|
||||
contentComponents.clear();
|
||||
}
|
||||
|
||||
void TabbedComponent::addTab (const String& tabName,
|
||||
Colour tabBackgroundColour,
|
||||
Component* contentComponent,
|
||||
bool deleteComponentWhenNotNeeded,
|
||||
int insertIndex)
|
||||
{
|
||||
contentComponents.insert (insertIndex, WeakReference<Component> (contentComponent));
|
||||
|
||||
if (deleteComponentWhenNotNeeded && contentComponent != nullptr)
|
||||
contentComponent->getProperties().set (TabbedComponentHelpers::deleteComponentId, true);
|
||||
|
||||
tabs->addTab (tabName, tabBackgroundColour, insertIndex);
|
||||
resized();
|
||||
}
|
||||
|
||||
void TabbedComponent::setTabName (int tabIndex, const String& newName)
|
||||
{
|
||||
tabs->setTabName (tabIndex, newName);
|
||||
}
|
||||
|
||||
void TabbedComponent::removeTab (int tabIndex)
|
||||
{
|
||||
if (isPositiveAndBelow (tabIndex, contentComponents.size()))
|
||||
{
|
||||
TabbedComponentHelpers::deleteIfNecessary (contentComponents.getReference (tabIndex).get());
|
||||
contentComponents.remove (tabIndex);
|
||||
tabs->removeTab (tabIndex);
|
||||
}
|
||||
}
|
||||
|
||||
void TabbedComponent::moveTab (int currentIndex, int newIndex, bool animate)
|
||||
{
|
||||
contentComponents.move (currentIndex, newIndex);
|
||||
tabs->moveTab (currentIndex, newIndex, animate);
|
||||
}
|
||||
|
||||
int TabbedComponent::getNumTabs() const
|
||||
{
|
||||
return tabs->getNumTabs();
|
||||
}
|
||||
|
||||
StringArray TabbedComponent::getTabNames() const
|
||||
{
|
||||
return tabs->getTabNames();
|
||||
}
|
||||
|
||||
Component* TabbedComponent::getTabContentComponent (int tabIndex) const noexcept
|
||||
{
|
||||
return contentComponents[tabIndex].get();
|
||||
}
|
||||
|
||||
Colour TabbedComponent::getTabBackgroundColour (int tabIndex) const noexcept
|
||||
{
|
||||
return tabs->getTabBackgroundColour (tabIndex);
|
||||
}
|
||||
|
||||
void TabbedComponent::setTabBackgroundColour (int tabIndex, Colour newColour)
|
||||
{
|
||||
tabs->setTabBackgroundColour (tabIndex, newColour);
|
||||
|
||||
if (getCurrentTabIndex() == tabIndex)
|
||||
repaint();
|
||||
}
|
||||
|
||||
void TabbedComponent::setCurrentTabIndex (int newTabIndex, bool sendChangeMessage)
|
||||
{
|
||||
tabs->setCurrentTabIndex (newTabIndex, sendChangeMessage);
|
||||
}
|
||||
|
||||
int TabbedComponent::getCurrentTabIndex() const
|
||||
{
|
||||
return tabs->getCurrentTabIndex();
|
||||
}
|
||||
|
||||
String TabbedComponent::getCurrentTabName() const
|
||||
{
|
||||
return tabs->getCurrentTabName();
|
||||
}
|
||||
|
||||
void TabbedComponent::setOutline (int thickness)
|
||||
{
|
||||
outlineThickness = thickness;
|
||||
resized();
|
||||
repaint();
|
||||
}
|
||||
|
||||
void TabbedComponent::setIndent (int indentThickness)
|
||||
{
|
||||
edgeIndent = indentThickness;
|
||||
resized();
|
||||
repaint();
|
||||
}
|
||||
|
||||
void TabbedComponent::paint (Graphics& g)
|
||||
{
|
||||
g.fillAll (findColour (backgroundColourId));
|
||||
|
||||
auto content = getLocalBounds();
|
||||
BorderSize<int> outline (outlineThickness);
|
||||
TabbedComponentHelpers::getTabArea (content, outline, getOrientation(), tabDepth);
|
||||
|
||||
g.reduceClipRegion (content);
|
||||
g.fillAll (tabs->getTabBackgroundColour (getCurrentTabIndex()));
|
||||
|
||||
if (outlineThickness > 0)
|
||||
{
|
||||
RectangleList<int> rl (content);
|
||||
rl.subtract (outline.subtractedFrom (content));
|
||||
|
||||
g.reduceClipRegion (rl);
|
||||
g.fillAll (findColour (outlineColourId));
|
||||
}
|
||||
}
|
||||
|
||||
void TabbedComponent::resized()
|
||||
{
|
||||
auto content = getLocalBounds();
|
||||
BorderSize<int> outline (outlineThickness);
|
||||
|
||||
tabs->setBounds (TabbedComponentHelpers::getTabArea (content, outline, getOrientation(), tabDepth));
|
||||
content = BorderSize<int> (edgeIndent).subtractedFrom (outline.subtractedFrom (content));
|
||||
|
||||
for (auto& c : contentComponents)
|
||||
if (auto comp = c.get())
|
||||
comp->setBounds (content);
|
||||
}
|
||||
|
||||
void TabbedComponent::lookAndFeelChanged()
|
||||
{
|
||||
for (auto& c : contentComponents)
|
||||
if (auto comp = c.get())
|
||||
comp->lookAndFeelChanged();
|
||||
}
|
||||
|
||||
void TabbedComponent::changeCallback (int newCurrentTabIndex, const String& newTabName)
|
||||
{
|
||||
auto* newPanelComp = getTabContentComponent (getCurrentTabIndex());
|
||||
|
||||
if (newPanelComp != panelComponent)
|
||||
{
|
||||
if (panelComponent != nullptr)
|
||||
{
|
||||
panelComponent->setVisible (false);
|
||||
removeChildComponent (panelComponent);
|
||||
}
|
||||
|
||||
panelComponent = newPanelComp;
|
||||
|
||||
if (panelComponent != nullptr)
|
||||
{
|
||||
// do these ops as two stages instead of addAndMakeVisible() so that the
|
||||
// component has always got a parent when it gets the visibilityChanged() callback
|
||||
addChildComponent (panelComponent);
|
||||
panelComponent->sendLookAndFeelChange();
|
||||
panelComponent->setVisible (true);
|
||||
panelComponent->toFront (true);
|
||||
}
|
||||
|
||||
repaint();
|
||||
}
|
||||
|
||||
resized();
|
||||
currentTabChanged (newCurrentTabIndex, newTabName);
|
||||
}
|
||||
|
||||
void TabbedComponent::currentTabChanged (int, const String&) {}
|
||||
void TabbedComponent::popupMenuClickOnTab (int, const String&) {}
|
||||
|
||||
//==============================================================================
|
||||
std::unique_ptr<AccessibilityHandler> TabbedComponent::createAccessibilityHandler()
|
||||
{
|
||||
return std::make_unique<AccessibilityHandler> (*this, AccessibilityRole::group);
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
|
@ -1,230 +1,226 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A component with a TabbedButtonBar along one of its sides.
|
||||
|
||||
This makes it easy to create a set of tabbed pages, just add a bunch of tabs
|
||||
with addTab(), and this will take care of showing the pages for you when the
|
||||
user clicks on a different tab.
|
||||
|
||||
@see TabbedButtonBar
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API TabbedComponent : public Component
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates a TabbedComponent, specifying where the tabs should be placed.
|
||||
Once created, add some tabs with the addTab() method.
|
||||
*/
|
||||
explicit TabbedComponent (TabbedButtonBar::Orientation orientation);
|
||||
|
||||
/** Destructor. */
|
||||
~TabbedComponent() override;
|
||||
|
||||
//==============================================================================
|
||||
/** Changes the placement of the tabs.
|
||||
|
||||
This will rearrange the layout to place the tabs along the appropriate
|
||||
side of this component, and will shift the content component accordingly.
|
||||
|
||||
@see TabbedButtonBar::setOrientation
|
||||
*/
|
||||
void setOrientation (TabbedButtonBar::Orientation orientation);
|
||||
|
||||
/** Returns the current tab placement.
|
||||
@see setOrientation, TabbedButtonBar::getOrientation
|
||||
*/
|
||||
TabbedButtonBar::Orientation getOrientation() const noexcept;
|
||||
|
||||
/** Specifies how many pixels wide or high the tab-bar should be.
|
||||
|
||||
If the tabs are placed along the top or bottom, this specified the height
|
||||
of the bar; if they're along the left or right edges, it'll be the width
|
||||
of the bar.
|
||||
*/
|
||||
void setTabBarDepth (int newDepth);
|
||||
|
||||
/** Returns the current thickness of the tab bar.
|
||||
@see setTabBarDepth
|
||||
*/
|
||||
int getTabBarDepth() const noexcept { return tabDepth; }
|
||||
|
||||
/** Specifies the thickness of an outline that should be drawn around the content component.
|
||||
|
||||
If this thickness is > 0, a line will be drawn around the three sides of the content
|
||||
component which don't touch the tab-bar, and the content component will be inset by this amount.
|
||||
|
||||
To set the colour of the line, use setColour (outlineColourId, ...).
|
||||
*/
|
||||
void setOutline (int newThickness);
|
||||
|
||||
/** Specifies a gap to leave around the edge of the content component.
|
||||
Each edge of the content component will be indented by the given number of pixels.
|
||||
*/
|
||||
void setIndent (int indentThickness);
|
||||
|
||||
//==============================================================================
|
||||
/** Removes all the tabs from the bar.
|
||||
@see TabbedButtonBar::clearTabs
|
||||
*/
|
||||
void clearTabs();
|
||||
|
||||
/** Adds a tab to the tab-bar.
|
||||
|
||||
The component passed in will be shown for the tab. If deleteComponentWhenNotNeeded
|
||||
is true, then the TabbedComponent will take ownership of the component and will delete
|
||||
it when the tab is removed or when this object is deleted.
|
||||
|
||||
@see TabbedButtonBar::addTab
|
||||
*/
|
||||
void addTab (const String& tabName,
|
||||
Colour tabBackgroundColour,
|
||||
Component* contentComponent,
|
||||
bool deleteComponentWhenNotNeeded,
|
||||
int insertIndex = -1);
|
||||
|
||||
/** Changes the name of one of the tabs. */
|
||||
void setTabName (int tabIndex, const String& newName);
|
||||
|
||||
/** Gets rid of one of the tabs. */
|
||||
void removeTab (int tabIndex);
|
||||
|
||||
/** Moves a tab to a new index in the list.
|
||||
Pass -1 as the index to move it to the end of the list.
|
||||
*/
|
||||
void moveTab (int currentIndex, int newIndex, bool animate = false);
|
||||
|
||||
/** Returns the number of tabs in the bar. */
|
||||
int getNumTabs() const;
|
||||
|
||||
/** Returns a list of all the tab names in the bar. */
|
||||
StringArray getTabNames() const;
|
||||
|
||||
/** Returns the content component that was added for the given index.
|
||||
Be careful not to reposition or delete the components that are returned, as
|
||||
this will interfere with the TabbedComponent's behaviour.
|
||||
*/
|
||||
Component* getTabContentComponent (int tabIndex) const noexcept;
|
||||
|
||||
/** Returns the colour of one of the tabs. */
|
||||
Colour getTabBackgroundColour (int tabIndex) const noexcept;
|
||||
|
||||
/** Changes the background colour of one of the tabs. */
|
||||
void setTabBackgroundColour (int tabIndex, Colour newColour);
|
||||
|
||||
/** Set deletion policy on removal */
|
||||
void setTabDeleteComponentWhenNotNeeded (int tabIndex, bool flag);
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/** Changes the currently-selected tab.
|
||||
To deselect all the tabs, pass -1 as the index.
|
||||
@see TabbedButtonBar::setCurrentTabIndex
|
||||
*/
|
||||
void setCurrentTabIndex (int newTabIndex, bool sendChangeMessage = true);
|
||||
|
||||
/** Returns the index of the currently selected tab.
|
||||
@see addTab, TabbedButtonBar::getCurrentTabIndex()
|
||||
*/
|
||||
int getCurrentTabIndex() const;
|
||||
|
||||
/** Returns the name of the currently selected tab.
|
||||
@see addTab, TabbedButtonBar::getCurrentTabName()
|
||||
*/
|
||||
String getCurrentTabName() const;
|
||||
|
||||
/** Returns the current component that's filling the panel.
|
||||
This will return nullptr if there isn't one.
|
||||
*/
|
||||
Component* getCurrentContentComponent() const noexcept { return panelComponent.get(); }
|
||||
|
||||
//==============================================================================
|
||||
/** Callback method to indicate the selected tab has been changed.
|
||||
@see setCurrentTabIndex
|
||||
*/
|
||||
virtual void currentTabChanged (int newCurrentTabIndex, const String& newCurrentTabName);
|
||||
|
||||
/** Callback method to indicate that the user has right-clicked on a tab. */
|
||||
virtual void popupMenuClickOnTab (int tabIndex, const String& tabName);
|
||||
|
||||
/** Returns the tab button bar component that is being used. */
|
||||
TabbedButtonBar& getTabbedButtonBar() const noexcept { return *tabs; }
|
||||
|
||||
//==============================================================================
|
||||
/** A set of colour IDs to use to change the colour of various aspects of the component.
|
||||
|
||||
These constants can be used either via the Component::setColour(), or LookAndFeel::setColour()
|
||||
methods.
|
||||
|
||||
@see Component::setColour, Component::findColour, LookAndFeel::setColour, LookAndFeel::findColour
|
||||
*/
|
||||
enum ColourIds
|
||||
{
|
||||
backgroundColourId = 0x1005800, /**< The colour to fill the background behind the tabs. */
|
||||
outlineColourId = 0x1005801, /**< The colour to use to draw an outline around the content.
|
||||
(See setOutline) */
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** @internal */
|
||||
void paint (Graphics&) override;
|
||||
/** @internal */
|
||||
void resized() override;
|
||||
/** @internal */
|
||||
void lookAndFeelChanged() override;
|
||||
|
||||
protected:
|
||||
//==============================================================================
|
||||
/** This creates one of the tab buttons.
|
||||
|
||||
If you need to use custom tab components, you can override this method and
|
||||
return your own class instead of the default.
|
||||
*/
|
||||
virtual TabBarButton* createTabButton (const String& tabName, int tabIndex);
|
||||
|
||||
/** @internal */
|
||||
std::unique_ptr<TabbedButtonBar> tabs;
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
Array<WeakReference<Component>> contentComponents;
|
||||
WeakReference<Component> panelComponent;
|
||||
int tabDepth = 30, outlineThickness = 1, edgeIndent = 0;
|
||||
|
||||
struct ButtonBar;
|
||||
std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override;
|
||||
void changeCallback (int newCurrentTabIndex, const String& newTabName);
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TabbedComponent)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A component with a TabbedButtonBar along one of its sides.
|
||||
|
||||
This makes it easy to create a set of tabbed pages, just add a bunch of tabs
|
||||
with addTab(), and this will take care of showing the pages for you when the
|
||||
user clicks on a different tab.
|
||||
|
||||
@see TabbedButtonBar
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API TabbedComponent : public Component
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates a TabbedComponent, specifying where the tabs should be placed.
|
||||
Once created, add some tabs with the addTab() method.
|
||||
*/
|
||||
explicit TabbedComponent (TabbedButtonBar::Orientation orientation);
|
||||
|
||||
/** Destructor. */
|
||||
~TabbedComponent() override;
|
||||
|
||||
//==============================================================================
|
||||
/** Changes the placement of the tabs.
|
||||
|
||||
This will rearrange the layout to place the tabs along the appropriate
|
||||
side of this component, and will shift the content component accordingly.
|
||||
|
||||
@see TabbedButtonBar::setOrientation
|
||||
*/
|
||||
void setOrientation (TabbedButtonBar::Orientation orientation);
|
||||
|
||||
/** Returns the current tab placement.
|
||||
@see setOrientation, TabbedButtonBar::getOrientation
|
||||
*/
|
||||
TabbedButtonBar::Orientation getOrientation() const noexcept;
|
||||
|
||||
/** Specifies how many pixels wide or high the tab-bar should be.
|
||||
|
||||
If the tabs are placed along the top or bottom, this specified the height
|
||||
of the bar; if they're along the left or right edges, it'll be the width
|
||||
of the bar.
|
||||
*/
|
||||
void setTabBarDepth (int newDepth);
|
||||
|
||||
/** Returns the current thickness of the tab bar.
|
||||
@see setTabBarDepth
|
||||
*/
|
||||
int getTabBarDepth() const noexcept { return tabDepth; }
|
||||
|
||||
/** Specifies the thickness of an outline that should be drawn around the content component.
|
||||
|
||||
If this thickness is > 0, a line will be drawn around the three sides of the content
|
||||
component which don't touch the tab-bar, and the content component will be inset by this amount.
|
||||
|
||||
To set the colour of the line, use setColour (outlineColourId, ...).
|
||||
*/
|
||||
void setOutline (int newThickness);
|
||||
|
||||
/** Specifies a gap to leave around the edge of the content component.
|
||||
Each edge of the content component will be indented by the given number of pixels.
|
||||
*/
|
||||
void setIndent (int indentThickness);
|
||||
|
||||
//==============================================================================
|
||||
/** Removes all the tabs from the bar.
|
||||
@see TabbedButtonBar::clearTabs
|
||||
*/
|
||||
void clearTabs();
|
||||
|
||||
/** Adds a tab to the tab-bar.
|
||||
|
||||
The component passed in will be shown for the tab. If deleteComponentWhenNotNeeded
|
||||
is true, then the TabbedComponent will take ownership of the component and will delete
|
||||
it when the tab is removed or when this object is deleted.
|
||||
|
||||
@see TabbedButtonBar::addTab
|
||||
*/
|
||||
void addTab (const String& tabName,
|
||||
Colour tabBackgroundColour,
|
||||
Component* contentComponent,
|
||||
bool deleteComponentWhenNotNeeded,
|
||||
int insertIndex = -1);
|
||||
|
||||
/** Changes the name of one of the tabs. */
|
||||
void setTabName (int tabIndex, const String& newName);
|
||||
|
||||
/** Gets rid of one of the tabs. */
|
||||
void removeTab (int tabIndex);
|
||||
|
||||
/** Moves a tab to a new index in the list.
|
||||
Pass -1 as the index to move it to the end of the list.
|
||||
*/
|
||||
void moveTab (int currentIndex, int newIndex, bool animate = false);
|
||||
|
||||
/** Returns the number of tabs in the bar. */
|
||||
int getNumTabs() const;
|
||||
|
||||
/** Returns a list of all the tab names in the bar. */
|
||||
StringArray getTabNames() const;
|
||||
|
||||
/** Returns the content component that was added for the given index.
|
||||
Be careful not to reposition or delete the components that are returned, as
|
||||
this will interfere with the TabbedComponent's behaviour.
|
||||
*/
|
||||
Component* getTabContentComponent (int tabIndex) const noexcept;
|
||||
|
||||
/** Returns the colour of one of the tabs. */
|
||||
Colour getTabBackgroundColour (int tabIndex) const noexcept;
|
||||
|
||||
/** Changes the background colour of one of the tabs. */
|
||||
void setTabBackgroundColour (int tabIndex, Colour newColour);
|
||||
|
||||
//==============================================================================
|
||||
/** Changes the currently-selected tab.
|
||||
To deselect all the tabs, pass -1 as the index.
|
||||
@see TabbedButtonBar::setCurrentTabIndex
|
||||
*/
|
||||
void setCurrentTabIndex (int newTabIndex, bool sendChangeMessage = true);
|
||||
|
||||
/** Returns the index of the currently selected tab.
|
||||
@see addTab, TabbedButtonBar::getCurrentTabIndex()
|
||||
*/
|
||||
int getCurrentTabIndex() const;
|
||||
|
||||
/** Returns the name of the currently selected tab.
|
||||
@see addTab, TabbedButtonBar::getCurrentTabName()
|
||||
*/
|
||||
String getCurrentTabName() const;
|
||||
|
||||
/** Returns the current component that's filling the panel.
|
||||
This will return nullptr if there isn't one.
|
||||
*/
|
||||
Component* getCurrentContentComponent() const noexcept { return panelComponent.get(); }
|
||||
|
||||
//==============================================================================
|
||||
/** Callback method to indicate the selected tab has been changed.
|
||||
@see setCurrentTabIndex
|
||||
*/
|
||||
virtual void currentTabChanged (int newCurrentTabIndex, const String& newCurrentTabName);
|
||||
|
||||
/** Callback method to indicate that the user has right-clicked on a tab. */
|
||||
virtual void popupMenuClickOnTab (int tabIndex, const String& tabName);
|
||||
|
||||
/** Returns the tab button bar component that is being used. */
|
||||
TabbedButtonBar& getTabbedButtonBar() const noexcept { return *tabs; }
|
||||
|
||||
//==============================================================================
|
||||
/** A set of colour IDs to use to change the colour of various aspects of the component.
|
||||
|
||||
These constants can be used either via the Component::setColour(), or LookAndFeel::setColour()
|
||||
methods.
|
||||
|
||||
@see Component::setColour, Component::findColour, LookAndFeel::setColour, LookAndFeel::findColour
|
||||
*/
|
||||
enum ColourIds
|
||||
{
|
||||
backgroundColourId = 0x1005800, /**< The colour to fill the background behind the tabs. */
|
||||
outlineColourId = 0x1005801, /**< The colour to use to draw an outline around the content.
|
||||
(See setOutline) */
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** @internal */
|
||||
void paint (Graphics&) override;
|
||||
/** @internal */
|
||||
void resized() override;
|
||||
/** @internal */
|
||||
void lookAndFeelChanged() override;
|
||||
|
||||
protected:
|
||||
//==============================================================================
|
||||
/** This creates one of the tab buttons.
|
||||
|
||||
If you need to use custom tab components, you can override this method and
|
||||
return your own class instead of the default.
|
||||
*/
|
||||
virtual TabBarButton* createTabButton (const String& tabName, int tabIndex);
|
||||
|
||||
/** @internal */
|
||||
std::unique_ptr<TabbedButtonBar> tabs;
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
Array<WeakReference<Component>> contentComponents;
|
||||
WeakReference<Component> panelComponent;
|
||||
int tabDepth = 30, outlineThickness = 1, edgeIndent = 0;
|
||||
|
||||
struct ButtonBar;
|
||||
std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override;
|
||||
void changeCallback (int newCurrentTabIndex, const String& newTabName);
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TabbedComponent)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,339 +1,373 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A Viewport is used to contain a larger child component, and allows the child
|
||||
to be automatically scrolled around.
|
||||
|
||||
To use a Viewport, just create one and set the component that goes inside it
|
||||
using the setViewedComponent() method. When the child component changes size,
|
||||
the Viewport will adjust its scrollbars accordingly.
|
||||
|
||||
A subclass of the viewport can be created which will receive calls to its
|
||||
visibleAreaChanged() method when the subcomponent changes position or size.
|
||||
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API Viewport : public Component,
|
||||
private ComponentListener,
|
||||
private ScrollBar::Listener
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates a Viewport.
|
||||
|
||||
The viewport is initially empty - use the setViewedComponent() method to
|
||||
add a child component for it to manage.
|
||||
*/
|
||||
explicit Viewport (const String& componentName = String());
|
||||
|
||||
/** Destructor. */
|
||||
~Viewport() override;
|
||||
|
||||
//==============================================================================
|
||||
/** Sets the component that this viewport will contain and scroll around.
|
||||
|
||||
This will add the given component to this Viewport and position it at (0, 0).
|
||||
|
||||
(Don't add or remove any child components directly using the normal
|
||||
Component::addChildComponent() methods).
|
||||
|
||||
@param newViewedComponent the component to add to this viewport, or null to remove
|
||||
the current component.
|
||||
@param deleteComponentWhenNoLongerNeeded if true, the component will be deleted
|
||||
automatically when the viewport is deleted or when a different
|
||||
component is added. If false, the caller must manage the lifetime
|
||||
of the component
|
||||
@see getViewedComponent
|
||||
*/
|
||||
void setViewedComponent (Component* newViewedComponent,
|
||||
bool deleteComponentWhenNoLongerNeeded = true);
|
||||
|
||||
/** Returns the component that's currently being used inside the Viewport.
|
||||
|
||||
@see setViewedComponent
|
||||
*/
|
||||
Component* getViewedComponent() const noexcept { return contentComp.get(); }
|
||||
|
||||
//==============================================================================
|
||||
/** Changes the position of the viewed component.
|
||||
|
||||
The inner component will be moved so that the pixel at the top left of
|
||||
the viewport will be the pixel at position (xPixelsOffset, yPixelsOffset)
|
||||
within the inner component.
|
||||
|
||||
This will update the scrollbars and might cause a call to visibleAreaChanged().
|
||||
|
||||
@see getViewPositionX, getViewPositionY, setViewPositionProportionately
|
||||
*/
|
||||
void setViewPosition (int xPixelsOffset, int yPixelsOffset);
|
||||
|
||||
/** Changes the position of the viewed component.
|
||||
|
||||
The inner component will be moved so that the pixel at the top left of
|
||||
the viewport will be the pixel at the specified coordinates within the
|
||||
inner component.
|
||||
|
||||
This will update the scrollbars and might cause a call to visibleAreaChanged().
|
||||
|
||||
@see getViewPositionX, getViewPositionY, setViewPositionProportionately
|
||||
*/
|
||||
void setViewPosition (Point<int> newPosition);
|
||||
|
||||
/** Changes the view position as a proportion of the distance it can move.
|
||||
|
||||
The values here are from 0.0 to 1.0 - where (0, 0) would put the
|
||||
visible area in the top-left, and (1, 1) would put it as far down and
|
||||
to the right as it's possible to go whilst keeping the child component
|
||||
on-screen.
|
||||
*/
|
||||
void setViewPositionProportionately (double proportionX, double proportionY);
|
||||
|
||||
/** If the specified position is at the edges of the viewport, this method scrolls
|
||||
the viewport to bring that position nearer to the centre.
|
||||
|
||||
Call this if you're dragging an object inside a viewport and want to make it scroll
|
||||
when the user approaches an edge. You might also find Component::beginDragAutoRepeat()
|
||||
useful when auto-scrolling.
|
||||
|
||||
@param mouseX the x position, relative to the Viewport's top-left
|
||||
@param mouseY the y position, relative to the Viewport's top-left
|
||||
@param distanceFromEdge specifies how close to an edge the position needs to be
|
||||
before the viewport should scroll in that direction
|
||||
@param maximumSpeed the maximum number of pixels that the viewport is allowed
|
||||
to scroll by.
|
||||
@returns true if the viewport was scrolled
|
||||
*/
|
||||
bool autoScroll (int mouseX, int mouseY, int distanceFromEdge, int maximumSpeed);
|
||||
|
||||
/** Returns the position within the child component of the top-left of its visible area. */
|
||||
Point<int> getViewPosition() const noexcept { return lastVisibleArea.getPosition(); }
|
||||
|
||||
/** Returns the visible area of the child component, relative to its top-left */
|
||||
Rectangle<int> getViewArea() const noexcept { return lastVisibleArea; }
|
||||
|
||||
/** Returns the position within the child component of the top-left of its visible area.
|
||||
@see getViewWidth, setViewPosition
|
||||
*/
|
||||
int getViewPositionX() const noexcept { return lastVisibleArea.getX(); }
|
||||
|
||||
/** Returns the position within the child component of the top-left of its visible area.
|
||||
@see getViewHeight, setViewPosition
|
||||
*/
|
||||
int getViewPositionY() const noexcept { return lastVisibleArea.getY(); }
|
||||
|
||||
/** Returns the width of the visible area of the child component.
|
||||
|
||||
This may be less than the width of this Viewport if there's a vertical scrollbar
|
||||
or if the child component is itself smaller.
|
||||
*/
|
||||
int getViewWidth() const noexcept { return lastVisibleArea.getWidth(); }
|
||||
|
||||
/** Returns the height of the visible area of the child component.
|
||||
|
||||
This may be less than the height of this Viewport if there's a horizontal scrollbar
|
||||
or if the child component is itself smaller.
|
||||
*/
|
||||
int getViewHeight() const noexcept { return lastVisibleArea.getHeight(); }
|
||||
|
||||
/** Returns the width available within this component for the contents.
|
||||
|
||||
This will be the width of the viewport component minus the width of a
|
||||
vertical scrollbar (if visible).
|
||||
*/
|
||||
int getMaximumVisibleWidth() const;
|
||||
|
||||
/** Returns the height available within this component for the contents.
|
||||
|
||||
This will be the height of the viewport component minus the space taken up
|
||||
by a horizontal scrollbar (if visible).
|
||||
*/
|
||||
int getMaximumVisibleHeight() const;
|
||||
|
||||
//==============================================================================
|
||||
/** Callback method that is called when the visible area changes.
|
||||
|
||||
This will be called when the visible area is moved either be scrolling or
|
||||
by calls to setViewPosition(), etc.
|
||||
*/
|
||||
virtual void visibleAreaChanged (const Rectangle<int>& newVisibleArea);
|
||||
|
||||
/** Callback method that is called when the viewed component is added, removed or swapped. */
|
||||
virtual void viewedComponentChanged (Component* newComponent);
|
||||
|
||||
//==============================================================================
|
||||
/** Turns scrollbars on or off.
|
||||
|
||||
If set to false, the scrollbars won't ever appear. When true (the default)
|
||||
they will appear only when needed.
|
||||
|
||||
The allowVerticalScrollingWithoutScrollbar parameters allow you to enable
|
||||
mouse-wheel scrolling even when there the scrollbars are hidden. When the
|
||||
scrollbars are visible, these parameters are ignored.
|
||||
*/
|
||||
void setScrollBarsShown (bool showVerticalScrollbarIfNeeded,
|
||||
bool showHorizontalScrollbarIfNeeded,
|
||||
bool allowVerticalScrollingWithoutScrollbar = false,
|
||||
bool allowHorizontalScrollingWithoutScrollbar = false);
|
||||
|
||||
/** Changes where the scroll bars are positioned
|
||||
|
||||
If verticalScrollbarOnRight is set to true, then the vertical scrollbar will
|
||||
appear on the right side of the view port's content (this is the default),
|
||||
otherwise it will be on the left side of the content.
|
||||
|
||||
If horizontalScrollbarAtBottom is set to true, then the horizontal scrollbar
|
||||
will appear at the bottom of the view port's content (this is the default),
|
||||
otherwise it will be at the top.
|
||||
*/
|
||||
void setScrollBarPosition (bool verticalScrollbarOnRight,
|
||||
bool horizontalScrollbarAtBottom);
|
||||
|
||||
/** True if the vertical scrollbar will appear on the right side of the content */
|
||||
bool isVerticalScrollbarOnTheRight() const noexcept { return vScrollbarRight; }
|
||||
|
||||
/** True if the horizontal scrollbar will appear at the bottom of the content */
|
||||
bool isHorizontalScrollbarAtBottom() const noexcept { return hScrollbarBottom; }
|
||||
|
||||
/** True if the vertical scrollbar is enabled.
|
||||
@see setScrollBarsShown
|
||||
*/
|
||||
bool isVerticalScrollBarShown() const noexcept { return showVScrollbar; }
|
||||
|
||||
/** True if the horizontal scrollbar is enabled.
|
||||
@see setScrollBarsShown
|
||||
*/
|
||||
bool isHorizontalScrollBarShown() const noexcept { return showHScrollbar; }
|
||||
|
||||
/** Changes the width of the scrollbars.
|
||||
If this isn't specified, the default width from the LookAndFeel class will be used.
|
||||
@see LookAndFeel::getDefaultScrollbarWidth
|
||||
*/
|
||||
void setScrollBarThickness (int thickness);
|
||||
|
||||
/** Returns the thickness of the scrollbars.
|
||||
@see setScrollBarThickness
|
||||
*/
|
||||
int getScrollBarThickness() const;
|
||||
|
||||
/** Changes the distance that a single-step click on a scrollbar button
|
||||
will move the viewport.
|
||||
*/
|
||||
void setSingleStepSizes (int stepX, int stepY);
|
||||
|
||||
/** Returns a reference to the scrollbar component being used.
|
||||
Handy if you need to customise the bar somehow.
|
||||
*/
|
||||
ScrollBar& getVerticalScrollBar() noexcept { return *verticalScrollBar; }
|
||||
|
||||
/** Returns a reference to the scrollbar component being used.
|
||||
Handy if you need to customise the bar somehow.
|
||||
*/
|
||||
ScrollBar& getHorizontalScrollBar() noexcept { return *horizontalScrollBar; }
|
||||
|
||||
/** Re-instantiates the scrollbars, which is only really useful if you've overridden createScrollBarComponent(). */
|
||||
void recreateScrollbars();
|
||||
|
||||
/** True if there's any off-screen content that could be scrolled vertically,
|
||||
or false if everything is currently visible.
|
||||
*/
|
||||
bool canScrollVertically() const noexcept;
|
||||
|
||||
/** True if there's any off-screen content that could be scrolled horizontally,
|
||||
or false if everything is currently visible.
|
||||
*/
|
||||
bool canScrollHorizontally() const noexcept;
|
||||
|
||||
/** Enables or disables drag-to-scroll functionality in the viewport.
|
||||
|
||||
If your viewport contains a Component that you don't want to receive mouse events when the
|
||||
user is drag-scrolling, you can disable this with the Component::setViewportIgnoreDragFlag()
|
||||
method.
|
||||
*/
|
||||
void setScrollOnDragEnabled (bool shouldScrollOnDrag);
|
||||
|
||||
/** Returns true if drag-to-scroll functionality is enabled. */
|
||||
bool isScrollOnDragEnabled() const noexcept;
|
||||
|
||||
/** Returns true if the user is currently dragging-to-scroll.
|
||||
@see setScrollOnDragEnabled
|
||||
*/
|
||||
bool isCurrentlyScrollingOnDrag() const noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** @internal */
|
||||
void resized() override;
|
||||
/** @internal */
|
||||
void scrollBarMoved (ScrollBar*, double newRangeStart) override;
|
||||
/** @internal */
|
||||
void mouseWheelMove (const MouseEvent&, const MouseWheelDetails&) override;
|
||||
/** @internal */
|
||||
bool keyPressed (const KeyPress&) override;
|
||||
/** @internal */
|
||||
void componentMovedOrResized (Component&, bool wasMoved, bool wasResized) override;
|
||||
/** @internal */
|
||||
void lookAndFeelChanged() override;
|
||||
/** @internal */
|
||||
bool useMouseWheelMoveIfNeeded (const MouseEvent&, const MouseWheelDetails&);
|
||||
/** @internal */
|
||||
static bool respondsToKey (const KeyPress&);
|
||||
|
||||
protected:
|
||||
//==============================================================================
|
||||
/** Creates the Scrollbar components that will be added to the Viewport.
|
||||
Subclasses can override this if they need to customise the scrollbars in some way.
|
||||
*/
|
||||
virtual ScrollBar* createScrollBarComponent (bool isVertical);
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
std::unique_ptr<ScrollBar> verticalScrollBar, horizontalScrollBar;
|
||||
Component contentHolder;
|
||||
WeakReference<Component> contentComp;
|
||||
Rectangle<int> lastVisibleArea;
|
||||
int scrollBarThickness = 0;
|
||||
int singleStepX = 16, singleStepY = 16;
|
||||
bool showHScrollbar = true, showVScrollbar = true, deleteContent = true;
|
||||
bool customScrollBarThickness = false;
|
||||
bool allowScrollingWithoutScrollbarV = false, allowScrollingWithoutScrollbarH = false;
|
||||
bool vScrollbarRight = true, hScrollbarBottom = true;
|
||||
|
||||
struct DragToScrollListener;
|
||||
std::unique_ptr<DragToScrollListener> dragToScrollListener;
|
||||
|
||||
Point<int> viewportPosToCompPos (Point<int>) const;
|
||||
|
||||
void updateVisibleArea();
|
||||
void deleteOrRemoveContentComp();
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Viewport)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A Viewport is used to contain a larger child component, and allows the child
|
||||
to be automatically scrolled around.
|
||||
|
||||
To use a Viewport, just create one and set the component that goes inside it
|
||||
using the setViewedComponent() method. When the child component changes size,
|
||||
the Viewport will adjust its scrollbars accordingly.
|
||||
|
||||
A subclass of the viewport can be created which will receive calls to its
|
||||
visibleAreaChanged() method when the subcomponent changes position or size.
|
||||
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API Viewport : public Component,
|
||||
private ComponentListener,
|
||||
private ScrollBar::Listener
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates a Viewport.
|
||||
|
||||
The viewport is initially empty - use the setViewedComponent() method to
|
||||
add a child component for it to manage.
|
||||
*/
|
||||
explicit Viewport (const String& componentName = String());
|
||||
|
||||
/** Destructor. */
|
||||
~Viewport() override;
|
||||
|
||||
//==============================================================================
|
||||
/** Sets the component that this viewport will contain and scroll around.
|
||||
|
||||
This will add the given component to this Viewport and position it at (0, 0).
|
||||
|
||||
(Don't add or remove any child components directly using the normal
|
||||
Component::addChildComponent() methods).
|
||||
|
||||
@param newViewedComponent the component to add to this viewport, or null to remove
|
||||
the current component.
|
||||
@param deleteComponentWhenNoLongerNeeded if true, the component will be deleted
|
||||
automatically when the viewport is deleted or when a different
|
||||
component is added. If false, the caller must manage the lifetime
|
||||
of the component
|
||||
@see getViewedComponent
|
||||
*/
|
||||
void setViewedComponent (Component* newViewedComponent,
|
||||
bool deleteComponentWhenNoLongerNeeded = true);
|
||||
|
||||
/** Returns the component that's currently being used inside the Viewport.
|
||||
|
||||
@see setViewedComponent
|
||||
*/
|
||||
Component* getViewedComponent() const noexcept { return contentComp.get(); }
|
||||
|
||||
//==============================================================================
|
||||
/** Changes the position of the viewed component.
|
||||
|
||||
The inner component will be moved so that the pixel at the top left of
|
||||
the viewport will be the pixel at position (xPixelsOffset, yPixelsOffset)
|
||||
within the inner component.
|
||||
|
||||
This will update the scrollbars and might cause a call to visibleAreaChanged().
|
||||
|
||||
@see getViewPositionX, getViewPositionY, setViewPositionProportionately
|
||||
*/
|
||||
void setViewPosition (int xPixelsOffset, int yPixelsOffset);
|
||||
|
||||
/** Changes the position of the viewed component.
|
||||
|
||||
The inner component will be moved so that the pixel at the top left of
|
||||
the viewport will be the pixel at the specified coordinates within the
|
||||
inner component.
|
||||
|
||||
This will update the scrollbars and might cause a call to visibleAreaChanged().
|
||||
|
||||
@see getViewPositionX, getViewPositionY, setViewPositionProportionately
|
||||
*/
|
||||
void setViewPosition (Point<int> newPosition);
|
||||
|
||||
/** Changes the view position as a proportion of the distance it can move.
|
||||
|
||||
The values here are from 0.0 to 1.0 - where (0, 0) would put the
|
||||
visible area in the top-left, and (1, 1) would put it as far down and
|
||||
to the right as it's possible to go whilst keeping the child component
|
||||
on-screen.
|
||||
*/
|
||||
void setViewPositionProportionately (double proportionX, double proportionY);
|
||||
|
||||
/** If the specified position is at the edges of the viewport, this method scrolls
|
||||
the viewport to bring that position nearer to the centre.
|
||||
|
||||
Call this if you're dragging an object inside a viewport and want to make it scroll
|
||||
when the user approaches an edge. You might also find Component::beginDragAutoRepeat()
|
||||
useful when auto-scrolling.
|
||||
|
||||
@param mouseX the x position, relative to the Viewport's top-left
|
||||
@param mouseY the y position, relative to the Viewport's top-left
|
||||
@param distanceFromEdge specifies how close to an edge the position needs to be
|
||||
before the viewport should scroll in that direction
|
||||
@param maximumSpeed the maximum number of pixels that the viewport is allowed
|
||||
to scroll by.
|
||||
@returns true if the viewport was scrolled
|
||||
*/
|
||||
bool autoScroll (int mouseX, int mouseY, int distanceFromEdge, int maximumSpeed);
|
||||
|
||||
/** Returns the position within the child component of the top-left of its visible area. */
|
||||
Point<int> getViewPosition() const noexcept { return lastVisibleArea.getPosition(); }
|
||||
|
||||
/** Returns the visible area of the child component, relative to its top-left */
|
||||
Rectangle<int> getViewArea() const noexcept { return lastVisibleArea; }
|
||||
|
||||
/** Returns the position within the child component of the top-left of its visible area.
|
||||
@see getViewWidth, setViewPosition
|
||||
*/
|
||||
int getViewPositionX() const noexcept { return lastVisibleArea.getX(); }
|
||||
|
||||
/** Returns the position within the child component of the top-left of its visible area.
|
||||
@see getViewHeight, setViewPosition
|
||||
*/
|
||||
int getViewPositionY() const noexcept { return lastVisibleArea.getY(); }
|
||||
|
||||
/** Returns the width of the visible area of the child component.
|
||||
|
||||
This may be less than the width of this Viewport if there's a vertical scrollbar
|
||||
or if the child component is itself smaller.
|
||||
*/
|
||||
int getViewWidth() const noexcept { return lastVisibleArea.getWidth(); }
|
||||
|
||||
/** Returns the height of the visible area of the child component.
|
||||
|
||||
This may be less than the height of this Viewport if there's a horizontal scrollbar
|
||||
or if the child component is itself smaller.
|
||||
*/
|
||||
int getViewHeight() const noexcept { return lastVisibleArea.getHeight(); }
|
||||
|
||||
/** Returns the width available within this component for the contents.
|
||||
|
||||
This will be the width of the viewport component minus the width of a
|
||||
vertical scrollbar (if visible).
|
||||
*/
|
||||
int getMaximumVisibleWidth() const;
|
||||
|
||||
/** Returns the height available within this component for the contents.
|
||||
|
||||
This will be the height of the viewport component minus the space taken up
|
||||
by a horizontal scrollbar (if visible).
|
||||
*/
|
||||
int getMaximumVisibleHeight() const;
|
||||
|
||||
//==============================================================================
|
||||
/** Callback method that is called when the visible area changes.
|
||||
|
||||
This will be called when the visible area is moved either be scrolling or
|
||||
by calls to setViewPosition(), etc.
|
||||
*/
|
||||
virtual void visibleAreaChanged (const Rectangle<int>& newVisibleArea);
|
||||
|
||||
/** Callback method that is called when the viewed component is added, removed or swapped. */
|
||||
virtual void viewedComponentChanged (Component* newComponent);
|
||||
|
||||
//==============================================================================
|
||||
/** Turns scrollbars on or off.
|
||||
|
||||
If set to false, the scrollbars won't ever appear. When true (the default)
|
||||
they will appear only when needed.
|
||||
|
||||
The allowVerticalScrollingWithoutScrollbar parameters allow you to enable
|
||||
mouse-wheel scrolling even when there the scrollbars are hidden. When the
|
||||
scrollbars are visible, these parameters are ignored.
|
||||
*/
|
||||
void setScrollBarsShown (bool showVerticalScrollbarIfNeeded,
|
||||
bool showHorizontalScrollbarIfNeeded,
|
||||
bool allowVerticalScrollingWithoutScrollbar = false,
|
||||
bool allowHorizontalScrollingWithoutScrollbar = false);
|
||||
|
||||
/** Changes where the scroll bars are positioned
|
||||
|
||||
If verticalScrollbarOnRight is set to true, then the vertical scrollbar will
|
||||
appear on the right side of the view port's content (this is the default),
|
||||
otherwise it will be on the left side of the content.
|
||||
|
||||
If horizontalScrollbarAtBottom is set to true, then the horizontal scrollbar
|
||||
will appear at the bottom of the view port's content (this is the default),
|
||||
otherwise it will be at the top.
|
||||
*/
|
||||
void setScrollBarPosition (bool verticalScrollbarOnRight,
|
||||
bool horizontalScrollbarAtBottom);
|
||||
|
||||
/** True if the vertical scrollbar will appear on the right side of the content */
|
||||
bool isVerticalScrollbarOnTheRight() const noexcept { return vScrollbarRight; }
|
||||
|
||||
/** True if the horizontal scrollbar will appear at the bottom of the content */
|
||||
bool isHorizontalScrollbarAtBottom() const noexcept { return hScrollbarBottom; }
|
||||
|
||||
/** True if the vertical scrollbar is enabled.
|
||||
@see setScrollBarsShown
|
||||
*/
|
||||
bool isVerticalScrollBarShown() const noexcept { return showVScrollbar; }
|
||||
|
||||
/** True if the horizontal scrollbar is enabled.
|
||||
@see setScrollBarsShown
|
||||
*/
|
||||
bool isHorizontalScrollBarShown() const noexcept { return showHScrollbar; }
|
||||
|
||||
/** Changes the width of the scrollbars.
|
||||
If this isn't specified, the default width from the LookAndFeel class will be used.
|
||||
@see LookAndFeel::getDefaultScrollbarWidth
|
||||
*/
|
||||
void setScrollBarThickness (int thickness);
|
||||
|
||||
/** Returns the thickness of the scrollbars.
|
||||
@see setScrollBarThickness
|
||||
*/
|
||||
int getScrollBarThickness() const;
|
||||
|
||||
/** Changes the distance that a single-step click on a scrollbar button
|
||||
will move the viewport.
|
||||
*/
|
||||
void setSingleStepSizes (int stepX, int stepY);
|
||||
|
||||
/** Returns a reference to the scrollbar component being used.
|
||||
Handy if you need to customise the bar somehow.
|
||||
*/
|
||||
ScrollBar& getVerticalScrollBar() noexcept { return *verticalScrollBar; }
|
||||
|
||||
/** Returns a reference to the scrollbar component being used.
|
||||
Handy if you need to customise the bar somehow.
|
||||
*/
|
||||
ScrollBar& getHorizontalScrollBar() noexcept { return *horizontalScrollBar; }
|
||||
|
||||
/** Re-instantiates the scrollbars, which is only really useful if you've overridden createScrollBarComponent(). */
|
||||
void recreateScrollbars();
|
||||
|
||||
/** True if there's any off-screen content that could be scrolled vertically,
|
||||
or false if everything is currently visible.
|
||||
*/
|
||||
bool canScrollVertically() const noexcept;
|
||||
|
||||
/** True if there's any off-screen content that could be scrolled horizontally,
|
||||
or false if everything is currently visible.
|
||||
*/
|
||||
bool canScrollHorizontally() const noexcept;
|
||||
|
||||
/** Enables or disables drag-to-scroll functionality for mouse sources in the viewport.
|
||||
|
||||
If your viewport contains a Component that you don't want to receive mouse events when the
|
||||
user is drag-scrolling, you can disable this with the Component::setViewportIgnoreDragFlag()
|
||||
method.
|
||||
*/
|
||||
[[deprecated ("Use setScrollOnDragMode instead.")]]
|
||||
void setScrollOnDragEnabled (bool shouldScrollOnDrag)
|
||||
{
|
||||
setScrollOnDragMode (shouldScrollOnDrag ? ScrollOnDragMode::all : ScrollOnDragMode::never);
|
||||
}
|
||||
|
||||
/** Returns true if drag-to-scroll functionality is enabled for mouse input sources. */
|
||||
[[deprecated ("Use getScrollOnDragMode instead.")]]
|
||||
bool isScrollOnDragEnabled() const noexcept { return getScrollOnDragMode() == ScrollOnDragMode::all; }
|
||||
|
||||
enum class ScrollOnDragMode
|
||||
{
|
||||
never, /**< Dragging will never scroll the viewport. */
|
||||
nonHover, /**< Dragging will only scroll the viewport if the input source cannot hover. */
|
||||
all /**< Dragging will always scroll the viewport. */
|
||||
};
|
||||
|
||||
/** Sets the current scroll-on-drag mode. The default is ScrollOnDragMode::nonHover.
|
||||
|
||||
If your viewport contains a Component that you don't want to receive mouse events when the
|
||||
user is drag-scrolling, you can disable this with the Component::setViewportIgnoreDragFlag()
|
||||
method.
|
||||
*/
|
||||
void setScrollOnDragMode (ScrollOnDragMode scrollOnDragMode);
|
||||
|
||||
/** Returns the current scroll-on-drag mode. */
|
||||
ScrollOnDragMode getScrollOnDragMode() const { return scrollOnDragMode; }
|
||||
|
||||
/** Returns true if the user is currently dragging-to-scroll.
|
||||
@see setScrollOnDragEnabled
|
||||
*/
|
||||
bool isCurrentlyScrollingOnDrag() const noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** @internal */
|
||||
void resized() override;
|
||||
/** @internal */
|
||||
void scrollBarMoved (ScrollBar*, double newRangeStart) override;
|
||||
/** @internal */
|
||||
void mouseWheelMove (const MouseEvent&, const MouseWheelDetails&) override;
|
||||
/** @internal */
|
||||
void mouseDown (const MouseEvent& e) override;
|
||||
/** @internal */
|
||||
bool keyPressed (const KeyPress&) override;
|
||||
/** @internal */
|
||||
void componentMovedOrResized (Component&, bool wasMoved, bool wasResized) override;
|
||||
/** @internal */
|
||||
void lookAndFeelChanged() override;
|
||||
/** @internal */
|
||||
bool useMouseWheelMoveIfNeeded (const MouseEvent&, const MouseWheelDetails&);
|
||||
/** @internal */
|
||||
static bool respondsToKey (const KeyPress&);
|
||||
|
||||
protected:
|
||||
//==============================================================================
|
||||
/** Creates the Scrollbar components that will be added to the Viewport.
|
||||
Subclasses can override this if they need to customise the scrollbars in some way.
|
||||
*/
|
||||
virtual ScrollBar* createScrollBarComponent (bool isVertical);
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
class AccessibilityIgnoredComponent : public Component
|
||||
{
|
||||
std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override
|
||||
{
|
||||
return createIgnoredAccessibilityHandler (*this);
|
||||
}
|
||||
};
|
||||
|
||||
std::unique_ptr<ScrollBar> verticalScrollBar, horizontalScrollBar;
|
||||
AccessibilityIgnoredComponent contentHolder;
|
||||
WeakReference<Component> contentComp;
|
||||
Rectangle<int> lastVisibleArea;
|
||||
int scrollBarThickness = 0;
|
||||
int singleStepX = 16, singleStepY = 16;
|
||||
ScrollOnDragMode scrollOnDragMode = ScrollOnDragMode::nonHover;
|
||||
bool showHScrollbar = true, showVScrollbar = true, deleteContent = true;
|
||||
bool customScrollBarThickness = false;
|
||||
bool allowScrollingWithoutScrollbarV = false, allowScrollingWithoutScrollbarH = false;
|
||||
bool vScrollbarRight = true, hScrollbarBottom = true;
|
||||
|
||||
struct DragToScrollListener;
|
||||
std::unique_ptr<DragToScrollListener> dragToScrollListener;
|
||||
|
||||
Point<int> viewportPosToCompPos (Point<int>) const;
|
||||
|
||||
void updateVisibleArea();
|
||||
void deleteOrRemoveContentComp();
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Viewport)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
|
Reference in New Issue
Block a user