migrating to the latest JUCE version

This commit is contained in:
2022-11-04 23:11:33 +01:00
committed by Nikolai Rodionov
parent 4257a0f8ba
commit faf8f18333
2796 changed files with 888518 additions and 784244 deletions

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

File diff suppressed because it is too large Load Diff

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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