juce - 自定义组件之SpinBox

SpinBox.h

#pragma once
#include <JuceHeader.h>

class SpinBox : public juce::Component,
    public juce::Button::Listener,
    public juce::TextEditor::Listener
{
public:
    // 构造函数:默认范围 0-100,步长 1,无单位
    SpinBox(float minValue = 0, float maxValue = 100, float step = 1);
    ~SpinBox();

    // 设置当前值
    void setValue(float newValue);
    // 获取当前值
    float getValue() const;

    //设置最小值
    void setMinValue(float newValue);
    //设置最大值
    void setMaxValue(float newValue);
    //设置步长
    void setSteo(float step);

    // 数值变化回调
    //std::function<void(float)> onValueChanged;

    void resized() override;
    void buttonClicked(juce::Button* button) override;
    // 输入框内容变化
    void textEditorTextChanged(juce::TextEditor& editor) override;

    // 新增监听器接口
    class Listener {
    public:
        virtual ~Listener() = default;
        virtual void ValueChanged(SpinBox* spinBox) = 0;
    };

    // 新增注册/注销监听器方法
    void addListener(Listener* listener);
    void removeListener(Listener* listener);

    // 新增监听器列表
private:
    juce::ListenerList<Listener> listeners;

    juce::ScopedPointer<juce::DrawableImage> upImage;
    juce::ScopedPointer<juce::DrawableImage> downImage;
    juce::ScopedPointer<juce::DrawableImage> pressed_upImage;
    juce::ScopedPointer<juce::DrawableImage> pressed_downImage;
    juce::TextEditor valueEditor;     // 数值输入框
    juce::ScopedPointer<juce::DrawableButton> upButton;      // 增加按钮(向上箭头)
    juce::ScopedPointer<juce::DrawableButton> downButton;      // 减少按钮(向下箭头)

    float m_currentValue;         // 当前值
    float m_minValue;             // 最小值
    float m_maxValue;             // 最大值
    float m_step;                 // 步长


};

SpinBox.cpp

#include "SpinBox.h"


SpinBox::SpinBox(float minValue, float maxValue, float step)
{
    m_minValue = minValue;
    m_maxValue = maxValue;
    m_currentValue = m_minValue;
    m_step = step;

    // 设置初始文本
    //valueEditor.setText(juce::String(m_currentValue), juce::dontSendNotification);
    valueEditor.setText(juce::String::formatted("%.2f", m_currentValue), juce::dontSendNotification);
    // 设置文本对齐方式为水平和垂直居中
    valueEditor.setJustification(juce::Justification::centred);
    // 可选:设置字体大小(例如 16px)
    //valueEditor.setFont(juce::Font(18.0f));
    // 可选:移除边框,避免边框干扰居中效果
    //valueEditor.setColour(juce::TextEditor::outlineColourId, juce::Colours::transparentBlack);
    //valueEditor.setColour(juce::TextEditor::backgroundColourId, juce::Colours::transparentBlack);
    valueEditor.addListener(this);
    //先添加输入框后添加按钮,要不然会遮挡按钮
    addAndMakeVisible(valueEditor);

    upImage.reset(new juce::DrawableImage());
    downImage.reset(new juce::DrawableImage());
    pressed_upImage.reset(new juce::DrawableImage());
    pressed_downImage.reset(new juce::DrawableImage());

    // 加载二进制数据到Image对象
    juce::Image upImageData = juce::ImageFileFormat::loadFrom(BinaryData::up_png, BinaryData::up_pngSize);
    juce::Image downImageData = juce::ImageFileFormat::loadFrom(BinaryData::down_png, BinaryData::down_pngSize);
    juce::Image pressed_upImageData = juce::ImageFileFormat::loadFrom(BinaryData::pressed_up_png, BinaryData::pressed_up_pngSize);
    juce::Image pressed_downImageData = juce::ImageFileFormat::loadFrom(BinaryData::pressed_down_png, BinaryData::pressed_down_pngSize);

    // 验证图像有效性
    if (!upImageData.isValid() || !downImageData.isValid())
    {
        juce::AlertWindow::showMessageBoxAsync(juce::AlertWindow::WarningIcon, "error", "Unable to load image asset!");
        jassertfalse;
    }
    DBG("upImage width: " << upImageData.getWidth() << ", height: " << upImageData.getHeight());
    DBG("downImage width: " << downImageData.getWidth() << ", height: " << downImageData.getHeight());

    // 设置图像
    upImage->setImage(upImageData);
    downImage->setImage(downImageData);
    pressed_upImage->setImage(pressed_upImageData);
    pressed_downImage->setImage(pressed_downImageData);

    //upButton = new juce::DrawableButton("", juce::DrawableButton::ImageFitted); // 按钮模式:使用 ImageFitted 确保图标自动缩放适配按钮大小
    upButton.reset(new juce::DrawableButton("", juce::DrawableButton::ImageRaw));
    upButton->setImages(
        upImage,    // 正常状态
        upImage,    // 悬停状态
        pressed_upImage, // 按下状态
        nullptr,          // 禁用状态
        nullptr           // 正常状态覆盖颜色
    );
    upButton->addListener(this);
    upButton->setColour(juce::DrawableButton::backgroundColourId, juce::Colours::transparentBlack);
    addAndMakeVisible(upButton);

    //downButton = new juce::DrawableButton("", juce::DrawableButton::ImageFitted);
    downButton.reset(new juce::DrawableButton("", juce::DrawableButton::ImageRaw));
    // 设置按钮多状态图像
    downButton->setImages(
        downImage,
        downImage,
        pressed_downImage,
        nullptr,
        nullptr
    );
    downButton->addListener(this);// 注册按钮监听
    downButton->setColour(juce::DrawableButton::backgroundColourId, juce::Colours::transparentBlack);// 按钮背景颜色
    addAndMakeVisible(downButton);
}

SpinBox::~SpinBox()
{
}


float SpinBox::getValue() const
{
    DBG("currentValue = " << m_currentValue);
    return m_currentValue;
}

void SpinBox::setMinValue(float newValue)
{
    m_minValue = newValue;
}

void SpinBox::setMaxValue(float newValue)
{
    m_maxValue = newValue;
}

void SpinBox::setSteo(float step)
{
    m_step = step;
}

void SpinBox::resized()
{
    int w = getWidth();
    int h = getHeight();

    // 输入框占大部分宽度,留出右侧空间给按钮
    valueEditor.setBounds(0, 0, w, h);

    if (upButton && downButton)
    {
        // 按钮宽高与图片原始尺寸匹配
        const int btnWidth = 16; // 假设图片原始宽度为 32px
        const int btnHeight = 16; // 假设图片原始高度为 32px

        upButton->setBounds(w - btnWidth, 0, btnWidth, btnHeight);
        downButton->setBounds(w - btnWidth, h - btnHeight, btnWidth, btnHeight);
    }
}


// 修改 setValue 方法,触发监听器
void SpinBox::setValue(float newValue)
{
    newValue = juce::jlimit(m_minValue, m_maxValue, newValue);
    if (m_currentValue != newValue) 
    {
        m_currentValue = newValue;
        valueEditor.setText(juce::String::formatted("%.2f", m_currentValue), juce::dontSendNotification);
        listeners.call([this](Listener& listen) { listen.ValueChanged(this); }); // 触发回调
    }
}


// 按钮点击处理函数
void SpinBox::buttonClicked(juce::Button* button)
{
    if (button == upButton)
    {
        setValue(m_currentValue + m_step);
    }
    else if (button == downButton)
    {
        setValue(m_currentValue - m_step);
    }
}
// 输入框内容变化逻辑
void SpinBox::textEditorTextChanged(juce::TextEditor& editor)
{
    float newValue = valueEditor.getText().getFloatValue();
    DBG("newValue = " << newValue);
    setValue(newValue);
}

void SpinBox::addListener(Listener* listener)
{
    listeners.add(listener);
}

void SpinBox::removeListener(Listener* listener)
{
    listeners.remove(listener);
}

使用SpinBox组件

ExtenderComponent.h

#pragma once
#include <JuceHeader.h>
#include "SpinBox.h"

class ExtenderComponent : 
    public juce::Component,
    public SpinBox::Listener
{
public:
    ExtenderComponent();
    ~ExtenderComponent();


protected:
    void resized() override;
    void paint(juce::Graphics& g) override;
    void ValueChanged(SpinBox* spinBox) override;


private:

    juce::Label label_1;
    juce::Label label_2;
    juce::Label label_3;
    juce::Label label_4;

    SpinBox spin1;
    SpinBox spin2;
    SpinBox spin3;
    SpinBox spin4;


};

ExtenderComponent.cpp

#include "ExtenderComponent.h"

ExtenderComponent::ExtenderComponent()
{
	addAndMakeVisible(spin1);
	addAndMakeVisible(spin2);
	addAndMakeVisible(spin3);
	addAndMakeVisible(spin4);
    spin1.addListener(this);
    spin2.addListener(this);
    spin3.addListener(this);
    spin4.addListener(this);

    addAndMakeVisible(label_1);
    addAndMakeVisible(label_2);
    addAndMakeVisible(label_3);
    addAndMakeVisible(label_4);
    label_1.setText(juce::CharPointer_UTF8("阈值(dBu)"), juce::dontSendNotification);
    label_2.setText(juce::CharPointer_UTF8(("比率")), juce::dontSendNotification);
    label_3.setText(juce::CharPointer_UTF8("启动时间(ms)"), juce::dontSendNotification);
    label_4.setText(juce::CharPointer_UTF8("恢复时间(ms)"), juce::dontSendNotification);

}

ExtenderComponent::~ExtenderComponent()
{

}

void ExtenderComponent::resized()
{
    label_1.setBounds(40, 10, 60, 28);
    label_2.setBounds(110, 10, 60, 28);
    label_3.setBounds(180, 10, 60, 28);
    label_4.setBounds(250, 10, 60, 28);

    spin1.setBounds(40, 50, 60, 25);
    spin2.setBounds(110, 50, 60, 25);
    spin3.setBounds(180, 50, 60, 25);
    spin4.setBounds(250, 50, 60, 25);

    
}

void ExtenderComponent::paint(juce::Graphics& g)
{
    // 填充背景色
    g.fillAll(juce::Colours::darkgrey);

    // 设置边框属性
    const float cornerSize = 0.0f;    // 圆角半径
    const float borderThickness = 0.5f; // 边框粗细

    // 获取组件边界(向内缩进1像素防止边缘裁剪)
    auto bounds = getLocalBounds().toFloat().reduced(1.0f);

    // 绘制白色圆角边框
    g.setColour(juce::Colours::white);
    g.drawRoundedRectangle(bounds, cornerSize, borderThickness);
}

void ExtenderComponent::ValueChanged(SpinBox* spinBox)
{
    if (spinBox == &spin1) 
    {
        // 处理 spin1 值变化
        DBG("SpinBox 1 value: " << spin1.getValue());
    }
    else if (spinBox == &spin2)
    {
        // 处理 spin2 值变化
        DBG("SpinBox 2 value: " << spin2.getValue());
    }
    else if (spinBox == &spin3)
    {
        // 处理 spin3 值变化
        DBG("SpinBox 3 value: " << spin3.getValue());
    }
    else if (spinBox == &spin4) 
    {
        // 处理 spin4 值变化
        DBG("SpinBox 4 value: " << spin4.getValue());
    }
}

运行效果:

 

posted @ 2025-05-23 15:57  [BORUTO]  阅读(20)  评论(0)    收藏  举报