1
0
Fork 0
mirror of https://github.com/juce-framework/JUCE.git synced 2026-01-10 23:44:24 +00:00
JUCE/extras/Projucer/Source/ComponentEditor/PaintElements/jucer_PaintElement.cpp
2023-10-02 15:42:20 +01:00

688 lines
22 KiB
C++

/*
==============================================================================
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.
==============================================================================
*/
#include "../../Application/jucer_Headers.h"
#include "../../Application/jucer_Application.h"
#include "../jucer_PaintRoutine.h"
#include "../jucer_UtilityFunctions.h"
#include "../UI/jucer_JucerCommandIDs.h"
#include "../UI/jucer_PaintRoutineEditor.h"
#include "../Properties/jucer_PositionPropertyBase.h"
#include "jucer_ElementSiblingComponent.h"
#include "jucer_PaintElementUndoableAction.h"
//==============================================================================
PaintElement::PaintElement (PaintRoutine* owner_,
const String& typeName_)
: borderThickness (4),
owner (owner_),
typeName (typeName_),
selected (false),
dragging (false),
originalAspectRatio (1.0)
{
setRepaintsOnMouseActivity (true);
position.rect.setWidth (100);
position.rect.setHeight (100);
setMinimumOnscreenAmounts (0, 0, 0, 0);
setSizeLimits (borderThickness * 2 + 1, borderThickness * 2 + 1, 8192, 8192);
border.reset (new ResizableBorderComponent (this, this));
addChildComponent (border.get());
border->setBorderThickness (BorderSize<int> (borderThickness));
if (owner != nullptr)
owner->getSelectedElements().addChangeListener (this);
selfChangeListenerList.addChangeListener (this);
siblingComponentsChanged();
}
PaintElement::~PaintElement()
{
siblingComponents.clear();
if (owner != nullptr)
{
owner->getSelectedElements().deselect (this);
owner->getSelectedElements().removeChangeListener (this);
}
}
//==============================================================================
void PaintElement::setInitialBounds (int parentWidth, int parentHeight)
{
RelativePositionedRectangle pr (getPosition());
pr.rect.setX (parentWidth / 4 + Random::getSystemRandom().nextInt (parentWidth / 4) - parentWidth / 8);
pr.rect.setY (parentHeight / 3 + Random::getSystemRandom().nextInt (parentHeight / 4) - parentHeight / 8);
setPosition (pr, false);
}
//==============================================================================
const RelativePositionedRectangle& PaintElement::getPosition() const
{
return position;
}
class PaintElementMoveAction : public PaintElementUndoableAction <PaintElement>
{
public:
PaintElementMoveAction (PaintElement* const element, const RelativePositionedRectangle& newState_)
: PaintElementUndoableAction <PaintElement> (element),
newState (newState_),
oldState (element->getPosition())
{
}
bool perform()
{
showCorrectTab();
getElement()->setPosition (newState, false);
return true;
}
bool undo()
{
showCorrectTab();
getElement()->setPosition (oldState, false);
return true;
}
RelativePositionedRectangle newState, oldState;
};
class ChangePaintElementBoundsAction : public PaintElementUndoableAction <PaintElement>
{
public:
ChangePaintElementBoundsAction (PaintElement* const element, const Rectangle<int>& bounds)
: PaintElementUndoableAction <PaintElement> (element),
newBounds (bounds),
oldBounds (element->getBounds())
{
}
bool perform()
{
showCorrectTab();
getElement()->setBounds (newBounds);
return true;
}
bool undo()
{
showCorrectTab();
getElement()->setBounds (oldBounds);
return true;
}
private:
Rectangle<int> newBounds, oldBounds;
};
class ChangePaintElementBoundsAndPropertiesAction : public PaintElementUndoableAction <PaintElement>
{
public:
ChangePaintElementBoundsAndPropertiesAction (PaintElement* const element, const Rectangle<int>& bounds,
const NamedValueSet& props)
: PaintElementUndoableAction <PaintElement> (element),
newBounds (bounds),
oldBounds (element->getBounds()),
newProps (props),
oldProps (element->getProperties())
{
}
bool perform()
{
showCorrectTab();
if (auto* pe = dynamic_cast<PaintRoutineEditor*> (getElement()->getParentComponent()))
getElement()->setCurrentBounds (newBounds, pe->getComponentArea(), false);
getElement()->getProperties() = newProps;
return true;
}
bool undo()
{
showCorrectTab();
if (auto* pe = dynamic_cast<PaintRoutineEditor*> (getElement()->getParentComponent()))
getElement()->setCurrentBounds (oldBounds, pe->getComponentArea(), false);
getElement()->getProperties() = oldProps;
return true;
}
private:
Rectangle<int> newBounds, oldBounds;
NamedValueSet newProps, oldProps;
};
void PaintElement::setPosition (const RelativePositionedRectangle& newPosition, const bool undoable)
{
if (position != newPosition)
{
if (undoable)
{
perform (new PaintElementMoveAction (this, newPosition),
"Move " + getTypeName());
}
else
{
position = newPosition;
if (owner != nullptr)
owner->changed();
}
}
}
void PaintElement::setPaintElementBounds (const Rectangle<int>& newBounds, const bool undoable)
{
if (getBounds() != newBounds)
{
if (undoable)
{
perform (new ChangePaintElementBoundsAction (this, newBounds), "Change paint element bounds");
}
else
{
setBounds (newBounds);
changed();
}
}
}
void PaintElement::setPaintElementBoundsAndProperties (PaintElement* elementToPosition, const Rectangle<int>& newBounds,
PaintElement* referenceElement, const bool undoable)
{
auto props = NamedValueSet (elementToPosition->getProperties());
auto rect = elementToPosition->getPosition().rect;
auto referenceElementPosition = referenceElement->getPosition();
auto referenceElementRect = referenceElementPosition.rect;
rect.setModes (referenceElementRect.getAnchorPointX(), referenceElementRect.getPositionModeX(),
referenceElementRect.getAnchorPointY(), referenceElementRect.getPositionModeY(),
referenceElementRect.getWidthMode(), referenceElementRect.getHeightMode(),
elementToPosition->getBounds());
props.set ("pos", rect.toString());
props.set ("relativeToX", String::toHexString (referenceElementPosition.relativeToX));
props.set ("relativeToY", String::toHexString (referenceElementPosition.relativeToY));
props.set ("relativeToW", String::toHexString (referenceElementPosition.relativeToW));
props.set ("relativeToH", String::toHexString (referenceElementPosition.relativeToH));
if (elementToPosition->getBounds() != newBounds || elementToPosition->getProperties() != props)
{
if (undoable)
{
perform (new ChangePaintElementBoundsAndPropertiesAction (elementToPosition, newBounds, props),
"Change paint element bounds");
}
else
{
if (auto* pe = dynamic_cast<PaintRoutineEditor*> (elementToPosition->getParentComponent()))
elementToPosition->setCurrentBounds (newBounds, pe->getComponentArea(), false);
elementToPosition->getProperties() = props;
owner->changed();
}
}
}
//==============================================================================
Rectangle<int> PaintElement::getCurrentBounds (const Rectangle<int>& parentArea) const
{
return position.getRectangle (parentArea, getDocument()->getComponentLayout());
}
void PaintElement::setCurrentBounds (const Rectangle<int>& newBounds,
const Rectangle<int>& parentArea,
const bool undoable)
{
RelativePositionedRectangle pr (position);
pr.updateFrom (newBounds.getX() - parentArea.getX(),
newBounds.getY() - parentArea.getY(),
jmax (1, newBounds.getWidth()),
jmax (1, newBounds.getHeight()),
Rectangle<int> (0, 0, parentArea.getWidth(), parentArea.getHeight()),
getDocument()->getComponentLayout());
setPosition (pr, undoable);
updateBounds (parentArea);
}
void PaintElement::updateBounds (const Rectangle<int>& parentArea)
{
if (! parentArea.isEmpty())
{
setBounds (getCurrentBounds (parentArea)
.expanded (borderThickness,
borderThickness));
for (int i = siblingComponents.size(); --i >= 0;)
siblingComponents.getUnchecked (i)->updatePosition();
}
}
//==============================================================================
class ElementPositionProperty : public PositionPropertyBase
{
public:
ElementPositionProperty (PaintElement* e, const String& name,
ComponentPositionDimension dimension_)
: PositionPropertyBase (e, name, dimension_, true, false,
e->getDocument()->getComponentLayout()),
listener (e),
element (e)
{
listener.setPropertyToRefresh (*this);
}
void setPosition (const RelativePositionedRectangle& newPos)
{
if (element->getOwner()->getSelectedElements().getNumSelected() > 1)
positionOtherSelectedElements (getPosition(), newPos);
listener.owner->setPosition (newPos, true);
}
RelativePositionedRectangle getPosition() const
{
return listener.owner->getPosition();
}
private:
ElementListener<PaintElement> listener;
PaintElement* element;
void positionOtherSelectedElements (const RelativePositionedRectangle& oldPos, const RelativePositionedRectangle& newPos)
{
for (auto* s : element->getOwner()->getSelectedElements())
{
if (s != element)
{
auto currentPos = s->getPosition();
auto diff = 0.0;
if (dimension == ComponentPositionDimension::componentX)
{
diff = newPos.rect.getX() - oldPos.rect.getX();
currentPos.rect.setX (currentPos.rect.getX() + diff);
}
else if (dimension == ComponentPositionDimension::componentY)
{
diff = newPos.rect.getY() - oldPos.rect.getY();
currentPos.rect.setY (currentPos.rect.getY() + diff);
}
else if (dimension == ComponentPositionDimension::componentWidth)
{
diff = newPos.rect.getWidth() - oldPos.rect.getWidth();
currentPos.rect.setWidth (currentPos.rect.getWidth() + diff);
}
else if (dimension == ComponentPositionDimension::componentHeight)
{
diff = newPos.rect.getHeight() - oldPos.rect.getHeight();
currentPos.rect.setHeight (currentPos.rect.getHeight() + diff);
}
s->setPosition (currentPos, true);
}
}
}
};
//==============================================================================
void PaintElement::getEditableProperties (Array <PropertyComponent*>& props, bool multipleSelected)
{
ignoreUnused (multipleSelected);
props.add (new ElementPositionProperty (this, "x", PositionPropertyBase::componentX));
props.add (new ElementPositionProperty (this, "y", PositionPropertyBase::componentY));
props.add (new ElementPositionProperty (this, "width", PositionPropertyBase::componentWidth));
props.add (new ElementPositionProperty (this, "height", PositionPropertyBase::componentHeight));
}
//==============================================================================
JucerDocument* PaintElement::getDocument() const
{
return owner->getDocument();
}
void PaintElement::changed()
{
repaint();
owner->changed();
}
bool PaintElement::perform (UndoableAction* action, const String& actionName)
{
return owner->perform (action, actionName);
}
void PaintElement::parentHierarchyChanged()
{
updateSiblingComps();
}
//==============================================================================
void PaintElement::drawExtraEditorGraphics (Graphics&, const Rectangle<int>& /*relativeTo*/)
{
}
void PaintElement::paint (Graphics& g)
{
if (auto* pe = dynamic_cast<PaintRoutineEditor*> (getParentComponent()))
{
auto area = pe->getComponentArea();
g.saveState();
g.setOrigin (area.getPosition() - Component::getPosition());
area.setPosition (0, 0);
g.saveState();
g.reduceClipRegion (0, 0, area.getWidth(), area.getHeight());
draw (g, getDocument()->getComponentLayout(), area);
g.restoreState();
drawExtraEditorGraphics (g, area);
g.restoreState();
if (selected)
{
const BorderSize<int> borderSize (border->getBorderThickness());
auto baseColour = findColour (defaultHighlightColourId);
drawResizableBorder (g, getWidth(), getHeight(), borderSize,
(isMouseOverOrDragging() || border->isMouseOverOrDragging()),
baseColour.withAlpha (owner->getSelectedElements().getSelectedItem (0) == this ? 1.0f : 0.3f));
}
else if (isMouseOverOrDragging())
{
drawMouseOverCorners (g, getWidth(), getHeight());
}
}
}
void PaintElement::resized()
{
border->setBounds (getLocalBounds());
}
void PaintElement::mouseDown (const MouseEvent& e)
{
dragging = false;
if (owner != nullptr)
{
owner->getSelectedPoints().deselectAll();
mouseDownSelectStatus = owner->getSelectedElements().addToSelectionOnMouseDown (this, e.mods);
}
if (e.mods.isPopupMenu())
{
showPopupMenu();
return; // this may be deleted now..
}
}
void PaintElement::mouseDrag (const MouseEvent& e)
{
if (! e.mods.isPopupMenu())
{
if (auto* pe = dynamic_cast<PaintRoutineEditor*> (getParentComponent()))
{
auto area = pe->getComponentArea();
if (selected && ! dragging)
{
dragging = e.mouseWasDraggedSinceMouseDown();
if (dragging)
owner->startDragging (area);
}
if (dragging)
owner->dragSelectedComps (e.getDistanceFromDragStartX(),
e.getDistanceFromDragStartY(),
area);
}
}
}
void PaintElement::mouseUp (const MouseEvent& e)
{
if (owner != nullptr)
{
if (dragging)
owner->endDragging();
if (owner != nullptr)
owner->getSelectedElements().addToSelectionOnMouseUp (this, e.mods, dragging, mouseDownSelectStatus);
}
}
void PaintElement::resizeStart()
{
if (getHeight() > 0)
originalAspectRatio = getWidth() / (double) getHeight();
else
originalAspectRatio = 1.0;
}
void PaintElement::resizeEnd()
{
}
void PaintElement::checkBounds (Rectangle<int>& b,
const Rectangle<int>& previousBounds,
const Rectangle<int>& limits,
const bool isStretchingTop,
const bool isStretchingLeft,
const bool isStretchingBottom,
const bool isStretchingRight)
{
if (ModifierKeys::currentModifiers.isShiftDown())
setFixedAspectRatio (originalAspectRatio);
else
setFixedAspectRatio (0.0);
ComponentBoundsConstrainer::checkBounds (b, previousBounds, limits, isStretchingTop, isStretchingLeft, isStretchingBottom, isStretchingRight);
if (auto* document = getDocument())
{
if (document->isSnapActive (true))
{
if (auto* pe = dynamic_cast<PaintRoutineEditor*> (getParentComponent()))
{
auto area = pe->getComponentArea();
int x = b.getX();
int y = b.getY();
int w = b.getWidth();
int h = b.getHeight();
x += borderThickness - area.getX();
y += borderThickness - area.getY();
w -= borderThickness * 2;
h -= borderThickness * 2;
int right = x + w;
int bottom = y + h;
if (isStretchingRight)
right = document->snapPosition (right);
if (isStretchingBottom)
bottom = document->snapPosition (bottom);
if (isStretchingLeft)
x = document->snapPosition (x);
if (isStretchingTop)
y = document->snapPosition (y);
w = (right - x) + borderThickness * 2;
h = (bottom - y) + borderThickness * 2;
x -= borderThickness - area.getX();
y -= borderThickness - area.getY();
b = { x, y, w, h };
}
}
}
}
void PaintElement::applyBoundsToComponent (Component&, Rectangle<int> newBounds)
{
if (getBounds() != newBounds)
{
getDocument()->getUndoManager().undoCurrentTransactionOnly();
auto dX = newBounds.getX() - getX();
auto dY = newBounds.getY() - getY();
auto dW = newBounds.getWidth() - getWidth();
auto dH = newBounds.getHeight() - getHeight();
if (auto* pe = dynamic_cast<PaintRoutineEditor*> (getParentComponent()))
setCurrentBounds (newBounds.expanded (-borderThickness, -borderThickness),
pe->getComponentArea(), true);
if (owner->getSelectedElements().getNumSelected() > 1)
{
for (auto selectedElement : owner->getSelectedElements())
{
if (selectedElement != nullptr && selectedElement != this)
{
if (auto* pe = dynamic_cast<PaintRoutineEditor*> (selectedElement->getParentComponent()))
{
Rectangle<int> r { selectedElement->getX() + dX, selectedElement->getY() + dY,
selectedElement->getWidth() + dW, selectedElement->getHeight() + dH };
selectedElement->setCurrentBounds (r.expanded (-borderThickness, -borderThickness),
pe->getComponentArea(), true);
}
}
}
}
}
}
Rectangle<int> PaintElement::getCurrentAbsoluteBounds() const
{
if (auto* pe = dynamic_cast<PaintRoutineEditor*> (getParentComponent()))
return position.getRectangle (pe->getComponentArea(), getDocument()->getComponentLayout());
return {};
}
void PaintElement::getCurrentAbsoluteBoundsDouble (double& x, double& y, double& w, double& h) const
{
if (auto* pe = dynamic_cast<PaintRoutineEditor*> (getParentComponent()))
position.getRectangleDouble (x, y, w, h, pe->getComponentArea(), getDocument()->getComponentLayout());
}
void PaintElement::changeListenerCallback (ChangeBroadcaster*)
{
const bool nowSelected = owner != nullptr && owner->getSelectedElements().isSelected (this);
if (selected != nowSelected)
{
selected = nowSelected;
border->setVisible (nowSelected);
repaint();
selectionChanged (nowSelected);
}
updateSiblingComps();
}
void PaintElement::selectionChanged (const bool /*isSelected*/)
{
}
void PaintElement::createSiblingComponents()
{
}
void PaintElement::siblingComponentsChanged()
{
siblingComponents.clear();
selfChangeListenerList.sendChangeMessage();
}
void PaintElement::updateSiblingComps()
{
if (selected && getParentComponent() != nullptr && owner->getSelectedElements().getNumSelected() == 1)
{
if (siblingComponents.size() == 0)
createSiblingComponents();
for (int i = siblingComponents.size(); --i >= 0;)
siblingComponents.getUnchecked (i)->updatePosition();
}
else
{
siblingComponents.clear();
}
}
void PaintElement::showPopupMenu()
{
auto* commandManager = &ProjucerApplication::getCommandManager();
PopupMenu m;
m.addCommandItem (commandManager, JucerCommandIDs::toFront);
m.addCommandItem (commandManager, JucerCommandIDs::toBack);
m.addSeparator();
if (owner != nullptr && owner->getSelectedElements().getNumSelected() > 1)
{
m.addCommandItem (commandManager, JucerCommandIDs::alignTop);
m.addCommandItem (commandManager, JucerCommandIDs::alignRight);
m.addCommandItem (commandManager, JucerCommandIDs::alignBottom);
m.addCommandItem (commandManager, JucerCommandIDs::alignLeft);
m.addSeparator();
}
m.addCommandItem (commandManager, StandardApplicationCommandIDs::cut);
m.addCommandItem (commandManager, StandardApplicationCommandIDs::copy);
m.addCommandItem (commandManager, StandardApplicationCommandIDs::paste);
m.addCommandItem (commandManager, StandardApplicationCommandIDs::del);
m.showMenuAsync ({});
}