An old snippet dug out of my personal archive, thought I might share it here…
It’s a Component you can use as an overlay to allow the user to edit the layout of children of another Component. The only prerequisite is that the editor component is a sibling of the component being edited.
Just add the editor to the same component as your ‘editee’, and call setTargetComponent(). It will automatically take the bounds of the target, and create overlay alias components (each draggable/resizable) for each child of the target. You can toggle the editor using setEnabled().
Pretty simple, really.
ComponentLayoutEditor.h
#ifndef _COMPONENTLAYOUTEDITOR_H_
#define _COMPONENTLAYOUTEDITOR_H_
#include "juce.h"
//=============================================================================
class ChildAlias : public Component
{
public:
ChildAlias (Component* targetChild);
~ChildAlias ();
void resized ();
void paint (Graphics& g);
const Component* getTargetChild ();
void updateFromTarget ();
void applyToTarget ();
virtual void userChangedBounds ();
virtual void userStartedChangingBounds ();
virtual void userStoppedChangingBounds ();
bool boundsChangedSinceStart ();
void mouseEnter (const MouseEvent& e);
void mouseExit (const MouseEvent& e);
void mouseDown (const MouseEvent& e);
void mouseUp (const MouseEvent& e);
void mouseDrag (const MouseEvent& e);
private:
CriticalSection bounds;
ComponentDragger dragger;
ComponentDeletionWatcher target;
bool interest;
bool userAdjusting;
Rectangle startBounds;
ResizableBorderComponent* resizer;
};
//=============================================================================
class ComponentLayoutEditor : public Component
{
public:
enum ColourIds
{
aliasIdleColour,
aliasHoverColour
};
ComponentLayoutEditor ();
~ComponentLayoutEditor ();
void resized ();
void paint (Graphics& g);
void setTargetComponent (Component* target);
void bindWithTarget ();
void updateFrames ();
void enablementChanged ();
const Component* getTarget ();
private:
virtual ChildAlias* createAlias (Component* child);
ComponentDeletionWatcher* target;
OwnedArray<ChildAlias> frames;
};
#endif//_COMPONENTLAYOUTEDITOR_H_
ComponentLayoutEditor.cpp
#include "ComponentLayoutEditor.h"
ChildAlias::ChildAlias (Component* targetChild)
: target (targetChild)
{
resizer = new ResizableBorderComponent (this,0);
addAndMakeVisible (resizer);
resizer->addMouseListener (this,false);
interest = false;
userAdjusting = false;
updateFromTarget ();
setRepaintsOnMouseActivity (true);
}
ChildAlias::~ChildAlias ()
{
delete resizer;
}
void ChildAlias::resized ()
{
resizer->setBounds (0,0,getWidth(),getHeight());
if (resizer->isMouseButtonDown ())
{
applyToTarget ();
}
}
void ChildAlias::paint (Graphics& g)
{
Colour c;
if (interest)
c = findColour (ComponentLayoutEditor::aliasHoverColour,true);
else c = findColour (ComponentLayoutEditor::aliasIdleColour,true);
g.setColour (c.withMultipliedAlpha (0.3f));
g.fillAll ();
g.setColour (c);
g.drawRect (0,0,getWidth(),getHeight(),3);
}
const Component* ChildAlias::getTargetChild ()
{
return target.getComponent ();
}
void ChildAlias::updateFromTarget ()
{
if (!target.hasBeenDeleted ())
{
setBounds ( target.getComponent ()->getBounds () );
}
}
void ChildAlias::applyToTarget ()
{
if (!target.hasBeenDeleted ())
{
Component* c = (Component*) target.getComponent ();
c->setBounds (getBounds ());
userChangedBounds ();
}
}
void ChildAlias::userChangedBounds ()
{
}
void ChildAlias::userStartedChangingBounds ()
{
}
void ChildAlias::userStoppedChangingBounds ()
{
}
bool ChildAlias::boundsChangedSinceStart ()
{
return startBounds != getBounds ();
}
void ChildAlias::mouseDown (const MouseEvent& e)
{
toFront (true);
if (e.eventComponent == resizer)
{
}
else
{
dragger.startDraggingComponent (this,0);
}
userAdjusting = true;
startBounds = getBounds ();
userStartedChangingBounds ();
}
void ChildAlias::mouseUp (const MouseEvent& e)
{
if (e.eventComponent == resizer)
{
}
else
{
}
if (userAdjusting) userStoppedChangingBounds ();
userAdjusting = false;
}
void ChildAlias::mouseDrag (const MouseEvent& e)
{
if (e.eventComponent == resizer)
{
}
else
{
if (!e.mouseWasClicked ())
{
dragger.dragComponent (this,e);
applyToTarget ();
}
}
}
void ChildAlias::mouseEnter (const MouseEvent& e)
{
interest = true;
repaint ();
}
void ChildAlias::mouseExit (const MouseEvent& e)
{
interest = false;
repaint ();
}
//=============================================================================
ComponentLayoutEditor::ComponentLayoutEditor ()
: target (0)
{
setColour (ComponentLayoutEditor::aliasIdleColour,Colours::lightgrey.withAlpha(0.2f));
setColour (ComponentLayoutEditor::aliasHoverColour,Colours::white.withAlpha(0.5f));
}
ComponentLayoutEditor::~ComponentLayoutEditor ()
{
if (target) { deleteAndZero (target); }
}
void ComponentLayoutEditor::resized ()
{
for (int i=0; i<frames.size(); i++)
{
frames.getUnchecked(i)->updateFromTarget ();
}
}
void ComponentLayoutEditor::paint (Graphics& g)
{
}
void ComponentLayoutEditor::setTargetComponent (Component* targetComp)
{
jassert (targetComp);
jassert (targetComp->getParentComponent() == getParentComponent());
if (target)
{
if (target->getComponent() == targetComp) return;
deleteAndZero (target);
}
target = new ComponentDeletionWatcher (targetComp);
bindWithTarget ();
}
void ComponentLayoutEditor::bindWithTarget ()
{
if (target && !target->hasBeenDeleted ())
{
Component* t = (Component*) target->getComponent ();
Component* p = t->getParentComponent ();
p->addAndMakeVisible (this);
setBounds (t->getBounds ());
updateFrames ();
}
}
void ComponentLayoutEditor::updateFrames ()
{
frames.clear ();
if (target && !target->hasBeenDeleted ())
{
Component* t = (Component*) target->getComponent ();
int n = t->getNumChildComponents ();
for (int i=0; i<n; i++)
{
Component* c = t->getChildComponent (i);
if (c)
{
ChildAlias* alias = createAlias (c);
if (alias)
{
frames.add (alias);
addAndMakeVisible (alias);
}
}
}
}
}
void ComponentLayoutEditor::enablementChanged ()
{
if (isEnabled ())
{
setVisible (true);
}
else
{
setVisible (false);
}
}
const Component* ComponentLayoutEditor::getTarget ()
{
if (target) return target->getComponent ();
return 0;
}
ChildAlias* ComponentLayoutEditor::createAlias (Component* child)
{
return new ChildAlias (child);
}
It’s been dug out as I’m rebuilding handxl from the ground up in my free time, so maybe it might one day see a release (and in the plugin form it was always meant to be in!). I was planning on expanding this to be a little more customisable [e.g. subclassing the alias overlay to act on the user editing the position - bypassing awkward resized() issues when components are mapped to scaled back-end bounds, etc], but this is the form it’s been sat about doing nothing in. Have fun!