Showing posts with label touch. Show all posts
Showing posts with label touch. Show all posts

Wednesday, May 30, 2018

ignore QTapGesture after QTapAndHoldGesture

Leave a Comment

I want to get QTapAndHoldGesture and QTapGesture in my widget and do different thing as reaction on these gestures. So I override QWidget::event method and add such code:

 bool event(QEvent *event) override {     if (event->type() == QEvent::Gesture) {       auto g_event = static_cast<QGestureEvent *>(event);       qDebug() << "GestureEvent BEGIN: gestures " << g_event->gestures().size() << ", active: " << g_event->activeGestures();       if (auto g = qobject_cast<QTapGesture *>(g_event->gesture(Qt::TapGesture))) {         g_event->accept(g);         return true;       }       if (auto g = qobject_cast<QTapAndHoldGesture *>(g_event->gesture(Qt::TapAndHoldGesture))) {         if (g->state() == Qt::GestureFinished) {                 qDebug("FINISHED!!!");                 g->setGestureCancelPolicy(QGesture::CancelAllInContext);         }         g_event->accept(g);         return true;       } 

The problem is that I get not wanted QTapGesture at the end of QTapAndHoldGesture.

It looks like this:

GestureEvent BEGIN: gestures  1 , active:  (QTapGesture(state=GestureStarted,hotSpot=773.396,492.884,position=773.396,492.884)) GestureEvent BEGIN: gestures  1 , active:  (QTapGesture(state=GestureUpdated,hotSpot=773.396,492.884,position=773.396,492.884)) mouse event x  773 , y  493 GestureEvent BEGIN: gestures  1 , active:  (QTapGesture(state=GestureUpdated,hotSpot=773.396,492.884,position=773.396,492.884)) ... GestureEvent BEGIN: gestures  1 , active:  (QTapGesture(state=GestureUpdated,hotSpot=773.396,492.884,position=773.396,492.884)) GestureEvent BEGIN: gestures  1 , active:  (QTapAndHoldGesture(state=GestureStarted,hotSpot=773,493,position=773,493,timeout=700)) GestureEvent BEGIN: gestures  1 , active:  (QTapAndHoldGesture(state=GestureFinished,hotSpot=773,493,position=773,493,timeout=700)) FINISHED!!! GestureEvent BEGIN: gestures  1 , active:  (QTapGesture(state=GestureUpdated,hotSpot=773.396,492.884,position=773.396,492.884)) GestureEvent BEGIN: gestures  1 , active:  (QTapGesture(state=GestureUpdated,hotSpot=773.396,492.884,position=773.396,492.884)) GestureEvent BEGIN: gestures  1 , active:  (QTapGesture(state=GestureFinished,hotSpot=773.396,492.884,position=773.396,492.884)) 

As you see at start I got QTapGesture in started state, then QTapGesture in updated state, after that QTapAndHoldGesture and after that QTabGesture finished.

I need some way to ignore it. But I don't see how without re-implementing of gesture framework: collect position and time of event and filter events based on this information. Because of I receive gestures one by one and can not connect QTapGesture and QTapAndHoldGesture.

So is it possible to ignore QTapGesture after QTapAndHoldGesture without collecting information about position and time of QGestureEvent?

1 Answers

Answers 1

Because QTapAndHoldGesture gesture also requires a "Tap", it is expected to receive both:

  • QTapAndHoldGesture first, when the users holds for a certain time (end of a timer)
  • QTapGesture then, when the user releases the touch

Because they will always be received in that order, you can use that fact, to filter or cancel the QTapGesture which will be started, but not finished, when you receive QTapAndHoldGesture is validated (i.e. finished).

No need for time or position information, if you are managing a single touch point (Disclamer: The following is untested).

bool MyClass::event(QEvent *event) override {     // QPointer in case we receive partial events. Should remove "isNull()" keys at some point.     static QMap<QPointer<QTapGesture*>, bool> tapGestures;     if (event->type() != QEvent::Gesture)         return QQuickItem::event(event);      auto g_event = static_cast<QGestureEvent *>(event);     if (auto g = qobject_cast<QTapGesture *>(g_event->gesture(Qt::TapGesture))) {         // A TapAndHold was triggered during that tap... let's ignore it         if (tapGestures.value(g))             g_event->ignore(g); // Or handle as you like          if (g->state() == Qt::GestureFinished || g->state() == Qt::GestureCancelled)             tapGestures.remove(g);         else if (!tapGestures.contains(g))             tapGestures.insert(g, false);          g_event->accept(g);         return true;     }      if (auto g = qobject_cast<QTapAndHoldGesture *>(g_event->gesture(Qt::TapAndHoldGesture))) {         // Probably not needed if the gesture handle doesn't conflict with another component         //if (g->state() == Qt::GestureFinished)         //    g->setGestureCancelPolicy(QGesture::CancelAllInContext);          // Mark all QTapGesture in progress to be ignored         for (auto it = tapGestures.begin(); it != tapGestures.end(); ++it)             it.value() = true;          g_event->accept(g);         return true;     } } 

All gestures are available in QGestureManager class, so there may be a way to access it.

There is also the GestureOverride event type, but I believe that in your case it will not be triggered.

Read More

Friday, April 6, 2018

Google Maps Drawing on Mobile loses Draggable

Leave a Comment

I am trying to use google maps to allow a user to create multiple editabled/draggable polygons. Everything works fine from a desktop browser, but on a mobile/touch device all polygons are no longer draggable once a user adds a new polygon to the map. Polygons loaded on the map during initialize are draggable as expected.

I know touch events are not really supported by the google maps api, but is there a workaround I can do?

Simplified example:

function initMap() {   var map = new          google.maps.Map(document.getElementById('map'), {   zoom: 5,   center: {lat: 24.886, lng: -70.268},   mapTypeId: 'terrain' });  var triangleCoords = [   {lat: 25.774, lng: -80.190},   {lat: 18.466, lng: -66.118},   {lat: 32.321, lng: -64.757},   {lat: 25.774, lng: -80.190} ];  // Construct the polygon. var bermudaTriangle = new google.maps.Polygon({   paths: triangleCoords,   strokeColor: '#FF0000',   strokeOpacity: 0.8,   strokeWeight: 2,   fillColor: '#FF0000',   fillOpacity: 0.35,   draggable: true }); bermudaTriangle.setMap(map);  var drawingManager = new google.maps.drawing.DrawingManager({    drawingMode: google.maps.drawing.OverlayType.MARKER,    drawingControl: true,    drawingControlOptions: {      position: google.maps.ControlPosition.TOP_CENTER,      drawingModes: ['polygon']    },    polygonOptions: {      fillColor: '#ffff00',      fillOpacity: 1,      strokeWeight: 5,      draggable: true,      zIndex: 10000     }  });  drawingManager.setMap(map);   google.maps.event.addListener(drawingManager, 'overlaycomplete', function (e) {    drawingManager.setOptions({       drawingMode: null    });  });    }  google.maps.event.addDomListener(window, 'load', initMap); 

https://codepen.io/cyberjus/pen/XEVoer

Upon load from a mobile device, the first polygon is draggable, but once another polygon is drawn, neither can be dragged anymore. I am testing on an actual iPad, so I dont know if it is IOS specific or not.

0 Answers

Read More

Saturday, September 23, 2017

Interactivity and touch with Firefox on Microsoft Surface devices?

Leave a Comment

On Microsoft touch devices (such as the Surface Pro), on Chrome and IE it is possible to capture mouse/pointer/touch events, and in the process, prevent scrolling the page.

On Firefox, getting the same level of activity while stopping the page from scrolling with the touch seems impossible. You can stop the page from scrolling by preventing "wheel":

can.addEventListener('wheel', function(e) {   console.log('stopping wheel')   e.preventDefault(); }, false); 

But Firefox does not seem to emit mouse/pointer/touch events that you can listen for, so you cannot do the same actions.

There's a live example here:

https://codepen.io/simonsarris/pen/PJwMay

With touch on a Surface: You can draw on the canvas in Chrome and IE, but you cannot draw on it in Firefox. If you comment out the "wheel" listener, Firefox will additionally scroll the page.

So the question is: What is needed to get HTML Element touch interactivity in Firefox, on parity with the other browsers on the system?

1 Answers

Answers 1

Consider using Touch Events. And its supported across browsers(Firefox, Chrome, Edge).

Solution is simple, Handle the Touch Events and prevent defaults.

Consider this example-

function startup() {   var el = document.getElementsByTagName("canvas")[0];   el.addEventListener("touchstart", handleStart, false);   el.addEventListener("touchend", handleEnd, false);   el.addEventListener("touchcancel", handleCancel, false);   el.addEventListener("touchmove", handleMove, false);   log("initialized."); }  element.addEventListener('touchstart', function(e){     var touchobj = e.changedTouches[0] // reference first touch point (ie: first finger)     startx = parseInt(touchobj.clientX) // get x position of touch point relative to left edge of browser     statusdiv.innerHTML = 'Status: touchstart<br> ClientX: ' + startx + 'px'     e.preventDefault() }, false) 

Source : MDN - Touch Events

Simple demo for Touch Events can be found here(mdn) or here(jsdfiddle)

Check this example which would be much more complete(Reference/Demo)-

<div class="box" id="box1"> <h3> Touch Me! </h3> </div>  <h3 id="statusdiv">Status</h3>  <script>  window.addEventListener('load', function(){      var box1 = document.getElementById('box1')     var statusdiv = document.getElementById('statusdiv')     var startx = 0     var dist = 0      box1.addEventListener('touchstart', function(e){         var touchobj = e.changedTouches[0] // reference first touch point (ie: first finger)         startx = parseInt(touchobj.clientX) // get x position of touch point relative to left edge of browser         statusdiv.innerHTML = 'Status: touchstart<br> ClientX: ' + startx + 'px'         e.preventDefault()     }, false)      box1.addEventListener('touchmove', function(e){         var touchobj = e.changedTouches[0] // reference first touch point for this event         var dist = parseInt(touchobj.clientX) - startx         statusdiv.innerHTML = 'Status: touchmove<br> Horizontal distance traveled: ' + dist + 'px'         e.preventDefault()     }, false)      box1.addEventListener('touchend', function(e){         var touchobj = e.changedTouches[0] // reference first touch point for this event         statusdiv.innerHTML = 'Status: touchend<br> Resting x coordinate: ' + touchobj.clientX + 'px'         e.preventDefault()     }, false)  }, false)  </script> 

References:

  • MDN Touch Events here
  • Using Touch Events here
  • Introduction to Touch events + Scroll + Drag here
  • Detecting Swipe using touch here
  • Swipe Image gallery here
  • Online Paint demo | code This is good!
  • Touch events in detail here

Hope it helps.

Read More

Wednesday, August 23, 2017

Dynamically disabling touch-action (overscroll) for SVG elements

Leave a Comment

I'm having problems with touch screen overscrolling on Chrome.

I have a document with am SVG element in it, which contains some shape, say a rectangle:

enter image description here

Now, I want to make the rectangle draggable, which means that I want to disable all kinds of touch actions on the respective <rect> element, by setting it's style property touch-action: none.

This works fine on all desktop browsers, except Chrome. On Chrome, when I touch and move on a rectangle, browser's overscroll feature kicks in. This causes the browser window to awkwardly move, as well as all Pointer events that I have set on the rectangle cancelled.

I.e. pointermove is registered for a fraction of second, then it just stops when overscroll kicks in. pointerup is never called even when touch is released.

enter image description here

Now, if I had an HTML element instead of an SVG element, setting touch-action: none would do the job. The same thing on an SVG element fails.

Technically, this could be solved by either setting touch-action: none on document.body or wrapping the whole SVG into a <div> element with touch-action: none set.

Unfortunately, that is not an option for me since I need the document (and rest of the rectangle-surrounding SVG) to retain all of its original touch gestures, except when directly on a rectangle.

As a solution, I did try to dynamically set touch-action: none on document: body when pointerdown event occurs on a rectangle.

// Get element var o = document.getElementById( "test" );  // disable touch action on press of the SVG element o.addEventListener( "pointerdown", function(e) {   document.body.style.touchAction = "none"; } );  // re-enable touch action when released o.addEventListener( "pointerup", function(e) {   document.body.style.touchAction = "auto"; } ); 

Unfortunately, that does not help. The style on body gets set and next time I try dragging rectangle it works as expected (because pointerup event never gets executed), just not this time.

Adding preventDefault() to event handlers has also no effect.

I would appreciate if someone who have had similar experience could share a solution.

Here's a live example of the above.

// Get element  var o = document.getElementById( "test" );    // disable touch action on press of the SVG element  o.addEventListener( "pointerdown", function(e) {    document.body.style.touchAction = "none";  } );    // re-enable touch action when released  o.addEventListener( "pointerup", function(e) {    document.body.style.touchAction = "auto";  } );
<svg id="test" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="width: 300px; height: 300px; border: 1px solid #eee;">    <g transform="translate(50,50)">      <rect fill="#00fff0" width="200" height="200" style="touch-action: none;"></rect>    </g>  </svg>


UPDATE: looks like using preventDefault() on an touchstart event does the trick.

However, it seems to be wrong to use both modern pointerdown and legacy touchstart at the same time. This seems like a bug in Chrome 60. If anyone could confirm it, that would be great.

1 Answers

Answers 1

I think it would be enough by preventing touchstart:

window.addEventListener( "touchstart", function(e) {   // Determine wether or not you are panning from rectangle:   if (e.target ....)       e.preventDefault(); } ); 
Read More

Sunday, August 20, 2017

Rotate android layout view about its center using touch listener

Leave a Comment

Here is how my set up to rotate looks.

enter image description here

I am listening for action down and move on the rotation icon, trying to calculate the angle of rotation and applying that angle of rotation to entire layout encapsulating the edittext. The applied rotation has to be about the center of encapsulating relative layout.

Here is how I am trying to achieve this, in the touchListener for rotation icon.

if(v == ivRotateSticker){           //TODO grab xc and yc using (top and bottom)/2 and (left and right)/2 to grab center of sticker after each rotation         final float x = event.getX();         final float y = event.getY();         rlTextStickerLayout.getGlobalVisibleRect(myViewRect);         xc = myViewRect.width()/2;         yc = myViewRect.height()/2;          dx = scaleReceiver.getWidth() - myViewRect.width();         dy = scaleReceiver.getHeight() - myViewRect.height();          leftBound = scaleReceiver.getX();         topBound = scaleReceiver.getY();          switch (event.getAction()){              case MotionEvent.ACTION_DOWN:                 mViewAngle = rlTextStickerLayout.getRotation();                 mPrevAngle = Math.toDegrees(Math.atan2(x - xc, yc - y));                  break;              case MotionEvent.ACTION_MOVE:                  Log.d("EventRegistered", " " + event.getRawX() + "--" + dx + " -- " + dy);                  mCurrAngle = Math.toDegrees(Math.atan2(x - xc, yc - y));                 if (x >= leftBound && y >= topBound && x <= dx && y <= dy) {                     float rot = (float) (mViewAngle + mCurrAngle - mPrevAngle);                     rlTextStickerLayout.setRotation((float) (mViewAngle + mCurrAngle - mPrevAngle));                     Log.d("stickerRotation"," "+rot);                 }                 break;          }          return true;      } 

But the resulting rotation is glitchy and not as desired smooth. Also, if I move the sticker to a different location, how do I update the new center for rotation, as rotation after moving the sticker dramatically changes the center of rotation. How can I achieve a far more smooth rotation?

2 Answers

Answers 1

  1. View's rotation to be animated to the specified values (90, 180, 270) in 1000ms:

    view.animate().rotation(90f).setDuration(1000).start(); view.animate().rotation(180f).setDuration(1000).start(); view.animate().rotation(270f).setDuration(1000).start();

  2. Using this library you can rotate whole view hierarchy https://github.com/rongi/rotate-layout

Answers 2

I think this library will help you a lot.

https://github.com/kencheung4/android-StickerView

its smooth and easy.

Read More

Wednesday, April 6, 2016

Touch screen stops responding, but only on rails collection_select dropdown

Leave a Comment

I have a collection_select in my rails view that displays a list of cars from the database. The web app is displayed (using Chrome) on a laptop that is connected to an Acer touch-screen monitor, which works well. I am having a problem where the cars collection_select field in my form will stop responding to the touch screen. I have done a ton of trouble-shooting, but I can't seem to isolate the issue.

  • The collection_select menu opens, but it will not scroll and you cannot select a car from the touch-screen monitor
  • The rest of the same browser page still responds to the touch screen
  • The collection_select works if you use the laptop's trackpad
  • Closing and re-opening the browser fixes the problem (until it randomly happens again)
  • Turning the touch-screen off and back on does not fix the problem

Does anyone have any ideas as to what might be causing this?

The app is using Rails 4.2, Bootstrap 3 and simple_form.

0 Answers

Read More