mirror of
https://codeberg.org/armin/justasample.git
synced 2026-09-01 12:20:48 +02:00
377 lines
No EOL
13 KiB
C++
377 lines
No EOL
13 KiB
C++
/*
|
|
==============================================================================
|
|
|
|
Buttons.h
|
|
Created: 30 Jul 2024 11:30:24pm
|
|
Author: binya
|
|
|
|
==============================================================================
|
|
*/
|
|
|
|
#pragma once
|
|
|
|
#include <JuceHeader.h>
|
|
|
|
#include "../Utilities/ComponentUtils.h"
|
|
|
|
/** Simple wrapper around JUCE's ShapeButton for transparent disabled styling */
|
|
class CustomShapeButton final : public juce::ShapeButton, public CustomHelpTextProvider, public EnabledMouseCursor
|
|
{
|
|
public:
|
|
explicit CustomShapeButton(juce::Colour buttonColor, const juce::Path& shape = {}, CustomHelpTextDisplay* helpTextDisplay = nullptr) :
|
|
ShapeButton("", buttonColor, buttonColor, buttonColor),
|
|
CustomHelpTextProvider(this, helpTextDisplay),
|
|
color(buttonColor)
|
|
{
|
|
ShapeButton::setShape(shape, false, true, false);
|
|
}
|
|
|
|
explicit CustomShapeButton(const juce::Path& shape = {}, CustomHelpTextDisplay* helpTextDisplay = nullptr) :
|
|
CustomShapeButton(juce::Colours::transparentBlack, shape, helpTextDisplay)
|
|
{
|
|
}
|
|
|
|
void setColor(juce::Colour buttonColor)
|
|
{
|
|
color = buttonColor;
|
|
setColours(buttonColor, buttonColor, buttonColor);
|
|
enablementChanged();
|
|
}
|
|
|
|
void setShape(const juce::Path& newShape)
|
|
{
|
|
ShapeButton::setShape(newShape, false, true, false);
|
|
repaint();
|
|
}
|
|
|
|
private:
|
|
void enablementChanged() override
|
|
{
|
|
auto adjustedColor = color.withMultipliedAlpha(isEnabled() ? 1.f : 0.5f);
|
|
setColours(adjustedColor, adjustedColor, adjustedColor);
|
|
|
|
EnabledMouseCursor::enablementChanged(*this);
|
|
|
|
repaint();
|
|
}
|
|
|
|
juce::Colour color;
|
|
};
|
|
|
|
/** A nicely styled button that can be toggled on and off. An owner button can be set to "own" this button,
|
|
meaning this button is only on when the owner button is on.
|
|
*/
|
|
class CustomToggleableButton final : public juce::Button, public juce::Button::Listener, public CustomHelpTextProvider, public EnabledMouseCursor
|
|
{
|
|
public:
|
|
/** Create a new button with the given colors.
|
|
altStyle keeps the text the same while changing the background, onBackground removes the shadow and sets a background when on
|
|
*/
|
|
CustomToggleableButton(juce::Colour offColor, juce::Colour onColor, bool altStyle = false, bool useOnBackground = false, CustomHelpTextDisplay* helpTextDisplay = nullptr) :
|
|
Button(""), CustomHelpTextProvider(this, helpTextDisplay),
|
|
offColor(offColor), onColor(onColor), altStyle(altStyle), onBackground(useOnBackground)
|
|
{
|
|
setClickingTogglesState(true);
|
|
}
|
|
|
|
CustomToggleableButton(bool altStyle = false, bool useOnBackground = false, CustomHelpTextDisplay* helpTextDisplay = nullptr) :
|
|
CustomToggleableButton(juce::Colours::transparentBlack, juce::Colours::transparentBlack, altStyle, useOnBackground, helpTextDisplay)
|
|
{
|
|
}
|
|
|
|
~CustomToggleableButton() override
|
|
{
|
|
if (ownerButton)
|
|
ownerButton->removeListener(this);
|
|
}
|
|
|
|
void setColors(juce::Colour off, juce::Colour on, juce::Colour shadow = defaultTheme.dark.withAlpha(0.25f))
|
|
{
|
|
offColor = off;
|
|
onColor = on;
|
|
onShadow.setColor(shadow);
|
|
|
|
repaint();
|
|
}
|
|
|
|
/** An owner button is a button that will be toggled on when this button is toggled but can also be toggled independently. */
|
|
void setOwnerButton(Button* owner)
|
|
{
|
|
ownerButton = owner;
|
|
owner->addListener(this);
|
|
if (!owner->getToggleState())
|
|
setClickingTogglesState(false);
|
|
}
|
|
|
|
/** Use x, y, width, height to set the left, top, right, and bottom bounds of the button */
|
|
void setBorder(float width, float rounding = 0.f, bool roundTopLeft = true, bool roundTopRight = true, bool roundBottomLeft = true, bool roundBottomRight = true)
|
|
{
|
|
borderWidth = width;
|
|
borderRounding = rounding;
|
|
topLeft = roundTopLeft;
|
|
topRight = roundTopRight;
|
|
bottomLeft = roundBottomLeft;
|
|
bottomRight = roundBottomRight;
|
|
}
|
|
|
|
void setPadding(float amount)
|
|
{
|
|
setPadding(amount, amount, amount, amount);
|
|
}
|
|
|
|
void setPadding(float paddingLeft, float paddingRight, float paddingTop, float paddingBottom)
|
|
{
|
|
padLeft = paddingLeft;
|
|
padRight = paddingRight;
|
|
padTop = paddingTop;
|
|
padBottom = paddingBottom;
|
|
}
|
|
|
|
void useShape(const juce::Path& shapePath, const juce::Path& offShapePath = {}, juce::Justification justification = juce::Justification::centred)
|
|
{
|
|
shape = shapePath;
|
|
offShape = offShapePath;
|
|
shapeJustification = justification;
|
|
}
|
|
|
|
private:
|
|
void buttonStateChanged(Button* button) override
|
|
{
|
|
if (button == ownerButton)
|
|
{
|
|
setClickingTogglesState(ownerButton->getToggleState());
|
|
if (getToggleState())
|
|
setState(ownerButton->getState());
|
|
repaint();
|
|
}
|
|
}
|
|
|
|
void buttonClicked(Button*) override {}
|
|
|
|
void clicked() override
|
|
{
|
|
if (ownerButton && !ownerButton->getToggleState())
|
|
{
|
|
ownerButton->setToggleState(true, juce::sendNotificationSync);
|
|
ownerButton->setState(buttonNormal);
|
|
setToggleState(true, juce::dontSendNotification);
|
|
repaint();
|
|
}
|
|
}
|
|
|
|
void buttonStateChanged() override
|
|
{
|
|
if (ownerButton && !ownerButton->getToggleState())
|
|
{
|
|
ownerButton->setState(getState());
|
|
ownerButton->repaint();
|
|
}
|
|
}
|
|
|
|
void paintButton(juce::Graphics& g, bool /*shouldDrawButtonAsHighlighted*/, bool shouldDrawButtonAsDown) override
|
|
{
|
|
bool toggledOn = getToggleState() && (!ownerButton || ownerButton->getToggleState());
|
|
bool drawAsOn = toggledOn != shouldDrawButtonAsDown;
|
|
|
|
auto bounds = getLocalBounds().toFloat();
|
|
|
|
if (drawAsOn)
|
|
{
|
|
juce::Path background;
|
|
auto pathBounds = bounds.reduced(borderWidth * 0.33f);
|
|
background.addRoundedRectangle(pathBounds.getX(), pathBounds.getY(), pathBounds.getWidth(), pathBounds.getHeight(),
|
|
borderRounding, borderRounding, topLeft, topRight, bottomLeft, bottomRight);
|
|
|
|
g.setColour(altStyle ? onColor : offColor);
|
|
g.fillPath(background);
|
|
|
|
if (!onBackground)
|
|
onShadow.render(g, background);
|
|
}
|
|
else if (!onBackground)
|
|
{
|
|
juce::Path border;
|
|
auto pathBounds = bounds.reduced(borderWidth * 0.66f);
|
|
border.addRoundedRectangle(pathBounds.getX(), pathBounds.getY(), pathBounds.getWidth(), pathBounds.getHeight(),
|
|
borderRounding, borderRounding, topLeft, topRight, bottomLeft, bottomRight);
|
|
|
|
g.setColour(offColor);
|
|
g.strokePath(border, juce::PathStrokeType(borderWidth));
|
|
}
|
|
|
|
bounds.removeFromLeft(padLeft);
|
|
bounds.removeFromRight(padRight);
|
|
bounds.removeFromTop(padTop);
|
|
bounds.removeFromBottom(padBottom);
|
|
|
|
auto useShape = !drawAsOn && !offShape.isEmpty() ? offShape : shape;
|
|
auto trans = useShape.getTransformToScaleToFit(bounds, true, shapeJustification);
|
|
|
|
g.setColour(drawAsOn && !altStyle ? onColor : offColor);
|
|
if (!useShape.isEmpty())
|
|
{
|
|
g.fillPath(useShape, trans);
|
|
}
|
|
else if (getButtonText().isNotEmpty())
|
|
{
|
|
g.setFont(getInterBold().withHeight(getHeight() * 0.381f));
|
|
g.drawText(getButtonText(), getLocalBounds(), juce::Justification::centred);
|
|
}
|
|
|
|
if (!isEnabled())
|
|
{
|
|
g.setColour(findColour(Colors::backgroundColorId, true).withAlpha(0.5f));
|
|
g.fillRect(getLocalBounds());
|
|
}
|
|
}
|
|
|
|
void enablementChanged() override
|
|
{
|
|
EnabledMouseCursor::enablementChanged(*this);
|
|
|
|
repaint();
|
|
}
|
|
|
|
//==============================================================================
|
|
juce::Colour offColor, onColor;
|
|
bool altStyle{ false }, onBackground{ false };
|
|
|
|
float borderWidth{ 1.f };
|
|
float borderRounding{ 0.f };
|
|
bool topLeft{ true }, topRight{ true }, bottomLeft{ true }, bottomRight{ true };
|
|
float padLeft{ 0.f }, padRight{ 0.f }, padTop{ 0.f }, padBottom{ 0.f };
|
|
|
|
juce::Path shape, offShape;
|
|
juce::Justification shapeJustification{ juce::Justification::centred };
|
|
|
|
melatonin::InnerShadow onShadow{ defaultTheme.dark.withAlpha(0.25f), 3, {0, 2} };
|
|
|
|
Button* ownerButton{ nullptr };
|
|
|
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CustomToggleableButton)
|
|
};
|
|
|
|
/** Two buttons that act as a nicely styled toggle. */
|
|
class CustomChoiceButton final : public CustomComponent
|
|
{
|
|
public:
|
|
CustomChoiceButton(const APVTS& apvts, const juce::Identifier& parameter, juce::Colour offColor, juce::Colour onColor,
|
|
const juce::String& firstButtonText, const juce::String& secondButtonText, CustomHelpTextDisplay* helpTextDisplay = nullptr) :
|
|
CustomComponent(helpTextDisplay),
|
|
firstButton(offColor, onColor), secondButton(offColor, onColor),
|
|
choiceAttachment(*apvts.getParameter(parameter), [this](float newValue) { valueChanged(newValue); }, apvts.undoManager),
|
|
offColor(offColor), onColor(onColor)
|
|
{
|
|
setPaintingIsUnclipped(true); // Stop border from clipping
|
|
|
|
firstButton.setButtonText(firstButtonText);
|
|
secondButton.setButtonText(secondButtonText);
|
|
|
|
firstButton.onClick = [this] { choiceAttachment.setValueAsCompleteGesture(0.f); };
|
|
secondButton.onClick = [this] { choiceAttachment.setValueAsCompleteGesture(1.f); };
|
|
|
|
firstButton.onStateChange = [this] { secondButton.setState(firstButton.getState()); };
|
|
secondButton.onStateChange = [this] { firstButton.setState(secondButton.getState()); };
|
|
|
|
addAndMakeVisible(firstButton);
|
|
addAndMakeVisible(secondButton);
|
|
|
|
choiceAttachment.sendInitialUpdate();
|
|
}
|
|
|
|
CustomChoiceButton(const APVTS& apvts, const juce::Identifier& parameter, const juce::String& firstButtonText, const juce::String& secondButtonText,
|
|
CustomHelpTextDisplay* helpTextDisplay = nullptr) :
|
|
CustomChoiceButton(apvts, parameter, juce::Colours::transparentBlack, juce::Colours::transparentBlack, firstButtonText, secondButtonText, helpTextDisplay)
|
|
{
|
|
}
|
|
|
|
void setColors(juce::Colour off, juce::Colour on, juce::Colour shadow = defaultTheme.dark.withAlpha(0.25f))
|
|
{
|
|
offColor = off;
|
|
onColor = on;
|
|
|
|
firstButton.setColors(off, on, shadow);
|
|
secondButton.setColors(off, on, shadow);
|
|
|
|
repaint();
|
|
}
|
|
|
|
void setBorder(float width, float rounding = 0.f)
|
|
{
|
|
borderWidth = width;
|
|
borderRounding = rounding;
|
|
repaint();
|
|
}
|
|
|
|
bool getChoice() const
|
|
{
|
|
return secondButton.getToggleState();
|
|
}
|
|
|
|
void setHelpText(const juce::String& firstHelpText, const juce::String& secondHelpText)
|
|
{
|
|
firstButton.setHelpText(firstHelpText);
|
|
secondButton.setHelpText(secondHelpText);
|
|
}
|
|
|
|
juce::String getCustomHelpText() override
|
|
{
|
|
return getChoice() ? secondButton.getHelpText() : firstButton.getHelpText();
|
|
}
|
|
|
|
private:
|
|
void paint(juce::Graphics& g) override
|
|
{
|
|
auto bounds = getLocalBounds().toFloat();
|
|
|
|
firstButton.setBorder(0.f, borderRounding, true, false, true, false);
|
|
secondButton.setBorder(0.f, borderRounding, false, true, false, true);
|
|
|
|
g.setColour(offColor);
|
|
g.drawRoundedRectangle(bounds.reduced(borderWidth / 2.f), borderRounding, borderWidth);
|
|
}
|
|
|
|
void resized() override
|
|
{
|
|
auto bounds = getLocalBounds();
|
|
|
|
firstButton.setBounds(bounds.removeFromLeft(int(bounds.getWidth() / 2.f)));
|
|
secondButton.setBounds(bounds);
|
|
}
|
|
|
|
void valueChanged(float newValue)
|
|
{
|
|
firstButton.setToggleState(newValue < 0.5f, juce::dontSendNotification);
|
|
secondButton.setToggleState(newValue > 0.5f, juce::dontSendNotification);
|
|
|
|
firstButton.setInterceptsMouseClicks(!firstButton.getToggleState(), false);
|
|
secondButton.setInterceptsMouseClicks(!secondButton.getToggleState(), false);
|
|
|
|
sendHelpTextUpdate(false);
|
|
}
|
|
|
|
void enablementChanged() override
|
|
{
|
|
if (!isEnabled())
|
|
{
|
|
firstButton.setInterceptsMouseClicks(false, false);
|
|
secondButton.setInterceptsMouseClicks(false, false);
|
|
}
|
|
else
|
|
{
|
|
choiceAttachment.sendInitialUpdate();
|
|
}
|
|
repaint();
|
|
}
|
|
|
|
//==============================================================================
|
|
CustomToggleableButton firstButton;
|
|
CustomToggleableButton secondButton;
|
|
juce::ParameterAttachment choiceAttachment;
|
|
|
|
juce::Colour offColor, onColor;
|
|
|
|
float borderWidth{ 1.f }, borderRounding{ 0.f };
|
|
|
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(CustomChoiceButton)
|
|
}; |