Problems with Painting and MouseEvents

Hi ^-^
First, some general information:
This is MacOS 12.1 with Projucer v6.1.5.
I am using JUCE for a synthesiser project in my university.
In the project, I paint light sources with paths and a gradient, from which I will get the colour to affect the sound.

I have two major problems:
The first is about the mouse events. The position of the mouse event has extreme jitter. If I move the mouse, the object I move with MouseDrag(Event &e) jumps wildly across two positions, the mouse position and a position with a large offset.
The code, which has this problem is

`void mouseDrag(const MouseEvent &e) override {
auto zeroed = e.getEventRelativeTo(&zero);
setCentrePosition(zeroed.position.x, zeroed.position.y);

    Rectangle<int> r = Desktop::getInstance().getDisplays().getDisplayForPoint(getPosition())->userArea;
    int x = r.getWidth();
    int y = r.getHeight();
    
    if (getX() < 0) setBounds(0, getY(), getWidth(), getHeight());
    if (getY() < 0) setBounds(getX(), 0, getWidth(), getHeight());
    if (getX() > x) setBounds(x, getY(), getWidth(), getHeight());
    if (getY() > y) setBounds(getX(), y, getWidth(), getHeight());
    
    repaint();
    
} `

I have to use this “zeroed” variable, because the coordinates of the event itself have an offset to my mouse position.
The setBounds also works better than setCentrePosition, with setCentrePosition, the objects get jittery as well on the window edges.
In the constructor I have a addMouseListener(this, true);.
A workaround I found was using the getOffsetFromDragStart() and saving the start positions. It is working, but I’d like to use the mousePosition, because it is way easier to use.
What can I do to fix this?

The second problem is the painting.
1 - The objects I move with the mouseDrags are painting not correct. I have to resize the window to get rid of artifacts in the window.

2 - Also, a copy(?) of the object is being painted, when I get near the left upper position of the window.

I will attach some Screenshots to clarify :slight_smile:

the code in question is

void paint(Graphics &g) override {
        g.setColour(Colours::blue);
        root->paint(g);
        g.setColour(Colours::red);
        end->paint(g);
        g.setColour(Colours::green);
        width->paint(g);
        
        Point<float> mirrored = mirrorOnLine(root->getPosition().toFloat(), end->getPosition().toFloat(), width->getPosition().toFloat());
        
        juce::Path path;
        path.startNewSubPath(juce::Point<float> (root->getX(),root->getY()));
        path.lineTo(juce::Point<float>(width->getX(),width->getY()));
        path.cubicTo(end->getPosition().toFloat(), end->getPosition().toFloat(), mirrored);
        path.lineTo(root->getPosition().toFloat());
        path.closeSubPath();
        
        g.strokePath(path, PathStrokeType(2.f));
        
        g.setGradientFill(*gradient);
        g.fillPath(path);
    }

root, width and end are the squares.
The mirrorOnLine function mirrors “width” on the line “root” → “end”.

I just found the spoiler tags, so here is the code for the three classes:
(beware, this is my first juce project, so this is probably not beautiful ^^)

//
//  DragObject.h
//  NewProject
//
//  Created on 18.11.21.
//

#ifndef DragObject_h
#define DragObject_h

#include <JuceHeader.h>
using namespace juce;

class DragObject: public Component {
private:
    Component zero;
    
public:
    DragObject() {
        setBounds(getX(), getY(), 10, 10);
        addMouseListener(this, true);
        zero.setCentrePosition(0, 0);
    }
    
    void mouseDrag(const MouseEvent &e) override {
        auto zeroed = e.getEventRelativeTo(&zero);
        setCentrePosition(zeroed.position.x, zeroed.position.y);
        //printf("x: %d y: %d\n", e.getPosition().x, e.getPosition().y);
        Rectangle<int> r = Desktop::getInstance().getDisplays().getDisplayForPoint(getPosition())->userArea;
        int x = r.getWidth();
        int y = r.getHeight();
        
        
        if (getX() < 0) setBounds(0, getY(), getWidth(), getHeight());
        if (getY() < 0) setBounds(getX(), 0, getWidth(), getHeight());
        if (getX() > x) setBounds(x, getY(), getWidth(), getHeight());
        if (getY() > y) setBounds(getX(), y, getWidth(), getHeight());
        
        repaint();
        
    }
    
    void resized() override {
        Rectangle<int> r = Desktop::getInstance().getDisplays().getDisplayForPoint(getPosition())->userArea;
        int x = r.getWidth();
        int y = r.getHeight();
        
        
        if (getX() < 0) setBounds(0, getY(), getWidth(), getHeight());
        if (getY() < 0) setBounds(getX(), 0, getWidth(), getHeight());
        if (getX() > x) setBounds(x, getY(), getWidth(), getHeight());
        if (getY() > y) setBounds(getX(), y, getWidth(), getHeight());
    }
    
    void paint(Graphics &g) override {
        g.fillRect(getX(), getY(), 10, 10);
    }
};

#endif /* DragObject_h */
/*
  ==============================================================================

    CircleSegment.h
    Created: 18 Jan 2022 2:01:00pm

  ==============================================================================
*/

#pragma once
#include "DragObject.h"
#include <sstream>

//for debugging purposes
template <typename T>
  std::string NumberToString ( T Number )
  {
     std::ostringstream ss;
     ss << Number;
     return ss.str();
  }



class CircleSegment: public Component {
private:
    Colour colour;
    ColourGradient* gradient;
    Component zero;
    Point<int> rootDragPosition;
    Point<int> endDragPosition;
    Point<int> widthDragPosition;
    
    template <class T>
    Point<T> mirrorOnLine(Point<T> lineOrigin, Point<T> lineEnd, Point<T> original) {
        float a = lineEnd.getY() - lineOrigin.getY();
        float b = -(lineEnd.getX() - lineOrigin.getX());
        float c = -a * lineOrigin.getX() - b * lineOrigin.getY();
        
        float m = sqrt(a*a + b*b);
        if (m == 0)
            return Point<T>(0,0);
        
        a /= m;
        b /= m;
        c /= m;
        
        float d = a * original.getX() + b * original.getY() + c;
        
        return Point<T>(original.getX() - 2 * a * d, original.getY() - 2 * b * d);
    }
    /// v1 and v2 are vectors!
    bool areClockwise(Point<int> v1, Point<int> v2) {
      return -v1.x*v2.y + v1.y*v2.x > 0;
    }
    
    /// v is vector2
    bool isWithinRadius(Point<int> v,float radiusSquared) {
      return v.x*v.x + v.y*v.y <= radiusSquared;
    }
    
public:
    DragObject *root, *end, *width;

    CircleSegment(Colour colour): colour(colour) {
        root = new DragObject();
        end = new DragObject();
        width = new DragObject();
        
        root->setBounds(500, 500, 10, 10);
        end->setBounds(600, 600,10,10);
        width->setBounds(459, 600,10,10);

        zero.setCentrePosition(0, 0);
        addMouseListener(this, true);
        
        gradient = new ColourGradient(Colour::fromRGBA(colour.getRed(),colour.getGreen(), colour.getBlue(), 200), root->getPosition().toFloat(),
                                      Colours::transparentBlack, end->getPosition().toFloat(),
                                      true);
        gradient->addColour(0.8f, Colour::fromRGBA(colour.getRed(),colour.getGreen(), colour.getBlue(), 40));
  
        resized();
    }
    
    void updateGradientPosition() {
        gradient->point1 = root->getPosition().toFloat();
        gradient->point2 = end->getPosition().toFloat();
    }
    
    ~CircleSegment() {
        delete root;
        delete end;
        delete width;
        delete gradient;
    }
    
    void calculateBounds() {
        auto mirrored = mirrorOnLine(root->getPosition().toFloat(), end->getPosition().toFloat(), width->getPosition().toFloat());
        auto minX = std::min(root->getX(),std::min(width->getX(),std::min((int)mirrored.getX(),end->getX())));
        auto minY = std::min(root->getY(),std::min(width->getY(),std::min((int)mirrored.getY(),end->getY())));
        auto maxX = std::max(root->getX(),std::max(width->getX(),std::max((int)mirrored.getX(),end->getX())));
        auto maxY = std::max(root->getY(),std::max(width->getY(),std::max((int)mirrored.getY(),end->getY())));
        setBounds(Rectangle<int>(minX+10, minY+10, maxX - minX, maxY - minY));
    }

    void resized() override {
        root->resized();
        end->resized();
        width->resized();
        
        calculateBounds();
    }
    

    void paint(Graphics &g) override {
        g.setColour(Colours::blue);
        root->paint(g);
        g.setColour(Colours::red);
        end->paint(g);
        g.setColour(Colours::green);
        width->paint(g);
        
        Point<float> mirrored = mirrorOnLine(root->getPosition().toFloat(), end->getPosition().toFloat(), width->getPosition().toFloat());
        
        juce::Path path;
        path.startNewSubPath(juce::Point<float> (root->getX(),root->getY()));
        path.lineTo(juce::Point<float>(width->getX(),width->getY()));
        path.cubicTo(end->getPosition().toFloat(), end->getPosition().toFloat(), mirrored);
        path.lineTo(root->getPosition().toFloat());
        path.closeSubPath();
        
        g.strokePath(path, PathStrokeType(2.f));
        
        g.setGradientFill(*gradient);
        g.fillPath(path);
    }
    
    void mouseDown(const MouseEvent &e) override {
        rootDragPosition = root->getPosition();
        widthDragPosition = width->getPosition();
        endDragPosition = end->getPosition();
    }
    
    void mouseUp(const MouseEvent &e) override {
        if (saneHitTest(root, e.x, e.y)) {
            return;
        }
        
        Point<int> offset = e.getOffsetFromDragStart();
        root->setBounds(rootDragPosition.x + offset.x, offset.y + rootDragPosition.y, 10, 10);
        width->setBounds(offset.x + widthDragPosition.x, offset.y + widthDragPosition.y, 10, 10);
        end->setBounds(offset.x + endDragPosition.x, offset.y + endDragPosition.y, 10, 10);
    }
    
    void mouseDrag(const MouseEvent &e) override {
        auto zeroed = e.getEventRelativeTo(&zero);
        if (saneHitTest(root, zeroed.x, zeroed.y)) {
            root->mouseDrag(e);
            return;
        }
        
        
        Point<int> offset = e.getOffsetFromDragStart();
        root->setBounds(rootDragPosition.x + offset.x,
                        rootDragPosition.y + offset.y,
                        10, 10);
        end->setBounds(endDragPosition.x + offset.x,
                       endDragPosition.y + offset.y,
                        10, 10);
        width->setBounds(widthDragPosition.x + offset.x,
                         widthDragPosition.y + offset.y,
                        10, 10);
        
        
        resized();
        updateGradientPosition();
        sanifyPosition();
        repaintObjects();
        
    }
    
    void repaintObjects() {
        repaint(getBounds());
        root->repaint();
        end->repaint();
        width->repaint();
    }
    
    void sanifyPosition() {
        Rectangle<int> r = Desktop::getInstance().getDisplays().getDisplayForPoint(getPosition())->userArea;
        int x = r.getWidth();
        int y = r.getHeight();
        
        
        if (getX() < 0) setBounds(0, getY(), getWidth(), getHeight());
        if (getY() < 0) setBounds(getX(), 0, getWidth(), getHeight());
        if (getX() > x) setBounds(x, getY(), getWidth(), getHeight());
        if (getY() > y) setBounds(getX(), y, getWidth(), getHeight());
    }
    
    bool saneHitTest(Component* target, int x, int y) {
        auto bounds = target->getBounds();
        if (x >= bounds.getX() && x <= bounds.getX() + bounds.getWidth()
            && y >= bounds.getY() && y <= bounds.getY() + bounds.getHeight()) {
            return true;
        }
        return false;
    }
    
    /// position is global position
    bool isInSegment(Point<int>position) {
        Point<int> relEnd(end->getX() - root->getX(), end->getY() - root->getY());
        float radiusSquared = relEnd.x * relEnd.x + relEnd.y * relEnd.y;
        Point<int> relPoint (position.x - root->getX(), position.y - root->getY());
        Point<int> relWidth(width->getPosition() - root->getPosition());
        Point<int> relMirrored(mirrorOnLine(Point<int>(0,0), relEnd, relWidth));
        
        bool one = areClockwise(relWidth, relPoint);
        bool two = areClockwise(relMirrored, relPoint);
        bool three = isWithinRadius(relPoint, radiusSquared);
        return  !one &&
                two &&
                three;
        
    }
    
    /// position is global position
    ///  this only does work, when angle of segment is smaller than 180°
    Colour getColourOfPosition(Point<int> position) {
        if (!isInSegment(position)) return Colours::transparentBlack;
        
        float d = sqrt(position.x * root->getX() + position.y * root->getY());
        float gradientlength = sqrt(root->getX() * end->getX() + root->getX() * end->getY());
        return gradient->getColourAtPosition(d / gradientlength);
    }
};
#pragma once
#include "CircleSegment.h"
#include "Synth.h"
//==============================================================================
class MainComponent   : public juce::AudioAppComponent, public juce::Button::Listener, public juce::MidiKeyboardState
{
private:
    std::vector<CircleSegment*> circleSegments;
    juce::AudioDeviceManager deviceManager;           // [1]
    juce::ComboBox midiInputList;                     // [2]
    juce::Label midiInputListLabel;
    int lastInputIndex = 0;                           // [3]
    bool isAddingFromMidiInput = false;               // [4]

    juce::MidiKeyboardState keyboardState;            // [5]
    juce::MidiKeyboardComponent keyboardComponent;    // [6]

    double startTime;
    
    DragObject* needle;
    
    
    SynthAudioSource source;
    
    
    
    juce::TextButton spawnSineButton;
    juce::TextButton spawnTriangleButton;
    juce::TextButton spawnSquareButton;
    
    double currentSampleRate = 0.0, currentAngle = 0.0, angleDelta = 0.0;
   
    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainComponent)
    
public:
    MainComponent() : keyboardComponent (keyboardState, juce::MidiKeyboardComponent::horizontalKeyboard),
        startTime (juce::Time::getMillisecondCounterHiRes() * 0.001),  source(keyboardState)
    {
        addAndMakeVisible(spawnSineButton);
        spawnSineButton.setButtonText("Spawn a sine Soundobject");
        spawnSineButton.addListener(this);
        
        addAndMakeVisible(spawnTriangleButton);
        spawnTriangleButton.setButtonText("Spawn a triangle Soundobject");
        spawnTriangleButton.addListener(this);
        
        addAndMakeVisible(spawnSquareButton);
        spawnSquareButton.setButtonText("Spawn a square Soundobject");
        spawnSquareButton.addListener(this);
        
        setSize (1024, 768);
        setAudioChannels (0, 2); // no inputs, two outputs
        
        
        addAndMakeVisible (midiInputList);
        midiInputList.setTextWhenNoChoicesAvailable ("No MIDI Inputs Enabled");
        auto midiInputs = juce::MidiInput::getAvailableDevices();

        juce::StringArray midiInputNames;

        for (auto input : midiInputs)
            midiInputNames.add (input.name);

        midiInputList.addItemList (midiInputNames, 1);
        midiInputList.onChange = [this] { setMidiInput (midiInputList.getSelectedItemIndex()); };

        // find the first enabled device and use that by default
        for (auto input : midiInputs)
        {
            if (deviceManager.isMidiInputDeviceEnabled (input.identifier))
            {
                setMidiInput (midiInputs.indexOf (input));
                break;
            }
        }

        // if no enabled devices were found just use the first one in the list
        if (midiInputList.getSelectedId() == 0)
            setMidiInput (0);
        
        addAndMakeVisible (keyboardComponent);
        
        needle = new DragObject();
        needle->setBounds(0, 0, 10, 10);
        
        addChildComponent(needle);
        addAndMakeVisible(needle);
        
    }
    
    void setMidiInput (int index)
    {
        auto list = juce::MidiInput::getAvailableDevices();

        deviceManager.removeMidiInputDeviceCallback(list[lastInputIndex].identifier, source.getMidiCollector());

        auto newInput = list[index];

        if (!deviceManager.isMidiInputDeviceEnabled (newInput.identifier))
            deviceManager.setMidiInputDeviceEnabled (newInput.identifier, true);

        deviceManager.addMidiInputDeviceCallback (newInput.identifier, source.getMidiCollector());
        midiInputList.setSelectedId (index + 1, juce::dontSendNotification);

        lastInputIndex = index;
    }
    


    
    ~MainComponent() override
    {
        for (auto object : circleSegments) {
            delete object;
        }
        delete needle;
        shutdownAudio();
    }

    void resized() override
    {
        spawnSineButton      .setBounds (10, 120, getWidth() / 3 - 10, 40);
        spawnTriangleButton  .setBounds (getWidth() / 3 + 10, 120, getWidth() / 3 - 10, 40);
        spawnSquareButton    .setBounds (2*getWidth() / 3 + 10, 120, getWidth() / 3 - 10, 40);
        
        auto area = getLocalBounds();

        midiInputList    .setBounds (area.removeFromTop (36).removeFromRight (getWidth() - 150).reduced (8));
        keyboardComponent.setBounds (area.removeFromTop (80).reduced(8));
        
        for (auto s : circleSegments) {
            s->resized();
        }
    }

    void prepareToPlay (int /**/, double sampleRate) override
    {
        currentSampleRate = sampleRate;
        source.prepareToPlay(0, sampleRate);
    }

    
    void releaseResources() override {
        source.releaseResources();
    }

    void getNextAudioBlock (const juce::AudioSourceChannelInfo& bufferToFill) override
    {
        source.getNextAudioBlock(bufferToFill);
    }
    
    void paint(juce::Graphics &g) override {
        for (CircleSegment* object : circleSegments) {
            object->paint(g);
        }
        g.setColour(Colours::black);
        needle->paint(g);
        getColourFromNeedle(needle);
        
    }
    
    void buttonClicked (juce::Button* button) override // [2]
    {
        if (button == &spawnSineButton) {
            CircleSegment *newObject = new CircleSegment(Colours::yellow);
            
            addChildComponent(newObject->root);
            addChildComponent(newObject->end);
            addChildComponent(newObject->width);
            
            addAndMakeVisible(newObject->root);
            addAndMakeVisible(newObject->end);
            addAndMakeVisible(newObject->width);
            
            addAndMakeVisible(newObject);
            circleSegments.push_back(newObject);
            
            repaint();
            resized();
        }
        else if (button == &spawnTriangleButton) {
            CircleSegment *newObject = new CircleSegment(Colours::green);
            
            addChildComponent(newObject->root);
            addChildComponent(newObject->end);
            addChildComponent(newObject->width);
            
            addAndMakeVisible(newObject->root);
            addAndMakeVisible(newObject->end);
            addAndMakeVisible(newObject->width);
            
            addAndMakeVisible(newObject);
            circleSegments.push_back(newObject);
            
            repaint();
            resized();
        }
        else if (button == &spawnSquareButton) {
            CircleSegment *newObject = new CircleSegment(Colours::red);
            
            addChildComponent(newObject->root);
            addChildComponent(newObject->end);
            addChildComponent(newObject->width);
            
            addAndMakeVisible(newObject->root);
            addAndMakeVisible(newObject->end);
            addAndMakeVisible(newObject->width);
            
            addAndMakeVisible(newObject);
            circleSegments.push_back(newObject);
            
            repaint();
            resized();
        }
    }
    
    Colour getColourFromNeedle(DragObject* needle) {
        float r = 0, g = 0, b = 0, a = 0;
        float count = 0;
        for(CircleSegment* c : circleSegments) {
            Colour colour = c->getColourOfPosition(needle->getPosition());
            r += colour.getRed();
            g += colour.getGreen();
            b += colour.getBlue();
            a += colour.getAlpha();
            count++;
        }
        r /= count;
        g /= count;
        b /= count;
        a /= count;
        //printf("%f : %f : %f : %f\n", r,g,b,a);
        //printf("%d : %d\n", needle->getX(), needle->getY());
        return Colour((uint8)r,(uint8)g,(uint8)b,(uint8)a);
    }
};