mirror of
https://github.com/juce-framework/JUCE.git
synced 2026-01-10 23:44:24 +00:00
428 lines
16 KiB
C++
428 lines
16 KiB
C++
|
|
#ifndef BLOCKCOMPONENTS_H_INCLUDED
|
|
#define BLOCKCOMPONENTS_H_INCLUDED
|
|
|
|
#include "../JuceLibraryCode/JuceHeader.h"
|
|
|
|
//==============================================================================
|
|
/**
|
|
Base class that renders a Block on the screen
|
|
*/
|
|
class BlockComponent : public Component,
|
|
public SettableTooltipClient,
|
|
private TouchSurface::Listener,
|
|
private ControlButton::Listener,
|
|
private Timer
|
|
{
|
|
public:
|
|
/** Constructor */
|
|
BlockComponent (Block::Ptr blockToUse)
|
|
: block (blockToUse)
|
|
{
|
|
updateStatsAndTooltip();
|
|
|
|
// Register BlockComponent as a listener to the touch surface
|
|
if (auto touchSurface = block->getTouchSurface())
|
|
touchSurface->addListener (this);
|
|
|
|
// Register BlockComponent as a listener to any buttons
|
|
for (auto button : block->getButtons())
|
|
button->addListener (this);
|
|
|
|
// If this is a Lightpad then set the grid program to be blank
|
|
if (auto grid = block->getLEDGrid())
|
|
grid->setProgram (new BitmapLEDProgram(*grid));
|
|
|
|
// If this is a Lightpad then redraw it at 25Hz
|
|
if (block->getType() == Block::lightPadBlock)
|
|
startTimerHz (25);
|
|
}
|
|
|
|
/** Destructor */
|
|
~BlockComponent()
|
|
{
|
|
// Remove any listeners
|
|
if (auto touchSurface = block->getTouchSurface())
|
|
touchSurface->removeListener (this);
|
|
|
|
for (auto button : block->getButtons())
|
|
button->removeListener (this);
|
|
}
|
|
|
|
/** Called periodically to update the tooltip with inforamtion about the Block */
|
|
void updateStatsAndTooltip()
|
|
{
|
|
// Get the battery level of this Block and inform any subclasses
|
|
const float batteryLevel = block->getBatteryLevel();
|
|
handleBatteryLevelUpdate (batteryLevel);
|
|
|
|
// Format the tooltip string
|
|
const String ttString = "Name = " + block->getDeviceDescription() + "\n"
|
|
+ "UID = " + String (block->uid) + "\n"
|
|
+ "Serial number = " + block->serialNumber + "\n"
|
|
+ "Battery level = " + String ((int) (batteryLevel * 100)) + "%"
|
|
+ (block->isBatteryCharging() ? "++" : "--");
|
|
|
|
// Update the tooltip string if it has changed
|
|
if (ttString != getTooltip())
|
|
setTooltip (ttString);
|
|
}
|
|
|
|
/** Subclasses should override this to paint the Block object on the screen */
|
|
virtual void paint (Graphics&) override = 0;
|
|
|
|
/** Subclasses can override this to receive button down events from the Block */
|
|
virtual void handleButtonPressed (ControlButton::ButtonFunction, uint32) {}
|
|
|
|
/** Subclasses can override this to receive button up events from the Block */
|
|
virtual void handleButtonReleased (ControlButton::ButtonFunction, uint32) {}
|
|
|
|
/** Subclasses can override this to receive touch events from the Block */
|
|
virtual void handleTouchChange (TouchSurface::Touch) {}
|
|
|
|
/** Subclasses can override this to battery level updates from the Block */
|
|
virtual void handleBatteryLevelUpdate (float) {}
|
|
|
|
/** The Block object that this class represents */
|
|
Block::Ptr block;
|
|
|
|
//==============================================================================
|
|
/** Returns an integer index corresponding to a physical position on the hardware
|
|
for each type of Control Block. */
|
|
static int controlButtonFunctionToIndex (ControlButton::ButtonFunction f)
|
|
{
|
|
static std::initializer_list<ControlButton::ButtonFunction> map[] =
|
|
{{ControlButton::mode, ControlButton::button0},
|
|
{ControlButton::volume, ControlButton::button1},
|
|
{ControlButton::scale, ControlButton::button2, ControlButton::click},
|
|
{ControlButton::chord, ControlButton::button3, ControlButton::snap},
|
|
{ControlButton::arp,ControlButton:: button4, ControlButton::back},
|
|
{ControlButton::sustain, ControlButton::button5, ControlButton::playOrPause},
|
|
{ControlButton::octave, ControlButton::button6, ControlButton::record},
|
|
{ControlButton::love, ControlButton::button7, ControlButton::learn},
|
|
{ControlButton::up},
|
|
{ControlButton::down}};
|
|
|
|
for (size_t i = 0; i < (sizeof (map) / sizeof (map[0])); ++i)
|
|
if (std::find (map[i].begin(), map[i].end(), f) != map[i].end())
|
|
return static_cast<int> (i);
|
|
|
|
return -1;
|
|
}
|
|
|
|
private:
|
|
/** Used to call repaint() periodically */
|
|
void timerCallback() override { repaint(); }
|
|
|
|
/** Overridden from TouchSurface::Listener */
|
|
void touchChanged (TouchSurface&, const TouchSurface::Touch& t) override { handleTouchChange (t); }
|
|
|
|
/** Overridden from ControlButton::Listener */
|
|
void buttonPressed (ControlButton& b, Block::Timestamp t) override { handleButtonPressed (b.getType(), t); }
|
|
|
|
/** Overridden from ControlButton::Listener */
|
|
void buttonReleased (ControlButton& b, Block::Timestamp t) override { handleButtonReleased (b.getType(), t); }
|
|
|
|
//==============================================================================
|
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (BlockComponent)
|
|
};
|
|
|
|
/**
|
|
Class that renders a Lightpad on the screen
|
|
*/
|
|
class LightpadComponent : public BlockComponent
|
|
{
|
|
public:
|
|
LightpadComponent (Block::Ptr blockToUse)
|
|
: BlockComponent (blockToUse)
|
|
{
|
|
}
|
|
|
|
void paint (Graphics& g) override
|
|
{
|
|
auto r = getLocalBounds().toFloat();
|
|
|
|
// clip the drawing area to only draw in the block area
|
|
{
|
|
Path clipArea;
|
|
clipArea.addRoundedRectangle (r, r.getWidth() / 20.0f);
|
|
|
|
g.reduceClipRegion (clipArea);
|
|
}
|
|
|
|
// Fill a black square for the Lightpad
|
|
g.fillAll (Colours::black);
|
|
|
|
// size ration between physical and on-screen blocks
|
|
const Point<float> ratio (r.getWidth() / block->getWidth(),
|
|
r.getHeight() / block->getHeight());
|
|
const float maxCircleSize = block->getWidth() / 3.0f;
|
|
|
|
// iterate over the list of current touches and draw them on the onscreen Block
|
|
for (auto touch : touches)
|
|
{
|
|
const float circleSize = touch.touch.z * maxCircleSize;
|
|
const Point<float> touchPosition = Point<float> (touch.touch.x, touch.touch.y);
|
|
|
|
const Colour c = colourArray[touch.touch.index];
|
|
const Rectangle<float> blob =
|
|
(Rectangle<float> (circleSize, circleSize).withCentre (touchPosition)) * ratio;
|
|
|
|
const ColourGradient cg = ColourGradient (colourArray[touch.touch.index], blob.getCentreX(), blob.getCentreY(),
|
|
Colours::transparentBlack, blob.getRight(), blob.getBottom(),
|
|
true);
|
|
|
|
g.setGradientFill (cg);
|
|
g.fillEllipse (blob);
|
|
}
|
|
}
|
|
|
|
void handleTouchChange (TouchSurface::Touch touch) override { touches.updateTouch (touch); }
|
|
|
|
private:
|
|
/** An Array of colours to use for touches */
|
|
Array<Colour> colourArray = { Colours::red, Colours::blue, Colours::green,
|
|
Colours::yellow, Colours::white, Colours::hotpink,
|
|
Colours::mediumpurple };
|
|
|
|
/** A list of current Touch events */
|
|
TouchList<TouchSurface::Touch> touches;
|
|
|
|
//==============================================================================
|
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LightpadComponent)
|
|
};
|
|
|
|
/**
|
|
Class that renders a Control Block on the screen
|
|
*/
|
|
class ControlBlockComponent : public BlockComponent
|
|
{
|
|
public:
|
|
ControlBlockComponent (Block::Ptr blockToUse)
|
|
: BlockComponent (blockToUse),
|
|
numLeds (block->getLEDRow()->getNumLEDs())
|
|
{
|
|
addAndMakeVisible (roundedRectangleButton);
|
|
|
|
// Display the battery level on the LEDRow
|
|
int numLedsToTurnOn = static_cast<int> (static_cast<float> (numLeds) * block->getBatteryLevel());
|
|
|
|
// add LEDs
|
|
LEDComponent* ledComponent;
|
|
for (int i = 0; i < numLeds; ++i)
|
|
{
|
|
ledComponent = new LEDComponent();
|
|
ledComponent->setOnState (i < numLedsToTurnOn);
|
|
|
|
addAndMakeVisible (leds.add (ledComponent));
|
|
}
|
|
|
|
previousNumLedsOn = numLedsToTurnOn;
|
|
|
|
// add buttons
|
|
for (int i = 0; i < 8; ++i)
|
|
addAndMakeVisible (circleButtons[i]);
|
|
}
|
|
|
|
void resized() override
|
|
{
|
|
const auto r = getLocalBounds().reduced (10);
|
|
|
|
const int rowHeight = r.getHeight() / 5;
|
|
const int ledWidth = (r.getWidth() - 70) / numLeds;
|
|
const int buttonWidth = (r.getWidth() - 40) / 5;
|
|
|
|
auto row = r;
|
|
|
|
auto ledRow = row.removeFromTop (rowHeight) .withSizeKeepingCentre (r.getWidth(), ledWidth);
|
|
auto buttonRow1 = row.removeFromTop (rowHeight * 2).withSizeKeepingCentre (r.getWidth(), buttonWidth);
|
|
auto buttonRow2 = row.removeFromTop (rowHeight * 2).withSizeKeepingCentre (r.getWidth(), buttonWidth);
|
|
|
|
for (int i = 0; i < numLeds; ++i)
|
|
{
|
|
leds.getUnchecked (i)->setBounds (ledRow.removeFromLeft (ledWidth).reduced (2));
|
|
ledRow.removeFromLeft (5);
|
|
}
|
|
|
|
for (int i = 0; i < 5; ++i)
|
|
{
|
|
circleButtons[i].setBounds (buttonRow1.removeFromLeft (buttonWidth).reduced (2));
|
|
buttonRow1.removeFromLeft (10);
|
|
}
|
|
|
|
for (int i = 5; i < 8; ++i)
|
|
{
|
|
circleButtons[i].setBounds (buttonRow2.removeFromLeft (buttonWidth).reduced (2));
|
|
buttonRow2.removeFromLeft (10);
|
|
}
|
|
|
|
roundedRectangleButton.setBounds (buttonRow2);
|
|
}
|
|
|
|
void paint (Graphics& g) override
|
|
{
|
|
const auto r = getLocalBounds().toFloat();
|
|
|
|
// Fill a black rectangle for the Control Block
|
|
g.setColour (Colours::black);
|
|
g.fillRoundedRectangle (r, r.getWidth() / 20.0f);
|
|
}
|
|
|
|
void handleButtonPressed (ControlButton::ButtonFunction function, uint32) override
|
|
{
|
|
displayButtonInteraction (controlButtonFunctionToIndex (function), true);
|
|
}
|
|
|
|
void handleButtonReleased (ControlButton::ButtonFunction function, uint32) override
|
|
{
|
|
displayButtonInteraction (controlButtonFunctionToIndex (function), false);
|
|
}
|
|
|
|
void handleBatteryLevelUpdate (float batteryLevel) override
|
|
{
|
|
// Update the number of LEDs that are on to represent the battery level
|
|
int numLedsOn = static_cast<int> (static_cast<float> (numLeds) * batteryLevel);
|
|
|
|
if (numLedsOn != previousNumLedsOn)
|
|
for (int i = 0; i < numLeds; ++i)
|
|
leds.getUnchecked (i)->setOnState (i < numLedsOn);
|
|
|
|
previousNumLedsOn = numLedsOn;
|
|
repaint();
|
|
}
|
|
|
|
private:
|
|
/**
|
|
Base class that renders a Control Block button
|
|
*/
|
|
struct ControlBlockSubComponent : public Component,
|
|
public TooltipClient
|
|
{
|
|
ControlBlockSubComponent (Colour componentColourToUse)
|
|
: componentColour (componentColourToUse),
|
|
onState (false)
|
|
{}
|
|
|
|
/** Subclasses should override this to paint the button on the screen */
|
|
virtual void paint (Graphics&) override = 0;
|
|
|
|
/** Sets the colour of the button */
|
|
void setColour (Colour c) { componentColour = c; }
|
|
|
|
/** Sets the on state of the button */
|
|
void setOnState (bool isOn)
|
|
{
|
|
onState = isOn;
|
|
repaint();
|
|
}
|
|
|
|
/** Returns the Control Block tooltip */
|
|
String getTooltip() override
|
|
{
|
|
for (Component* comp = this; comp != nullptr; comp = comp->getParentComponent())
|
|
if (SettableTooltipClient* sttc = dynamic_cast<SettableTooltipClient*> (comp))
|
|
return sttc->getTooltip();
|
|
|
|
return String();
|
|
}
|
|
|
|
//==============================================================================
|
|
Colour componentColour;
|
|
bool onState;
|
|
|
|
//==============================================================================
|
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ControlBlockSubComponent)
|
|
};
|
|
|
|
/**
|
|
Class that renders a Control Block LED on the screen
|
|
*/
|
|
struct LEDComponent : public ControlBlockSubComponent
|
|
{
|
|
LEDComponent() : ControlBlockSubComponent (Colours::green) {}
|
|
|
|
void paint (Graphics& g) override
|
|
{
|
|
g.setColour (componentColour.withAlpha (onState ? 1.0f : 0.2f));
|
|
g.fillEllipse (getLocalBounds().toFloat());
|
|
}
|
|
};
|
|
|
|
/**
|
|
Class that renders a Control Block single circular button on the screen
|
|
*/
|
|
struct CircleButtonComponent : public ControlBlockSubComponent
|
|
{
|
|
CircleButtonComponent() : ControlBlockSubComponent (Colours::blue) {}
|
|
|
|
void paint (Graphics& g) override
|
|
{
|
|
g.setColour (componentColour.withAlpha (onState ? 1.0f : 0.2f));
|
|
g.fillEllipse (getLocalBounds().toFloat());
|
|
}
|
|
};
|
|
|
|
/**
|
|
Class that renders a Control Block rounded rectangular button containing two buttons
|
|
on the screen
|
|
*/
|
|
struct RoundedRectangleButtonComponent : public ControlBlockSubComponent
|
|
{
|
|
RoundedRectangleButtonComponent() : ControlBlockSubComponent (Colours::blue) {}
|
|
|
|
void paint (Graphics& g) override
|
|
{
|
|
const auto r = getLocalBounds().toFloat();
|
|
|
|
|
|
g.setColour (componentColour.withAlpha (0.2f));
|
|
g.fillRoundedRectangle (r.toFloat(), 20.0f);
|
|
g.setColour (componentColour.withAlpha (1.0f));
|
|
|
|
// is a button pressed?
|
|
if (doubleButtonOnState[0] || doubleButtonOnState[1])
|
|
{
|
|
const float semiButtonWidth = r.getWidth() / 2.0f;
|
|
const auto semiButtonBounds = r.withWidth (semiButtonWidth)
|
|
.withX (doubleButtonOnState[1] ? semiButtonWidth : 0)
|
|
.reduced (5.0f, 2.0f);
|
|
|
|
|
|
g.fillEllipse (semiButtonBounds);
|
|
}
|
|
}
|
|
|
|
void setPressedState (bool isPressed, int button)
|
|
{
|
|
doubleButtonOnState[button] = isPressed;
|
|
repaint();
|
|
}
|
|
|
|
private:
|
|
bool doubleButtonOnState[2] = {false, false};
|
|
};
|
|
|
|
/** Displays a button press or release interaction for a button at a given index */
|
|
void displayButtonInteraction (int buttonIndex, bool isPressed)
|
|
{
|
|
if (! isPositiveAndBelow (buttonIndex, 10))
|
|
return;
|
|
|
|
if (buttonIndex >= 8)
|
|
roundedRectangleButton.setPressedState (isPressed, buttonIndex == 8);
|
|
else
|
|
circleButtons[buttonIndex].setOnState (isPressed);
|
|
}
|
|
|
|
//==============================================================================
|
|
int numLeds;
|
|
OwnedArray<LEDComponent> leds;
|
|
CircleButtonComponent circleButtons[8];
|
|
RoundedRectangleButtonComponent roundedRectangleButton;
|
|
int previousNumLedsOn;
|
|
|
|
//==============================================================================
|
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ControlBlockComponent)
|
|
};
|
|
|
|
#endif // BLOCKCOMPONENTS_H_INCLUDED
|