Gestures in JUCE


#1

Hello Jules,

I don't want to put pressure on you :), but would you have any idea if and when Juce will implement gesture recognition on mobile devices?


#2

The only answer I can give is "when possible!" We're going to need it for some stuff we're planning to do with tracktion next year, so it'll certainly happen, but I can't say when.


#3

It looks like I will have to do it myself. :)

Do you have any suggestion about where to start from?


#4

Not in detail.. A gesture recogniser class would use a mouselistener to attach to existing components so that it can trigger things - you don't want to actually have to change any existing code or components. Or there could be an abstract base class that a component can implement which would receive the gestures (similarly to the way the DragAndDropTarget class works). But I've not had chance to think this through in detail yet.


#5

Well, then I realized my solution for iOS. Because in the end it seems really quite simple, I would like to share it, hoping that Jules can use it as a starting point. I know nothing about Android, but I guess it could work similarly.

It has two advantages: it is non-invasive, because you can choose to ignore it completely, and is based on Apple Gesture Recognizers, which may be difficult to accurately emulate or change between versions of the operating system.

I have modified juce_ComponentPeer.h, adding this to 'ComponentPeer' private section:

GestureType currentGesture;

and this to the public section:

enum GestureType
    {
    gesture_None     = 0x00,
    gesture_Pan      = 0x01,
    gesture_Swipe    = 0x02,
    gesture_Pinch    = 0x04,
    gesture_Rotation = 0x08
    };

void    resetCurrentGesture()        { currentGesture = gesture_None; }
void    addGesture(GestureType type) { currentGesture = GestureType(currentGesture | type); }

bool    gesture_Exists()     { return currentGesture != gesture_None; }
bool    gesture_IsPan()      { return((currentGesture & gesture_Pan) == gesture_Pan); }
bool    gesture_IsSwipe()    { return((currentGesture & gesture_Swipe) == gesture_Swipe); }
bool    gesture_IsPinch()    { return((currentGesture & gesture_Pinch) == gesture_Pinch); }
bool    gesture_IsRotation() { return((currentGesture & gesture_Rotation) == gesture_Rotation); }

Then I have modified juce_ios_UIViewComponentPeer.mm:

changing 'JuceUIView' declaration:

@interface JuceUIView : UIView <UITextViewDelegate, UIGestureRecognizerDelegate>

its 'initWithOwner()' method, initializing the gesture recognizer suite:

UIPanGestureRecognizer*      pan   = [[UIPanGestureRecognizer alloc]      initWithTarget:self action:@selector(handlePan:)];
UISwipeGestureRecognizer*    swipe = [[UISwipeGestureRecognizer alloc]    initWithTarget:self action:@selector(handleSwipe:)];
UIPinchGestureRecognizer*    pinch = [[UIPinchGestureRecognizer alloc]    initWithTarget:self action:@selector(handlePinch:)];
UIRotationGestureRecognizer* rot   = [[UIRotationGestureRecognizer alloc] initWithTarget:self action:@selector(handleRotation:)];

self.gestureRecognizers = @[pan, swipe, pinch, rot];

for(UIGestureRecognizer *recognizer in self.gestureRecognizers)
    recognizer.delegate = self;

some of its touchesXXX() methods, adding a gesture reset call at the beginning:

- (void) touchesBegan: (NSSet*) touches withEvent: (UIEvent*) event
{
owner->resetCurrentGesture();

    (void) touches;

    if (owner != nullptr)
        owner->handleTouches (event, true, false, false);
}

- (void) touchesEnded: (NSSet*) touches withEvent: (UIEvent*) event
{
owner->resetCurrentGesture();

    (void) touches;

    if (owner != nullptr)
        owner->handleTouches (event, false, true, false);
}

- (void) touchesCancelled: (NSSet*) touches withEvent: (UIEvent*) event
{
owner->resetCurrentGesture();

    if (owner != nullptr)
        owner->handleTouches (event, false, true, true);

    [self touchesEnded: touches withEvent: event];
}

and finally adding these methods:

- (void) handlePan: (UIPanGestureRecognizer*)recognizer
{
}

- (void) handleSwipe: (UISwipeGestureRecognizer*)recognizer
{
}

- (void) handlePinch: (UIPinchGestureRecognizer*)recognizer
{
}

- (void) handleRotation: (UIRotationGestureRecognizer*)recognizer
{
}

- (BOOL) gestureRecognizer: (UIGestureRecognizer*)recognizer
shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherRecognizer
{
    return YES;
}

- (BOOL) gestureRecognizerShouldBegin: (UIGestureRecognizer*)recognizer
{
if([recognizer isKindOfClass:[UIPanGestureRecognizer class]])
    {
    owner->addGesture(ComponentPeer::gesture_Pan);
    }
else if([recognizer isKindOfClass:[UISwipeGestureRecognizer class]])
    {
    owner->addGesture(ComponentPeer::gesture_Swipe);
    }
else if([recognizer isKindOfClass:[UIPinchGestureRecognizer class]])
    {
    owner->addGesture(ComponentPeer::gesture_Pinch);
    }
else if([recognizer isKindOfClass:[UIRotationGestureRecognizer class]])
    {
    owner->addGesture(ComponentPeer::gesture_Rotation);
    }

return(NO);
}

I have intentionally not included the recognition of "tap" and "long press" gestures, because Juce is already perfectly capable of handling them.

In applications, mouse events continue to be managed with the normal functions of Juce, but now you can know if they occur in the context of a gesture.

In particular, given that gestures recognition takes some time and movement, it is necessary to move part of the intelligence normally inserted inside the mouseDown() method into mouseUp(), and always use a mouseDrag() method to properly handle gestures, drag & drop, selection , etc..

This is a minimal example:

void    myComponent::mouseDown(const MouseEvent& e)
{
beginDragAutoRepeat(75);

GrandDad::instance()->getGestureResponder()->setRecognizedDirections(CGestureResponder::iDir_Y);

f_WasGesture = false;
}

void    myComponent::mouseDrag(const MouseEvent& e)
{
if( GrandDad::instance()->getGestureResponder()->isEnabled() &&
    getPeer()->gesture_Exists())
    {
    f_WasGesture = true;
    return;
    }
}

void    myComponent::mouseUp(const MouseEvent& e)
{
if(!f_WasGesture)
    clickInRow(this, e);
}

which refers to a class GestureResponder, that could be declared in this way:

class CGestureResponder
    : public MouseListener,
      public AnimatedPosition<AnimatedPositionBehaviours::ContinuousWithMomentum>::Listener
{
public:

    enum EDirection
        {
        iDir_None = 0x00,
        iDir_X    = 0x01,
        iDir_Y    = 0x02,
        iDir_Both = 0x03,
        };

private:

    etc.. etc..

Thanks for watching!


#6

I have edited my last message, adding the part concerning the touchesXXX() methods, that was missing!


#7

Thanks, though adding handling for native gestures on every platform is something I'd really like to avoid if possible, as it's just inevitably going to be a bit different on each OS.

dave96 has been doing some work on a mobile app for which he's been building some cross-platform gesture code - I'm hoping to scrounge that from him soon!


#8

Thanks for this. 

Is there anything closer on the horizon in terms of gestures baked into JUCE?

 

 

 


#9

Gestures is one of the items in our big feature list - will hopefully decide on some priorities for things like that soon!


#10

Hi, what is the current state of gestures in Juce?


#11

Sorry, not much happened about gestures yet, though we added some helper classes like AnimatedPosition if you’re doing momentum-based flicking/dragging


#12

Hi - thanks. That’s probably all I’m after at the moment so will take a look… Cheers!


#13

Do any of the demos make use of AnimatedPosition?