#pragma once #include // Small flat circular button matching the joystick pad's dark style. class RoundNavButton : public juce::Button { public: explicit RoundNavButton (const juce::String& label) : juce::Button (label) {} void paintButton (juce::Graphics& g, bool isDown, bool isOver) override { const float w = (float) getWidth(); const float h = (float) getHeight(); const float cx = w * 0.5f; const float cy = h * 0.5f; const float r = juce::jmin (w, h) * 0.5f - 1.0f; g.fillAll (juce::Colour (0xFF000000)); g.setColour (isDown ? juce::Colour (0xFFECECEC) : juce::Colour (0xFF151515)); g.fillEllipse (cx - r, cy - r, r * 2.0f, r * 2.0f); g.setColour (isDown ? juce::Colour (0xFFECECEC) : juce::Colour (0xFF242424)); g.drawEllipse (cx - r, cy - r, r * 2.0f, r * 2.0f, 1.0f); g.setFont (juce::Font (juce::FontOptions { juce::Font::getDefaultMonospacedFontName(), 12.0f, isDown ? juce::Font::bold : juce::Font::plain })); g.setColour (isDown ? juce::Colour (0xFF0A0A0A) : juce::Colour (0xFF9A9A9A)); g.drawText (getButtonText(), juce::Rectangle (0, 0, w, h), juce::Justification::centred); } }; // A flat dark virtual-analog joystick pad. A circular well holds four // quarter-circle buttons (up/down/left/right). Holding a direction glows // the matching quarter and emits repeat actions like a gamepad D-pad. class JoystickPad : public juce::Component, private juce::Timer { public: enum Direction : int { None = -1, Up = 0, Down = 1, Left = 2, Right = 3 }; JoystickPad(); ~JoystickPad() override = default; void paint (juce::Graphics&) override; void mouseDown (const juce::MouseEvent&) override; void mouseDrag (const juce::MouseEvent&) override; void mouseUp (const juce::MouseEvent&) override; // Called on press (immediately) and then on repeat while held. std::function onAction; void setRepeatSpeed (int initialDelayMs, int repeatMs); private: void timerCallback() override; void beginHold (int direction); void endHold(); int directionFrom (const juce::MouseEvent&) const; Direction active = None; int initialDelayMs = 250; int repeatMs = 50; int64 holdStartMs = 0; int64 lastFireMs = 0; bool repeatsStarted = false; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (JoystickPad) };