Here’s a nifty class that I’ve carried with me through the years, modified to work with Juce. It will allow you to place anchors on your child Components so that when the parent is resized, your controls will automatically stretch, move, flow, whatever! The header file has some comments showing a usage example. There’s a compilable sample program at the bottom.
ResizableLayout.h
// Copyright (C) 2008-2011 by One Guy Group, Inc., All rights reserved worldwide.
/*******************************************************************************
Resizable Layout for Juce
By Vincent Falco
Juce:
http://www.rawmaterialsoftware
--------------------------------------------------------------------------------
License: MIT License (http://www.opensource.org/licenses/mit-license.php)
Copyright (c) 2011 by Vincent Falco
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*******************************************************************************/
#ifndef RESIZABLELAYOUT_H
#define RESIZABLELAYOUT_H
#include "juce.h"
//==============================================================================
/**
Smoothly repositions and resizes child Components without rounding errors,
according to caller provided stretching parameters.
Usage:
1) Derive your Component from ResizableLayout
2) Give your Component a well defined default size
3) Add child Components and position them according to how
they should appear at the default.
4) For each child Component, also call addToLayout() and specify what
portion of the owner's growth or reduction should apply to each corner.
5) At the end of your constructor, call activateLayout() to turn it on
6) If you later manually reposition a control (for example, using a
ComponentDragger) call updateLayoutFor() on the control that moved,
or if you moved all the controls call updateLayout().
7) You do not need to override resized() to make this work, all you
have to do is lay things out when you create your child Components
and add anchors as appropriate.
Example
struct ContentComponent : Component, ResizableLayout
{
ContentComponent() : ResizableLayout (this)
{
// for layouts to work you must start at some size
// place controls in a location that is initially correct.
setSize(512, 384);
TextButton* b;
b = new TextButton ("Juce");
b->setBounds (32, 32, 512-64, 32);
// stretch to fit the width of the window
addToLayout (b, anchorTopLeft, anchorTopRight);
addAndMakeVisible (b);
b = new TextButton ("One");
b->setBounds (32, 96, 128, 32);
// take up 1/3 of the width of the window
addToLayout (b, Point<int>(0,0), Point<int>(33,0));
addAndMakeVisible (b);
// turn it on
activateLayout();
}
}
*/
class ResizableLayout
: public ComponentBoundsConstrainer
, private ComponentListener
{
public:
enum
{
anchorUnit=100
};
enum Style
{
styleStretch,
styleFixedAspect
};
static const Point<int> anchorNone;
static const Point<int> anchorTopLeft;
static const Point<int> anchorTopCenter;
static const Point<int> anchorTopRight;
static const Point<int> anchorMidLeft;
static const Point<int> anchorMidCenter;
static const Point<int> anchorMidRight;
static const Point<int> anchorBottomLeft;
static const Point<int> anchorBottomCenter;
static const Point<int> anchorBottomRight;
public:
ResizableLayout (Component* owner);
~ResizableLayout ();
// Add a Component to the Layout.
// topLeft and bottomRight are the percentages that the top left and bottom right of
// the Component should move by, when the layout is resized.
// So if you wanted to have the control take on the full width of the parent, and
// half the height, you would use bottomRight.x=100, bottomRight.y=50. or
// use the constant anchorMidRight
void addToLayout (
Component *component,
const Point<int> &topLeft,
const Point<int> &bottomRight=anchorNone,
Style style = styleStretch );
// Remove a Component from the Layout.
void removeFromLayout (Component* component);
// Activate (or deactivate) the Layout. The Layout is initially inactive,
// to prevent spurious recalculation while a Component and its children are being
// constructed (causing resized() messages). Activating the Layout for the
// first time will cause an Update().
void activateLayout (bool bActive=true);
// Update the state information for all items. This is used on the first Activate(),
// and can also be used if multiple controls are moved or resized from elsewhere.
void updateLayout ();
// Call this to manually update the state information for a single control
// after it has been moved or resized from elsewhere.
void updateLayoutFor (Component *component);
// Convenience function
static Rectangle<int> calcBoundsOfChildren (Component* parent);
private:
struct Rect
{
Rect() {}
Rect( int top0, int left0, int bottom0, int right0 ) { top=top0; left=left0; bottom=bottom0; right=right0; }
Rect( const Rectangle<int> &r ) { top=int(r.getY()); left=int(r.getX()); bottom=int(r.getBottom()); right=int(r.getRight()); }
operator Rectangle<int>() const { return Rectangle<int>( left, top, Width(), Height() ); }
int Height( void ) const { return bottom-top; }
int Width( void ) const { return right-left; }
void Inset( int dx, int dy ) { top+=dy; left+=dx; bottom-=dy; right-=dx; }
int top;
int left;
int bottom;
int right;
};
struct Anchor
{
Style style;
Component* component;
Point<int> topLeft;
Point<int> bottomRight;
Anchor (Component* component=0);
bool operator== (const Anchor& rhs) const;
bool operator>= (const Anchor& rhs) const;
};
struct State
{
Component* component;
double aspect;
Rect margin;
State (Component* component=0);
bool operator== (const State& rhs) const;
bool operator>= (const State& rhs) const;
};
void addStateFor (const Anchor& anchor);
void recalculateLayout ();
void componentMovedOrResized (Component& component,
bool wasMoved,
bool wasResized);
void componentBeingDeleted (Component& component);
private:
Component* m_owner;
SortedSet<Anchor> m_anchors;
SortedSet<State> m_states;
bool m_bFirstTime;
bool m_bActive;
};
#endif
ResizableLayout.cpp
// Copyright (C) 2008-2011 by One Guy Group, Inc., All rights reserved worldwide.
/*******************************************************************************
Resizable Layout for Juce
By Vincent Falco
Juce:
http://www.rawmaterialsoftware
--------------------------------------------------------------------------------
License: MIT License (http://www.opensource.org/licenses/mit-license.php)
Copyright (c) 2011 by Vincent Falco
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*******************************************************************************/
#include "ResizableLayout.h"
const Point<int> ResizableLayout::anchorNone ( -1, -1 );
const Point<int> ResizableLayout::anchorTopLeft ( 0, 0 );
const Point<int> ResizableLayout::anchorTopCenter ( anchorUnit/2, 0 );
const Point<int> ResizableLayout::anchorTopRight ( anchorUnit, 0 );
const Point<int> ResizableLayout::anchorMidLeft ( 0, anchorUnit/2 );
const Point<int> ResizableLayout::anchorMidCenter ( anchorUnit/2, anchorUnit/2 );
const Point<int> ResizableLayout::anchorMidRight ( anchorUnit, anchorUnit/2 );
const Point<int> ResizableLayout::anchorBottomLeft ( 0, anchorUnit );
const Point<int> ResizableLayout::anchorBottomCenter( anchorUnit/2, anchorUnit );
const Point<int> ResizableLayout::anchorBottomRight ( anchorUnit, anchorUnit );
ResizableLayout::Anchor::Anchor (Component* component_)
: component (component_)
{
jassert (component);
}
bool ResizableLayout::Anchor::operator== (const Anchor& rhs) const
{ return component == rhs.component; }
bool ResizableLayout::Anchor::operator>= (const Anchor& rhs) const
{ return component >= rhs.component; }
ResizableLayout::State::State (Component* component_)
: component (component_)
{
jassert (component);
}
bool ResizableLayout::State::operator== (const State& rhs) const
{ return component == rhs.component; }
bool ResizableLayout::State::operator>= (const State& rhs) const
{ return component >= rhs.component; }
//----
ResizableLayout::ResizableLayout (Component* owner)
: m_owner (owner)
{
m_bFirstTime = true;
m_bActive = false;
m_owner->addComponentListener (this);
}
ResizableLayout::~ResizableLayout()
{
}
void ResizableLayout::addToLayout (Component* component,
const Point<int> &topLeft,
const Point<int> &bottomRight,
Style style )
{
jassert (topLeft!=anchorNone);
Anchor anchor (component);
anchor.style = style;
anchor.topLeft = topLeft;
anchor.bottomRight = bottomRight;
m_anchors.add (anchor);
component->addComponentListener (this);
}
void ResizableLayout::removeFromLayout (Component* component)
{
m_anchors.removeValue (component);
m_states.removeValue (component);
}
void ResizableLayout::activateLayout( bool bActive )
{
if( m_bActive!=bActive )
{
if( bActive && m_bFirstTime )
{
updateLayout();
m_bFirstTime=false;
}
m_bActive=bActive;
}
}
void ResizableLayout::updateLayout ()
{
m_states.clearQuick();
for( int i=0; i<m_anchors.size(); i++ )
addStateFor (m_anchors[i]);
}
void ResizableLayout::updateLayoutFor (Component *component)
{
m_states.removeValue (component);
addStateFor (m_anchors[m_anchors.indexOf (Anchor(component))]);
}
void ResizableLayout::addStateFor (const Anchor& anchor)
{
Rect rBounds = anchor.component->getBounds();
// owner size
Point<int> ptSize( int(m_owner->getWidth()), int(m_owner->getHeight()) );
State state (anchor.component);
// secret sauce
state.margin.top = rBounds.top - (ptSize.getY() * anchor.topLeft.getY()) / anchorUnit;
state.margin.left = rBounds.left - (ptSize.getX() * anchor.topLeft.getX()) / anchorUnit;
state.margin.bottom = rBounds.bottom - (ptSize.getY() * anchor.bottomRight.getY()) / anchorUnit;
state.margin.right = rBounds.right - (ptSize.getX() * anchor.bottomRight.getX()) / anchorUnit;
state.aspect = double (rBounds.Width()) / rBounds.Height();
m_states.add (state);
}
// Recalculate the position and size of all the controls
// in the layout, based on the owner Component size.
void ResizableLayout::recalculateLayout()
{
if( m_bActive )
{
Rect rParent = m_owner->getBounds();
for( int i=0; i<m_states.size(); i++ )
{
Anchor anchor = m_anchors[i];
State state = m_states[i];
jassert (anchor.component == state.component);
Rect rBounds;
// secret sauce
rBounds.top = state.margin.top + (rParent.Height() * anchor.topLeft.getY()) / anchorUnit;
rBounds.left = state.margin.left + (rParent.Width() * anchor.topLeft.getX()) / anchorUnit;
if( anchor.bottomRight != anchorNone )
{
rBounds.bottom= state.margin.bottom + (rParent.Height() * anchor.bottomRight.getY()) / anchorUnit;
rBounds.right = state.margin.right + (rParent.Width() * anchor.bottomRight.getX()) / anchorUnit;
}
else
{
rBounds.bottom = rBounds.top + anchor.component->getHeight();
rBounds.right = rBounds.left + anchor.component->getWidth();
}
if (anchor.style == styleStretch)
{
anchor.component->setBounds (rBounds);
}
else if (anchor.style==styleFixedAspect)
{
Rect rItem;
double aspect = double (rBounds.Width()) / rBounds.Height();
if( aspect > state.aspect )
{
rItem.top = rBounds.top;
rItem.bottom = rBounds.bottom;
int width = int (state.aspect * rItem.Height());
rItem.left = rBounds.left + (rBounds.Width()-width)/2;
rItem.right = rItem.left + width;
}
else
{
rItem.left = rBounds.left;
rItem.right = rBounds.right;
int height = int (1. / state.aspect * rItem.Width());
rItem.top = rBounds.top + (rBounds.Height() - height) / 2;
rItem.bottom = rItem.top + height;
}
anchor.component->setBounds( rItem );
}
}
}
}
void ResizableLayout::componentMovedOrResized (Component& component,
bool wasMoved,
bool wasResized)
{
if( &component == m_owner )
{
if (wasResized)
{
recalculateLayout ();
}
}
else
{
updateLayoutFor (&component);
}
}
void ResizableLayout::componentBeingDeleted (Component& component)
{
if( &component != m_owner )
{
m_anchors.removeValue (&component);
m_states.removeValue (&component);
}
}
Rectangle<int> ResizableLayout::calcBoundsOfChildren (Component* parent)
{
Rectangle<int> r;
for( int i=0; i<parent->getNumChildComponents(); i++ )
r = r.getUnion( parent->getChildComponent(i)->getBounds() );
return r;
}
Here’s a goofy example program that shows you how to use it
#include "juce.h"
#include "ResizableLayout.h"
struct ContentComponent : Component, ResizableLayout
{
ContentComponent() : ResizableLayout (this)
{
// create the initial layout
// must have a defined initial size
setSize(512, 384);
TextButton* b;
b = new TextButton ("Juce");
b->setBounds (32, 32, 512-64, 32);
addToLayout (b, anchorTopLeft, anchorTopRight);
addAndMakeVisible (b);
b = new TextButton ("One");
b->setBounds (32, 96, 128, 32);
addToLayout (b, Point<int>(0,0), Point<int>(33,0));
addAndMakeVisible (b);
b = new TextButton ("Two");
b->setBounds (32+128+32, 96, 128, 32);
addToLayout (b, Point<int>(33,0), Point<int>(66,0));
addAndMakeVisible (b);
b = new TextButton ("Three");
b->setBounds (32+128+32+128+32, 96, 128, 32);
addToLayout (b, Point<int>(66,0), Point<int>(100,0));
addAndMakeVisible (b);
b = new TextButton ("BR");
b->setBounds (512-96, 384-64, 64, 32);
addToLayout (b, anchorBottomRight);
addAndMakeVisible (b);
b = new TextButton ("Aspect");
b->setBounds (32, 160, 100, 100);
b->setConnectedEdges (Button::ConnectedOnLeft|Button::ConnectedOnTop|Button::ConnectedOnRight|Button::ConnectedOnBottom);
addToLayout (b, anchorTopLeft, anchorBottomCenter, styleFixedAspect);
addAndMakeVisible (b);
b = new TextButton ("Stretchy");
b->setBounds (256, 160, 200, 96);
b->setConnectedEdges (Button::ConnectedOnLeft|Button::ConnectedOnTop|Button::ConnectedOnRight|Button::ConnectedOnBottom);
addToLayout (b, anchorTopCenter, anchorBottomRight);
addAndMakeVisible (b);
// turn it on
activateLayout ();
}
~ContentComponent() { deleteAllChildren(); }
void paint (Graphics& g)
{
g.setColour (Colours::grey);
g.fillAll();
}
};
struct MainWindow : DocumentWindow
{
MainWindow()
: DocumentWindow (JUCE_T("Test")
, Colours::black
, DocumentWindow::allButtons
, true )
{
ContentComponent* p = new ContentComponent;
setResizable (true, false);
setContentComponent (p, true, true);
centreWithSize (getWidth(), getHeight());
setVisible( true );
}
~MainWindow() {}
void closeButtonPressed() { JUCEApplication::quit(); }
};
struct MainApp : JUCEApplication
{
MainApp() : mainWindow(0) { s_app=this; }
~MainApp() { s_app=0; }
static MainApp& GetInstance() { return *s_app; }
const String getApplicationName() { return JUCE_T("JuceTest"); }
const String getApplicationVersion() { return JUCE_T("0.1.0"); }
bool moreThanOneInstanceAllowed() { return true; }
void anotherInstanceStarted (const String& commandLine) {}
void initialise (const String& commandLine)
{
mainWindow = new MainWindow;
}
void shutdown()
{
delete mainWindow;
}
static MainApp* s_app;
MainWindow* mainWindow;
};
MainApp* MainApp::s_app = 0;
START_JUCE_APPLICATION (MainApp)