cinder / Cinder

Cinder is a community-developed, free and open source library for professional-quality creative coding in C++.
http://libcinder.org
Other
5.34k stars 943 forks source link

Store touch phase on `TouchEvent` and / or `TouchEvent::Touches`? #2015

Open richardeakin opened 6 years ago

richardeakin commented 6 years ago

I'm in a situation where I'm doing some more involved touch event processing, where I'm storing the Event to handle it within the next update loop. In this case, it would be handy to know what the 'phase' is of the event, similar to iOS's UITouch.phase property.

I think this information would be fairly easy to set as an additional enum value on the TouchEvent itself. Not sure I can see the need to store the phase on the Touch itself, which is where both UITouch and NSTouch store it (and I believe from reading our MSW impl code that it is per touch too). Do others have an opinion here?

One other thing that I think would be nice about knowing the phase, is that is some cases it is nice to convey that a touch has been 'cancelled' rather than just 'ended'. If we were to add a phase enum we could include this information here without needing an additional virtual method + signal.

paulhoux commented 6 years ago

I'm not familiar with touch phases. Could you give an example?

richardeakin commented 6 years ago

The common phases are 'began', 'moved', and 'ended'. UIkit / Appkit also define 'stationary' and 'cancelled' (which you can see descriptions of here.

I think the three common ones would also map to MouseEvent phases if needed, being 'down', 'drag', and 'up'.

paulhoux commented 6 years ago

Ah, OK, I get it.

Would you consider doing the following?

// Current definition of MouseEvent in Cinder. Will serve as a base class.
class ci::app::MouseEvent : public ci::app::Event { ... };

// Derived class will designate phase:
class ci::app::MouseMoveEvent  : public ci::app::MouseEvent { ... }; 
class ci::app::MouseDownEvent  : public ci::app::MouseEvent { ... };
class ci::app::MouseDragEvent  : public ci::app::MouseEvent { ... };
class ci::app::MouseUpEvent    : public ci::app::MouseEvent { ... };
class ci::app::MouseWheelEvent : public ci::app::MouseEvent { ... };

This is how I have done it in my toolkit, making it possible to handle events based on their type using templates (at compile time, as long as the class and its derived class have no virtual functions).

template <typename T>
bool Node::sendEvent( const T &event ) const
{
    const auto &eventId = typeid( T ).hash_code();

    if( mEvents.count( eventId ) == 0 )
        return false;

    auto &listeners = mEvents.at( eventId );
    if( listeners && !listeners->empty() )
        return listeners->call<T>( event );
}

// Example of adding listeners:
connectEvent<MouseDownEvent>( &MyClass::onMouseDown, this );

You can still use void mouseDown( MouseEvent event ); (unless I am too tired to remember that polymorphism only works with pointers and references), but you can also use void mouseDown( MouseDownEvent event );.

If an event needs more data to describe something, it can do so in its own private member variables. The generic stuff goes into the base class. We'd do something similar for touch events, key events, filedrop events, etc.

richardeakin commented 6 years ago

I think this is a bit of a different design than what cinder currently uses, whichs maps pretty close to UIKit / App Kit already. It'd also require storing active touches in separate containers (one for each type) or as shared pointers, and we'd have to change public API like Window::getActiveTouches().

I feel like the data in the mouse or touch events are all consistent across the different phases, so I do feel like apple has a nice design on this one where they use a single mouse or touch event type that contains a phase property. Also, phase would give us extra room for cancelled and possible stationary, which I'm finding more and more useful when building out UI controls.

paulhoux commented 6 years ago

I see. Yeah, a phase property would be a solution. Are you thinking of something like:

class MouseEvent : public Event {
  public:
    enum class Phase { Move, Down, Drag, Up, Wheel };

    ...

    Phase getPhase() const { return mPhase; }

  private:
    Phase mPhase;
}

This would work in my scenario as well. I'd do a connectEvent<MouseEvent>( &MyClass::onMouse, this ); and the function would look like:

bool onMouse( const MouseEvent &event )
{
    switch( event.getPhase() )
    {
        case MouseEvent::Move: ... break;
    }

    return false; // Specific to my code: return true to capture event, false to continue propagating it.
}