feat(gui): add knob value popup, tooltips, tab cycling, and layout system

Add a floating value label near the cursor while dragging a knob, giving
immediate feedback without altering slider behaviour. Inherit
SettableTooltipClient on ToggleButton so setTooltip works consistently
with knobs and combos.

Introduce a shared layout constants namespace in PluginEditor so every
panel, row and control position derives from one spacing system. Add
tooltips to all knobs, combos, toggles, and buttons. Add Tab/Shift+Tab
tab cycling via keyPressed, with a TooltipWindow for hover hints.
This commit is contained in:
2026-09-09 13:24:40 +02:00
parent f10409ad1e
commit a50666355c
5 changed files with 389 additions and 130 deletions
+62
View File
@@ -21,4 +21,66 @@ juce::String Knob::getTextFromValue (double value)
return formatPercent ((float) value);
}
void Knob::mouseDown (const juce::MouseEvent& e)
{
juce::Slider::mouseDown (e);
showValuePopup (e);
}
void Knob::mouseDrag (const juce::MouseEvent& e)
{
juce::Slider::mouseDrag (e);
showValuePopup (e);
}
void Knob::mouseUp (const juce::MouseEvent& e)
{
juce::Slider::mouseUp (e);
hideValuePopup();
}
void Knob::mouseExit (const juce::MouseEvent& e)
{
juce::Slider::mouseExit (e);
hideValuePopup();
}
// ---------------------------------------------------------------------------
// The floating readout lives as a child of the top-level component so it can
// follow the cursor outside this knob's own bounds without being clipped, and
// it never intercepts mouse clicks so it can't block interaction.
// ---------------------------------------------------------------------------
void Knob::showValuePopup (const juce::MouseEvent& e)
{
auto* topLevel = getTopLevelComponent();
if (topLevel == nullptr)
return;
if (valuePopup == nullptr)
{
valuePopup = std::make_unique<juce::Label>();
valuePopup->setAlwaysOnTop (true);
valuePopup->setInterceptsMouseClicks (false, false);
valuePopup->setColour (juce::Label::backgroundColourId, juce::Colours::transparentBlack);
valuePopup->setColour (juce::Label::textColourId, theme::text);
valuePopup->setFont (juce::Font (14.0f, juce::Font::bold));
valuePopup->setJustificationType (juce::Justification::centred);
topLevel->addAndMakeVisible (valuePopup.get());
}
valuePopup->setText (getTextFromValue (getValue()), juce::dontSendNotification);
constexpr int w = 84;
constexpr int h = 24;
constexpr int gap = 10;
const auto cursor = topLevel->getLocalPoint (nullptr, e.getScreenPosition());
valuePopup->setBounds (cursor.getX() - w / 2, cursor.getY() - h - gap, w, h);
}
void Knob::hideValuePopup()
{
valuePopup.reset();
}
} // namespace serum
+14
View File
@@ -1,6 +1,7 @@
#pragma once
#include <JuceHeader.h>
#include <memory>
#include "../Resources.h"
namespace serum
@@ -9,6 +10,10 @@ namespace serum
// ===========================================================================
// Rotary knob — a juce::Slider styled by SerumLookAndFeel. The value readout is
// formatted by a user-provided function via getTextFromValue().
//
// While the knob is being dragged it also shows a small floating label near the
// mouse cursor with the live (formatted) value, giving immediate feedback
// without changing the slider's normal behaviour.
// ===========================================================================
class Knob : public juce::Slider
{
@@ -20,8 +25,17 @@ public:
juce::String getTextFromValue (double value) override;
void mouseDown (const juce::MouseEvent& e) override;
void mouseDrag (const juce::MouseEvent& e) override;
void mouseUp (const juce::MouseEvent& e) override;
void mouseExit (const juce::MouseEvent& e) override;
private:
void showValuePopup (const juce::MouseEvent& e);
void hideValuePopup();
std::function<juce::String (float)> formatter;
std::unique_ptr<juce::Label> valuePopup;
};
} // namespace serum
+6 -1
View File
@@ -9,8 +9,13 @@ namespace serum
// ===========================================================================
// LED-style toggle (power) button that drives a float parameter (0/1) or an
// optional callback (used by RAVE).
//
// Inherits SettableTooltipClient so setTooltip() works exactly like it does on
// juce::Button / juce::Slider / juce::ComboBox (juce::Component itself has no
// tooltip support).
// ===========================================================================
class ToggleButton : public juce::Component
class ToggleButton : public juce::Component,
public juce::SettableTooltipClient
{
public:
explicit ToggleButton (const juce::String& label = {});