CircularMenu.cpp
// We need our declaration
#include "CircularMenu.hpp"
namespace juce
{
static const float Pi = 3.1415956535f;
static const float iconAlpha = 0.6f;
static const int dismissCommandId = 0x6287345f;
static VoidArray activeMenuWindows;
struct CircularMenuWindow : public Component,
private Timer
{
enum
{
timerInterval = 10,
maxNbSteps = 6,
FadeIn = 0,
Unfold = 1,
UnfoldAndFadeIn = 2,
ShowLabels = 3,
Static = 4,
FoldAndFadeOut = 5,
};
uint32 menuCreationTime, lastFocused, lastScroll, lastMouseMoveTime, timeEnteredCurrentChildComp;
CircularMenuWindow* activeSubMenu;
ComponentDeletionWatcher* attachedCompWatcher;
Component* const componentAttachedTo;
ApplicationCommandManager** managerOfChosenCommand;
int lastMouseX, lastMouseY;
ReduceOpacityEffect makeTransparent;
GlowEffect dropShadow;
int currentAnimationPhase;
int currentAnimationSteps;
int selectedItemID;
Point menuCenter;
float radius;
Array<Label*> labels;
OwnedArray<Drawable> & icons;
Array<int> & itemsID;
StringArray & itemsText;
CircularMenuWindow(const int screenX, const int screenY, const int width, const int height,
OwnedArray<Drawable> & _icons, Array<int> & _itemsID, StringArray & _itemsText, ApplicationCommandManager** _managerOfChosenCommand, Component* const _componentAttachedTo)
: icons(_icons),
itemsID(_itemsID),
itemsText(_itemsText),
activeSubMenu(0),
attachedCompWatcher(0),
makeTransparent(0),
selectedItemID(0),
radius((float)width / 2),
managerOfChosenCommand(_managerOfChosenCommand),
componentAttachedTo(_componentAttachedTo),
currentAnimationPhase(4),
currentAnimationSteps(0)
{
menuCreationTime = lastFocused = lastScroll = Time::getMillisecondCounter();
setWantsKeyboardFocus (true);
attachedCompWatcher = componentAttachedTo != 0 ? new ComponentDeletionWatcher (componentAttachedTo) : 0;
setOpaque (false);
setAlwaysOnTop (true);
Desktop::getInstance().addGlobalMouseListener (this);
activeMenuWindows.add (this);
Rectangle rect;
// At first, the petal are all transparent
// Then add and dispatch the petals
for (int i = 0; i < itemsID.size(); i++)
{
// Create the petal now
Colour baseColour = Colours::mediumpurple;
CircularMenu::MenuPetal * petal = new CircularMenu::MenuPetal(itemsID.size(), *icons[i], itemsID[i], baseColour.withRotatedHue((float)i / (float)itemsID.size()), false);
petal->resizeToFit((float)width/2, (float)i * 2 * 3.1415926535f / itemsID.size());
if (i == 0) rect = petal->getBounds();
else rect = rect.getUnion(petal->getBounds());
// Then rotate the petal to the final position (will be removed afterward)
// petal->rotateAround((float)width / 2, (float)height/2, (float)i * 2 * 3.1415926535f / itemsID.size());
// Then add this object
addAndMakeVisible(petal);
// Set the component effect
petal->setComponentEffect(&makeTransparent);
}
addToDesktop (ComponentPeer::windowIsTemporary);
setBounds(screenX /*+ rect.getX()*/, screenY /*+ rect.getY()*/, width, height);
menuCenter.setXY((float)width / 2, (float)height / 2);
Rectangle currentBounds = getBounds();
Point topLeft((float)currentBounds.getX(), (float)currentBounds.getY());
for (i = 0; i < itemsID.size(); i++)
{
// Create the label at first
Label * label = new Label(String("MenuLabel") << i, itemsText[i]);
if (i == 0)
{
Font labelFont = label->getFont();
labelFont.setBold(true);
label->setFont(labelFont);
}
// Then compute the required label size
float labelHeight = label->getFont().getHeight() + 2.0f;
float labelWidth = label->getFont().getStringWidthFloat(label->getText()) + 5.0f;
// Compute the center point now
float x = radius + labelWidth / 2 + 5.0f;
float y = 0;
// Rotate it for the given petal
const AffineTransform & rotationUsed = AffineTransform::rotation((float)i * 2 * Pi / itemsID.size());
rotationUsed.transformPoint(x, y);
// Then translate to the current origin
x += menuCenter.getX();
y += menuCenter.getY();
// Then add it to the array and child component
labels.add(label);
// Define the current label bound
label->setBounds(roundFloatToInt(x - labelWidth/2), roundFloatToInt(y - labelHeight/2), roundFloatToInt(labelWidth), roundFloatToInt(labelHeight));
currentBounds = currentBounds.getUnion(label->getBounds().translated(roundFloatToInt(topLeft.getX()), roundFloatToInt(topLeft.getY())));
}
// Then, we need to enlarge our current bounds to include the labels
menuCenter.setXY(menuCenter.getX() + (topLeft.getX() - currentBounds.getX()), menuCenter.getY() + (topLeft.getY() - currentBounds.getY()));
setBounds(currentBounds);
// Now move every petal to their respective positions
for (i = 0; i < itemsID.size(); i++)
{
Point componentCenter((float)(getChildComponent(i)->getX() + getChildComponent(i)->getWidth() / 2), (float)(getChildComponent(i)->getY() + getChildComponent(i)->getHeight() / 2));
getChildComponent(i)->setCentrePosition(roundFloatToInt(componentCenter.getX() + topLeft.getX() - currentBounds.getX()), roundFloatToInt(componentCenter.getY() + topLeft.getY() - currentBounds.getY()));
CircularMenu::MenuPetal * menu = dynamic_cast<CircularMenu::MenuPetal*>(getChildComponent(i));
menu->setGeneralOffset(roundFloatToInt(topLeft.getX() - currentBounds.getX()), roundFloatToInt(topLeft.getY() - currentBounds.getY()));
}
// And adjust the label final bounds too
for (i = 0; i < labels.size(); i++)
{
Label * label = labels[i];
Point componentCenter((float)(label->getX() + label->getWidth() / 2), (float)(label->getY() + label->getHeight() / 2));
label->setCentrePosition(roundFloatToInt(componentCenter.getX() + topLeft.getX() - currentBounds.getX()), roundFloatToInt(componentCenter.getY() + topLeft.getY() - currentBounds.getY()));
// Stupid hack to avoid popping artefact
addChildComponent(label);
// label->setComponentEffect(&dropShadow);
label->setVisible(false);
label->addMouseListener(this, false);
}
//dropShadow.setShadowProperties(2, 0.8f, 1, 1);
dropShadow.setGlowProperties(6.0f, Colours::white);
setVisible(true);
// Unfold the petal now
unfoldPetals();
}
~CircularMenuWindow()
{
activeMenuWindows.removeValue (this);
Desktop::getInstance().removeGlobalMouseListener (this);
jassert (activeSubMenu == 0 || activeSubMenu->isValidComponent());
delete activeSubMenu;
deleteAllChildren(); delete attachedCompWatcher;
}
void paint(Graphics& g)
{
// g.drawRect(menuCenter.getX() - 2, menuCenter.getY() - 2, 4, 4);
}
void unfoldPetals()
{
currentAnimationPhase = UnfoldAndFadeIn;
currentAnimationSteps = 0;
startTimer(timerInterval);
}
void foldPetalsAndExit(const int selectedId)
{
stopTimer();
currentAnimationSteps = 0;
currentAnimationPhase = FoldAndFadeOut;
selectedItemID = selectedId;
startTimer(timerInterval);
}
void mouseEnter (const MouseEvent& e)
{
// Find which label this event is relative too
Label * label = dynamic_cast<Label*>(e.eventComponent);
if (label)
{
// Ok, now find the label index
for (int i = 0; i < itemsText.size(); i++)
{
((CircularMenu::MenuPetal*)getChildComponent(i))->makeSelected(itemsText[i] == label->getText());
}
}
}
/** Called when mouse leaves this petal */
void mouseExit (const MouseEvent& e)
{
// Find which label this event is relative too
Label * label = dynamic_cast<Label*>(e.eventComponent);
if (label)
{
// Ok, now find the label index
for (int i = 0; i < itemsText.size(); i++)
{
if (itemsText[i] == label->getText())
((CircularMenu::MenuPetal*)getChildComponent(i))->makeSelected(false);
}
}
}
/** Called when mouse button hasd been clicked on this petal */
void mouseUp (const MouseEvent& e)
{
// Find which label this event is relative too
Label * label = dynamic_cast<Label*>(e.eventComponent);
if (label)
{
// Ok, now find the label index
for (int i = 0; i < itemsText.size(); i++)
{
if (itemsText[i] == label->getText())
foldPetalsAndExit(itemsID[i]);
}
}
}
bool isOverAnyMenu()
{
return false;
}
void inputAttemptWhenModal()
{
timerCallback();
if (! isOverAnyMenu())
{
if (componentAttachedTo != 0 && ! attachedCompWatcher->hasBeenDeleted())
{
// we want to dismiss the menu, but if we do it synchronously, then
// the mouse-click will be allowed to pass through. That's good, except
// when the user clicks on the button that orginally popped the menu up,
// as they'll expect the menu to go away, and in fact it'll just
// come back. So only dismiss synchronously if they're not on the original
// comp that we're attached to.
int mx, my;
componentAttachedTo->getMouseXYRelative (mx, my);
if (componentAttachedTo->reallyContains (mx, my, true))
{
postCommandMessage (dismissCommandId); // dismiss asynchrounously
return;
}
}
this->foldPetalsAndExit(0);
}
}
void handleCommandMessage (int commandId)
{
Component::handleCommandMessage (commandId);
if (commandId == dismissCommandId)
foldPetalsAndExit (0);
}
void triggerCurrentlyHighlightedItem()
{
// Find the currently selected petal
int i = 0, currentSelection = -1;
for (i = 0; i < itemsID.size(); i++)
{
CircularMenu::MenuPetal * petal = dynamic_cast<CircularMenu::MenuPetal*>(getChildComponent(i));
if (petal && petal->isSelected())
{
currentSelection = i;
break;
}
}
if (currentSelection < 0 || currentSelection >= itemsID.size()) currentSelection = 0;
foldPetalsAndExit(itemsID[currentSelection]);
}
void selectNextItem (const int delta)
{
// Find the currently selected petal
int i = 0, currentSelection = -1;
for (i = 0; i < itemsID.size(); i++)
{
CircularMenu::MenuPetal * petal = dynamic_cast<CircularMenu::MenuPetal*>(getChildComponent(i));
if (petal && petal->isSelected())
{
currentSelection = i;
break;
}
}
// Then adjust the selection
currentSelection += delta;
if (currentSelection < 0) currentSelection = itemsID.size() - 1;
if (currentSelection >= itemsID.size()) currentSelection = 0;
// And apply
for (i = 0; i < itemsID.size(); i++)
{
CircularMenu::MenuPetal * petal = dynamic_cast<CircularMenu::MenuPetal*>(getChildComponent(i));
if (petal) petal->makeSelected(i == currentSelection);
}
}
bool keyPressed (const KeyPress& key)
{
if (key.isKeyCode (KeyPress::downKey))
{
selectNextItem (1);
}
else if (key.isKeyCode (KeyPress::upKey))
{
selectNextItem (-1);
}
else if (key.isKeyCode (KeyPress::returnKey))
{
triggerCurrentlyHighlightedItem();
}
else if (key.isKeyCode (KeyPress::escapeKey))
{
foldPetalsAndExit(0);
}
else
{
return false;
}
return true;
}
void timerCallback()
{
if (!isVisible()) return;
if (attachedCompWatcher != 0 && attachedCompWatcher->hasBeenDeleted())
{
//dismissMenu (0);
return;
}
switch(currentAnimationPhase)
{
case FadeIn: // We make the petal appear
{
// The number of steps in this phase is 10
makeTransparent.setOpacity((float)(++currentAnimationSteps) / (float)maxNbSteps);
for (int i = 0; i < itemsID.size(); i++)
{
this->getChildComponent(i)->repaint();
}
if (currentAnimationSteps == maxNbSteps)
{
currentAnimationSteps = 0;
currentAnimationPhase = Unfold;
}
break;
}
case Unfold: // We rotate petals
{
currentAnimationSteps++;
for (int i = 0; i < itemsID.size(); i++)
{
CircularMenu::MenuPetal * petal = dynamic_cast<CircularMenu::MenuPetal*>(this->getChildComponent(i));
if (petal)
petal->rotateAround(menuCenter.getX(), menuCenter.getY(), (float)i * 2 * Pi * (float)(currentAnimationSteps / (float)maxNbSteps) / itemsID.size());
}
if (currentAnimationSteps == maxNbSteps)
{
currentAnimationSteps = 0;
currentAnimationPhase = ShowLabels;
}
}
break;
case UnfoldAndFadeIn: // We both make the petal appear and rotate
{
// The number of steps in this phase is 10
makeTransparent.setOpacity((float)(++currentAnimationSteps) / (float)maxNbSteps);
for (int i = 0; i < itemsID.size(); i++)
{
CircularMenu::MenuPetal * petal = dynamic_cast<CircularMenu::MenuPetal*>(this->getChildComponent(i));
if (petal)
petal->rotateAround(menuCenter.getX(), menuCenter.getY(), (float)i * 2 * Pi * (float)(currentAnimationSteps / (float)maxNbSteps) / itemsID.size());
}
if (currentAnimationSteps == maxNbSteps)
{
currentAnimationSteps = 0;
currentAnimationPhase = ShowLabels;
}
break;
}
case ShowLabels:
{
// Try to work out in 3 phases
// static Array<Component*> desktopComponents;
for (int i = 0; i < labels.size(); i++)
{
/*
labels[i]->setColour(Label::backgroundColourId, Colours::white);
labels[i]->setColour(Label::outlineColourId, Colours::transparentWhite);
labels[i]->setColour(Label::textColourId, Colours::black);
*/
labels[i]->setComponentEffect(&dropShadow);
labels[i]->setVisible(true);
}
currentAnimationSteps = 0;
currentAnimationPhase = Static;
}
break;
case Static: // This is static step
break;
case FoldAndFadeOut: // We fold and make petal disappear
{
// The number of steps in this phase is 10
makeTransparent.setOpacity(1.0f - (float)(++currentAnimationSteps) / (float)maxNbSteps);
for (int i = 0; i < itemsID.size(); i++)
{
CircularMenu::MenuPetal * petal = dynamic_cast<CircularMenu::MenuPetal*>(this->getChildComponent(i));
if (petal)
petal->rotateAround(menuCenter.getX(), menuCenter.getY(), (float)i * 2 * Pi * (1.0f - (float)(currentAnimationSteps / (float)maxNbSteps)) / itemsID.size());
}
if (currentAnimationSteps == maxNbSteps)
{
currentAnimationSteps = 0;
currentAnimationPhase = Static;
exitModalState(selectedItemID);
}
break;
}
}
int mx, my;
Desktop::getMousePosition (mx, my);
int x = mx, y = my;
globalPositionToRelative (x, y);
const uint32 now = Time::getMillisecondCounter();
/*
if (now > timeEnteredCurrentChildComp + 100
&& reallyContains (x, y, true)
&& currentChild->isValidComponent()
&& (! disableMouseMoves)
&& ! (activeSubMenu != 0 && activeSubMenu->isVisible()))
{
showSubMenuFor (currentChild);
}
*/
}
};
//==================================== Petals
CircularMenu::MenuPetal::MenuPetal(const int nbPetal, Drawable & _icon, const int _itemID, const Colour & _baseColour, const bool hasSubMenu)
: icon(_icon), baseColour(_baseColour.withAlpha(0.4f)), withSubMenu(hasSubMenu),
surfaceAngle(2 * Pi / (float)nbPetal), itemID(_itemID), isPreSelected(false),
currentAngle(0), petalRadius(0), iconImage(0), generalOffsetX(0), generalOffsetY(0)
{
}
CircularMenu::MenuPetal::~MenuPetal()
{
delete iconImage;
deleteAllChildren();
}
void CircularMenu::MenuPetal::resizeToFit(const float radius, const float finalAngle)
{
setCircleRadius(radius);
// This part need work too
// Then move the label now too
// label->setBounds(roundFloatToInt(radius * 2), roundFloatToInt(radius - label->getFont().getHeight() * 0.5f), label->getFont().getStringWidth(label->getText()) + 5, roundFloatToInt(label->getFont().getHeight()));
// Rectangle labelBounds = label->getBounds();
float x, y, w, h;
petalPath.getBounds(x, y, w, h);
Rectangle petalBounds(roundFloatToInt(x), roundFloatToInt(y), roundFloatToInt(w), roundFloatToInt(h));
topLeftCorner.setXY(-x, -y);
setBounds(petalBounds.translated(generalOffsetX, generalOffsetY));
// Then cache the icon image
delete iconImage;
// Get the icon bounds
const Rectangle & iconBounds = getIconBounds(finalAngle);
iconImage = new Image(Image::ARGB, iconBounds.getWidth(), iconBounds.getHeight(), false);
iconImage->clear(0, 0, iconBounds.getWidth(), iconBounds.getHeight(), Colours::transparentWhite);
Graphics g(*iconImage);
icon.drawWithin(g, 0, 0, iconBounds.getWidth(), iconBounds.getHeight(), RectanglePlacement(RectanglePlacement::fillDestination | RectanglePlacement::centred | RectanglePlacement::stretchToFit));
iconTopLeftCorner.setXY((float)iconBounds.getX(), (float)iconBounds.getY());
iconImage->multiplyAllAlphas(iconAlpha);
}
void CircularMenu::MenuPetal::setCircleRadius(const float radius)
{
// We need to create the path for this petal now
petalPath.clear();
Path workingPath;
workingPath.addPieSegment(0, 0, 2*radius, 2*radius, -surfaceAngle / 2 + Pi*0.5f, surfaceAngle/2 + Pi*0.5f, 0.1f);
// workingPath.addRectangle(0, 0, radius, radius);
petalPath = workingPath.createPathWithRoundedCorners(radius * 0.2f);
// petalPath.applyTransform(AffineTransform::translation(-radius, -radius));
// Ok, done
petalRadius = radius;
}
inline float squareDistanceBetween(const Point & a, const Point & b)
{
return (a.getX() - b.getX())* (a.getX() - b.getX()) + (a.getY() - b.getY()) * (a.getY() - b.getY());
}
const Rectangle CircularMenu::MenuPetal::getIconBounds(const float angle) const
{
// What is the biggest dimension for this drawable ?
float ix, iy, iw, ih;
icon.getBounds(ix, iy, iw, ih);
// Compute the maximum height, once we know the aspect ratio for the drawable
double ar = iw / ih;
double lambda = 0.5f * cos(surfaceAngle/2) + ar;
double maximumHeight = (double)petalRadius / lambda * cos (atan(0.5f / lambda));
// Once we have the maximum height, we can deduce the maximum width
double maximumWidth = maximumHeight * ar;
double minHorizontalPositionForIcon = cos(surfaceAngle / 2) * maximumHeight / 2;
double minVerticalPositionForIcon = (float)petalRadius - maximumHeight/2;
// Need to rotate this center point too
float x = (float)(minHorizontalPositionForIcon + maximumWidth / 2);
float y = 0;
AffineTransform::rotation(angle == -1 ? currentAngle : angle).transformPoint(x, y);
// Because we always draw the icons straight up, the bounding rectangle
// could overflow the circle limit
// So check this case, and deduce the size factor
double sizeFactor = 0.9f;
// Find the icon bottom left corner position
Point blc(x - (float)maximumWidth / 2, y + (float)maximumHeight / 2);
Point tlc(x - (float)maximumWidth / 2, y - (float)maximumHeight / 2);
Point trc(x + (float)maximumWidth / 2, y - (float)maximumHeight / 2);
Point brc(x + (float)maximumWidth / 2, y + (float)maximumHeight / 2);
// Check if any point is outside the circle
float squareRadius = petalRadius * petalRadius;
Point center(0, 0);
float distance = squareDistanceBetween(center, blc);
float maxDistance = distance;
distance = squareDistanceBetween(center, brc);
maxDistance = jmax(maxDistance, distance);
distance = squareDistanceBetween(center, trc);
maxDistance = jmax(maxDistance, distance);
distance = squareDistanceBetween(center, tlc);
maxDistance = jmax(maxDistance, distance);
if (maxDistance * sizeFactor * sizeFactor > squareRadius)
{
// Need to resize the value to match the given distance
sizeFactor *= petalRadius / sqrt(maxDistance);
}
// And finally compute the icon bounds
return Rectangle(roundDoubleToInt(x + topLeftCorner.getX() - maximumWidth / 2 + maximumWidth * (1.0f - sizeFactor) * 0.5f + petalRadius), roundDoubleToInt(y + topLeftCorner.getY() + petalRadius - maximumHeight / 2 + maximumHeight * (1.0f - sizeFactor) * 0.5f), roundDoubleToInt(maximumWidth * sizeFactor), roundDoubleToInt(maximumHeight * sizeFactor));
}
bool CircularMenu::MenuPetal::hitTest(int x, int y)
{
// Check if we are inside the petal area
return petalPath.contains(x - topLeftCorner.getX(), y - topLeftCorner.getY());
}
void CircularMenu::MenuPetal::paint(Graphics& g)
{
//g.fillAll(Colours::grey);
float x, y, w, h;
petalPath.getBounds(x, y, w, h);
Rectangle petalBounds(roundFloatToInt(0), roundFloatToInt(0), roundFloatToInt(w), roundFloatToInt(h));
x = petalRadius * 0.1f; y = 0;
AffineTransform rotationUsed = AffineTransform::rotation(currentAngle);
rotationUsed.transformPoint(x, y);
Point gradientPoint1(x + topLeftCorner.getX() + petalRadius, y + topLeftCorner.getY() + petalRadius);
x = petalRadius * 0.95f; y = 0;
rotationUsed.transformPoint(x, y);
Point gradientPoint2(x + topLeftCorner.getX() + petalRadius, y + topLeftCorner.getY() + petalRadius);
// The hardest part, I guess
Colour workingColour = baseColour;
GradientBrush gradient_2 (workingColour.withMultipliedAlpha(0.16f),
gradientPoint1.getX(), gradientPoint1.getY(),
workingColour.withMultipliedAlpha(1.0f),
gradientPoint2.getX(), gradientPoint2.getY(),
false);
g.setBrush (&gradient_2);
// Fill the petal path
Path workingPath(petalPath);
workingPath.applyTransform(AffineTransform::translation(topLeftCorner.getX(), topLeftCorner.getY()));
g.fillPath(workingPath);
// And the outline too
// g.setColour (Colour (0x661b1b1b));
workingColour = workingColour.darker();
GradientBrush gradient_3 (workingColour.withMultipliedAlpha(0.04f),
gradientPoint1.getX(), gradientPoint1.getY(),
workingColour.withMultipliedAlpha(1.0f),
gradientPoint2.getX(), gradientPoint2.getY(),
false);
g.setBrush (&gradient_3);
g.strokePath (workingPath, PathStrokeType (1.0000f));
// Then draw the drawable too
// This is a little bit more complex here
// We reduce the icon a little bit too to give free space on it (so it's more pleasant)
g.drawImageAt(iconImage, roundFloatToInt(iconTopLeftCorner.getX()), roundFloatToInt(iconTopLeftCorner.getY()));
}
void CircularMenu::MenuPetal::rotateAround(const float _x, const float _y, const float angle)
{
// Undo the previous rotation
petalPath.applyTransform(AffineTransform::rotation(-currentAngle, pivot.getX(), pivot.getY()));
// Then rotate
pivot.setXY(_x - generalOffsetX, _y - generalOffsetY);
currentAngle = angle;
petalPath.applyTransform(AffineTransform::rotation(currentAngle, pivot.getX(), pivot.getY()));
{
float x, y, w, h;
petalPath.getBounds(x, y, w, h);
Rectangle petalBounds(roundFloatToInt(x), roundFloatToInt(y), roundFloatToInt(w), roundFloatToInt(h));
topLeftCorner.setXY(-x, -y);
setBounds(petalBounds.translated(generalOffsetX, generalOffsetY));
const Rectangle & iconBounds = getIconBounds(angle);
iconTopLeftCorner.setXY((float)iconBounds.getX(), (float)iconBounds.getY());
}
}
void CircularMenu::MenuPetal::mouseUp(const MouseEvent& e)
{
// Dismiss the parent modal loop
CircularMenuWindow * window = dynamic_cast<CircularMenuWindow*>(getParentComponent());
if (window)
window->foldPetalsAndExit(itemID);
else getParentComponent()->exitModalState(itemID);
}
void CircularMenu::MenuPetal::mouseEnter(const MouseEvent& e)
{
makeSelected(true);
}
void CircularMenu::MenuPetal::mouseExit(const MouseEvent& e)
{
makeSelected(false);
}
void CircularMenu::MenuPetal::setGeneralOffset(const int x, const int y)
{
generalOffsetX = x;
generalOffsetY = y;
}
void CircularMenu::MenuPetal::makeSelected(const bool shouldBeSelected)
{
if (!isPreSelected && shouldBeSelected)
{
baseColour = baseColour.withAlpha(1.0f);
if (iconImage) iconImage->multiplyAllAlphas(1.0f/iconAlpha);
repaint();
isPreSelected = true;
}
else if (isPreSelected && !shouldBeSelected)
{
baseColour = baseColour.withAlpha(0.4f);
if (iconImage) iconImage->multiplyAllAlphas(iconAlpha);
repaint();
isPreSelected = false;
}
}
bool CircularMenu::MenuPetal::isSelected() { return isPreSelected; }
//===================================== Menu
bool CircularMenu::addItem(const int itemResultId, Drawable* iconToUse, const String& itemText)
{
if (!iconToUse) return false;
if (itemsId.contains(itemResultId)) return false;
itemsId.add(itemResultId);
icons.add(iconToUse);
itemTexts.add(itemText);
return true;
}
int CircularMenu::show(const int minimumRadius)
{
// Compute the radius
int x, y;
Desktop::getMousePosition (x, y);
return showAt(x, y, minimumRadius);
}
Component* CircularMenu::createMenuComponent (const int x, const int y, const int w, const int h, ApplicationCommandManager** managerOfChosenCommand, Component* const componentAttachedTo) throw()
{
CircularMenuWindow * window = new CircularMenuWindow(x, y, w, h, icons, itemsId, itemTexts, managerOfChosenCommand, componentAttachedTo);
if (window) window->setVisible(true);
return window;
}
int CircularMenu::showAt(const int screenX, const int screenY, const int minimumRadius)
{
// Save the previously focused item to restore after this call
Component* const prevFocused = Component::getCurrentlyFocusedComponent();
// Also save the top level component
Component* const prevTopLevel = (prevFocused != 0) ? prevFocused->getTopLevelComponent() : 0;
// We want to be informed if the previous focus component is being deleted
ComponentDeletionWatcher* deletionChecker1 = 0;
if (prevFocused != 0) deletionChecker1 = new ComponentDeletionWatcher (prevFocused);
ComponentDeletionWatcher* deletionChecker2 = 0;
if (prevTopLevel != 0) deletionChecker2 = new ComponentDeletionWatcher (prevTopLevel);
bool wasHiddenBecauseOfAppChange = false;
int result = 0;
ApplicationCommandManager* managerOfChosenCommand = 0;
Component* const popupComp = createMenuComponent (screenX - minimumRadius, screenY - minimumRadius, minimumRadius * 2 , minimumRadius * 2,
&managerOfChosenCommand,
0);
if (popupComp != 0)
{
popupComp->enterModalState (false);
popupComp->toFront (false); // need to do this after making it modal, or it could
// be stuck behind other comps that are already modal..
result = popupComp->runModalLoop();
delete popupComp;
if (! wasHiddenBecauseOfAppChange)
{
if (deletionChecker2 != 0 && ! deletionChecker2->hasBeenDeleted())
prevTopLevel->toFront (true);
if (deletionChecker1 != 0 && ! deletionChecker1->hasBeenDeleted())
prevFocused->grabKeyboardFocus();
}
}
delete deletionChecker1;
delete deletionChecker2;
if (managerOfChosenCommand != 0 && result != 0)
{
ApplicationCommandTarget::InvocationInfo info (result);
info.invocationMethod = ApplicationCommandTarget::InvocationInfo::fromMenu;
managerOfChosenCommand->invoke (info, true);
}
return result;
}
}