Detect double click inside class

Hello, I would like to detect a double mouse click from within a class. If a double click is detected, some event has to be executed. I would like the code to be modular, so the mouseClicked() or mousePressed() functions are not a viable option (I think).

More specific, I would like to set the value of the slider back to “startValue” if a double click is detected.

I would probably need to use an event handler, if anyone can show me how to do that using the minimum working example, that would be helpful. Any help is appreciated though. Thanks in advance!

See below for a MWE:

main.pde:

Slider slider1;


void setup(){
    size(500, 500);
    slider1 = new Slider(75, 75, 225, -50, 250, 75);
}


void draw(){
    background(180);
    slider1.reDraw(0, 0);
}

And a slider class Slider.pde:

class Slider{
    // Default constructor
    Slider(){
        circleX = (startValue-minValue)*sliderLength/(maxValue-minValue)+x-sliderLength/2; // position of the circle
        sliderUnit = "";
        sliderName = "";
        sliderNameColor = color(255);
        sliderTextSize = 20;
        lineWeight = 5;
        circleHeight = 20;
        sliderDarkLineColor = color(49,143,145);
        sliderLightLineColor = color(79,193,195);
        circleFillColor = color(20,45,55);
        circleFillDisabledColor = color(60);
        circleStrokeDisabledColor = color(110);
        sliderNameColor = color(255);
        circleStrokeColorEnabled = color(255);
        circleStrokeColorFocused = color(255,0,0);
        backgroundColor = color(180);
        sliderState = "enabled";
        amountOfDecimals = 1;
        interrruptLength = 8;
    }
    // Constructor
    Slider(int x, int y, int sliderLength, float minValue, float maxValue, float startValue){
        this(); // call default constructor
        this.x = x; // x coordinate of left of the slider
        this.y = y; // y coordinate of left of the slider
        this.sliderLength = sliderLength; // length of the slider in pixels
        this.minValue = minValue; // value when slider is left
        this.maxValue = maxValue; // value when slider is right
        this.startValue = startValue; // position of slider at the beginning
        currentValue = startValue; // current value of slider
    }


    // Set current values of slider and call draw slider function
    void reDraw(int xOffset, int yOffset){
        if (sliderState == "disabled"){
            drawSlider("disabled", xOffset, yOffset);
        }
        else if(mouseX >= x + xOffset && mouseX <= x + xOffset + sliderLength && mouseY >= y + yOffset - circleHeight/2 && mouseY <= y + yOffset + circleHeight/2 && !mouseClickedFirst){
            mousePositionFirst = true;
            if(mousePressed && mousePositionFirst){
                setCurrentValue();
            }
            drawSlider("focused", xOffset, yOffset);
        }
        else if((mouseX <= x + xOffset || mouseX >= x + xOffset +sliderLength || mouseY <= y + yOffset - circleHeight/2 || mouseY >= y + yOffset + circleHeight/2) && mousePressed && mousePositionFirst){
            if(mousePressed && !mousePositionFirst){
                mouseClickedFirst = true;
            }
            else if(mouseX >= x + xOffset && mouseX <= x + xOffset +sliderLength && mousePressed && mousePositionFirst){
                setCurrentValue();
            }
            else if(mouseX <= x + xOffset && mousePressed && mousePositionFirst){
                currentValue = minValue;
            }
            else if(mouseX >= x + xOffset + sliderLength && mousePressed && mousePositionFirst){
                currentValue = maxValue;
            }
            else{
                mouseClickedFirst = false;
                mousePositionFirst = false;
            }
            drawSlider("focused", xOffset, yOffset);
        }
        else{
            if(mousePressed && !mousePositionFirst){
                mouseClickedFirst = true;
            }
            else if(mouseX >= x + xOffset && mouseX <= x + xOffset +sliderLength && mousePressed && mousePositionFirst){
                setCurrentValue();
            }
            else if(mouseX <= x + xOffset && mousePressed && mousePositionFirst){
                currentValue = minValue;
            }
            else if(mouseX >= x + xOffset + sliderLength && mousePressed && mousePositionFirst){
                currentValue = maxValue;
            }
            else{
                mouseClickedFirst = false;
                mousePositionFirst = false;
            }
            drawSlider("enabled", xOffset, yOffset);
        }
    }


    void drawSlider(String state, int xOffset, int yOffset){
            stroke(sliderLightLineColor);
            strokeWeight(lineWeight);
            line(x + xOffset, y + yOffset, x + xOffset + sliderLength, y + yOffset);
            stroke(sliderDarkLineColor);
            line(getAbsoluteValue(minValue) + xOffset, y + yOffset, getAbsoluteValue(currentValue) + xOffset, y + yOffset);

            drawCircle(state, xOffset, yOffset);

            stroke(255);
            textAlign(CENTER, CENTER);
            fill(sliderNameColor);
            textSize(sliderTextSize);
            textAlign(RIGHT, BOTTOM);
            text(nf(currentValue, 0, amountOfDecimals) + sliderUnit, x + xOffset + sliderLength, y + yOffset - circleHeight/2 - lineWeight/2);

            fill(sliderNameColor);
            textSize(sliderTextSize);
            textAlign(LEFT, BOTTOM);
            text(sliderName, x + xOffset, y + yOffset - circleHeight/2 - lineWeight/2);
    }


    void drawCircle(String state, int xOffset, int yOffset){
        circleX = (currentValue-minValue)*sliderLength/(maxValue-minValue) + x + xOffset;

        fill(backgroundColor);
        stroke(backgroundColor);
        circle(circleX, y + yOffset, circleHeight + interrruptLength);

        if(state == "enabled"){
            fill(circleFillColor);
            stroke(circleStrokeColorEnabled);
        }
        else if(state == "disabled"){
            fill(circleFillDisabledColor);
            stroke(circleStrokeDisabledColor);
        }
        else{
            fill(circleFillColor);
            stroke(circleStrokeColorFocused);
        }

        circle(circleX, y + yOffset, circleHeight);
    }


    void setCurrentValue(){
        if (sliderState == "enabled"){
            currentValue = minValue + ((mouseX - (x + xOffset))/sliderLength)*(maxValue - minValue);
        }
        else{   }
    }

    float getAbsoluteValue(float sliderValue){
        return sliderLength - sliderLength*(maxValue/(maxValue - minValue)) + sliderValue/(maxValue-minValue)*sliderLength + x;
    }

    // Class attributes
    private int x, y, xOffset, yOffset, lineWeight, circleHeight, sliderTextSize, amountOfDecimals, interrruptLength;
    private float sliderLength, minValue, maxValue, startValue, currentValue, circleX, circleY;
    private boolean mousePositionFirst = false, mouseClickedFirst = false; // used for validation if mouse was clicked outside slider or not
    private String sliderName, sliderUnit, sliderState; // name of slider, unit to be displayed and hovering state
    private color sliderDarkLineColor, sliderLightLineColor, circleFillColor, circleStrokeColorEnabled, circleStrokeColorFocused, sliderNameColor, circleFillDisabledColor, circleStrokeDisabledColor, backgroundColor; // colors of the slider
}

1 Like

i play that
Key and mouse pressed with boolean here

1 Like

Thanks @kll, I’ve managed to make it work using your code. Just for my own reference, what do the variables ‘diag’, ‘act’, ‘sel’ and ‘seld’ mean?

you mean?

boolean diag = true; //__________________________________ diagnostic prints global switch

when i start coding i do line by line
and use many prints to console.
later i want disable the prints so a global switch makes that easy.


besides they show the resulting color / it means like the status of the button

  • over
  • over and click > act ( timer running )
  • sel is single click result ( or fallback after timeout )
  • seld is double click with correct timing detected.

you know,
it was never the idea to be used,
more a exercise about timer and logic

what gives you the idea to need a double click button in processing
( and what will you do on single click?? )

Yes that is what I meant indeed. Thanks for the clarification. A single click will set a certain value and double click resets the object to the starting value.

That brings me to the next problem, but that might be better suited for a 2nd post: what is the best way to make a button action?
E.g. if I have a button class, where I can detect a button click, but depending on which instance of this class you press, a different action has to be executed.
So when you press a button: execute this function, if you press another button: execute another function. The code must me modular, so nothing in the main draw function, but everything inside a button class. I think you have to use an event handler, but don’t know where to start. Any advise?

Thanks for taking the time to answer my question.

1 Like

so we must ask @Chrisir
how to connect a button ( here even with 2 different events "single click " and “double click” )
with a main program,
because from there the main could just read that status continuously
button[1].sel ( or .dsel )
gives the status, not the event!
so would need a new event logic inside main???

and to set a main variable, what the main can reset after usage
is bad coding style???

i check on your solution tomorrow

That’s an interesting question!

An important side note:

When we want to have the class reusable we have to design it in a way that the class is neutral to whatever main sketch we have.

But double click has been done often, just google here on the forum

In my opinion in mousePressed loop over all buttons and call the button class whether we have a click or a double click.

Class returns an ID that you can evaluate from main class. Eg button ID 0,1,2… and for double click 1000, 1001,1002…

ok, thanks, but that was not the help for the event question.

and a own event handler for the class would be the long way,
but just take a look how the PRO’s are doing it
Text input for choosing Colour .
a ( reduced ) G4P example

but here we try in small steps:


lets start again from the functional side:

as a electrician i know the difference between a

  • push button
  • switch

but here in coding what we actually have?
the button class acts like a switch,
when click mouse LEFT, the switch goes from OFF to ON.
( because it has a latching memory .sel )

if we write some code in main
( continuously polling )
bad style
if ( button[17].sel ) {}
better class code
if ( button[17].get_sel() ) {}
we can do what we need to do, and
then a
button[17].reset();
so we not see the ( .sel == true ) again in the next loop.

in other words from main we converted the switch to a push button
( might not even see the green fill color )
that is also a correct event thinking.


but if we want / need the switch as a switch
we want following 3 information:
-1- event ( click ) OFF to ON
-2- status ON or OFF
-3- event ( click ) ON to OFF
this we can do if we compare the .sel status while polling
with a memory variable in main ( last_sel = …sel;)
easy but kind of ugly,

assuming that via draw and button display the codes are synchronized
it might be enough to have 2 add event bits in the class.

  • .rise
  • .fall

what will be true only for one cycle.
in case of asynchronous operation we must make them latching until read || reset.


in this case with .sel and .seld it is a little bit more complicated.

so…
what you need ( on double click )

  • push button ?
  • switch ?

thinking.

Thank you for your replies. For now I will use identifiers, which is probably not the most elegant method, but I’ll do the job.

Is indeed what I want. If you come across an easy to follow solution, please let me know :wink: I will make a new question once I come across some of the drawbacks of using this method.

1 Like

The g4p and ControlP5 libraries both have event handling built in to an extensive set of control types.

If you are rolling your own, two approaches are:

  1. define an event handler callback, and put all special logic for all controls into that one callback. Then, based on your control ID, you can act appropriately when it sends an event.

Or:

  1. create a stub action in your Button class, e.g. “click()” that is called by your button instance on click. When you create a new Button, @Override its click() with custom code.
class Button {
  public void foo() {
  }
}

Button b;
Button k;

void setup() {
  b = new Button(){ @Override public void foo() {  println(frameCount); } };
  k = new Button(){ @Override public void foo() {  println(millis()); } };
}

void draw() {
  if(mousePressed) b.foo();
  if(keyPressed) k.foo();
}
4 Likes

It’s good to see that this got answered.

I would like to add to all this that it’s important to understand that Processing 's core libraries by default intercept mouse and keyboard input events from the JVM (Java VM) and calls the various callbacks (mousePressed(), mouseReleased(), keyPressed(), keyReleased() ) in your sketch if they are defined.

So in order for you to create your own UI elements without working with an existing library and sub-classing them you have to somehow propagate/delegate either those events/relevant context from the main PApplet to other places in your code. That can be done by a main callback in the sketch doing the heavy lifting and then calling a method on the identified UI element instance (e.g. click(), onclick(), etc). It’s also possible to delegate all of that to a separate class (other than the main sketch) by registering that class’s methods.

Afaik I know it may only be possible to register one method from one object for the callback in which case you might need your own internal event system to register the UI objects to.

https://processing.github.io/processing-javadocs/core/processing/core/PApplet.html#registerMethod-java.lang.String-java.lang.Object-

To handle a double click it’s necessary to retain some state somewhere to remember that the object was clicked before and that the last time you clicked immediately preceded the next click which also was on the same object (i.e. a double click is two consecutive clicks actions which are on the same object).

3 Likes

Thank you, this is a nice implementation without having to use external code in the draw function. Exactly what I was looking for!