OTAPI

Beneath the hood of RAD Studio

TLKPrecisionThread Properties

23rd October 2014
by Simon J Stuart
0 comments

Threading Evolved… again – High Precision Thread as a Component

LKSL

This article uses the LaKraven Studios Standard Library[LKSL]. If you wish to follow along with the code snips provided herein, you should download the latest version from the GitHub repository, and run the LKSLInstaller.exe program to configure the RAD Studio IDE so that you can easily use the LKSL units in your programs.

You will also need to install the Component Package provided within that same repository.

Introduction

In a previous article, I delved into the world of High Precision Threads as defined by the LaKraven Studios Standard Library.

I won’t repeat the entire article here (because you can read the original article for that) but as a brief summary, the article explained and demonstrated a special kind of Thread, designed to provide incredibly high-precision interpolation and extrapolation for a looped Threaded process.

Well, I have now added something new to the LKSL, which makes exploiting this special form of Threading much easier…

TLKPrecisionThread Component

When you install the Component Package provided within the LKSL repository, you’ll see a new entry on the component pallet called [LKSL] Threads.

TLKPrecisionThread Pallet

TLKPrecisionThread Pallet

TLKPrecisionThread works on both VCL and FireMonkey projects, on all platforms presently supported by Delphi: Win32, Win64, Mac, iOS and Android.

When you drop a TLKPrecisionThread component on a Form (or Data Module) you can simply double-click on that instance to implement an OnTick event.

Because TLKPrecisionThread is a Component intended to serve as an alternative to TTimer with numerous advantages over TTimer, and operating on a principal of Ticks Per Second rather than an Interval, the OnTick event callback is synchronized on the UI thread by design. If you don’t want the OnTick call to be synchronized, then you should implement a custom descendant of TLKThread entirely in code (as demonstrated in the previous article).

Properties

TLKPrecisionThread Properties

TLKPrecisionThread Properties

As you can see from this image (above), most of the Properties are read-only. This is because they provide calculated metrics for the performance of the Thread.

TickRateAverageOver, TickRateDesired and TickRateLimit are read-write properties, which influence the way the Thread operates, thus the way the metrics are calculated.

The properties represent (verbatim) their counterpart properties from TLKThread (and are discussed in more detail in the previous article).

The Active property toggles the Thread on and off (just like TTimer). The implementation is designed to prevent the Active property from initiating the Thread at design-time (otherwise the IDE will eat up an entire CPU core, which would be a bad thing).

Something to be careful of

If your TLKPrecisionThread instance communicates directly with a Form, Module, or other components thereon… you may wish to add an OnClose implementation to deactivate the TLKPrecisionThread instance to prevent OnTick from executing at the same time as the destructor.

Summary

In short, TLKPrecisionThread functions as a Threaded Timer, allowing you to specify the number of Ticks (or “iterations”) per second at which you would like it to operate. A Tick Rate Limit of 0 (zero) instructs the Thread to operate as quickly as it physically can, unlike TTimer‘s Interval property (where a setting of zero effectively disables the timer entirely).

If you require a simple alternative to TTimer which provides vastly superior precision, and provides useful performance metrics, TLKPrecisionThread is going to be useful to you.

Questions and feedback are always appreciated, and thanks for reading!

Multi-Touch Playground - Moving Controls

13th October 2014
by Simon J Stuart
1 Comment

Working with Multi-Touch Input in Delphi XE7 (Part 2)

Previously…

In the first part of this series of articles, we took a look at how the OnTouch event introduced in XE7 works, and discovered some potential stumbling blocks when it comes to taking advantage of this new feature.

In this part of the series, we’re going to look at potential “workarounds” for these problems; alternative (albeit slightly more complicated) methods of harnessing multi-touch input information for practical purposes in Delphi applications.

Another “quirk” in the way XE7’s Multi-Touch Input Handler Works

Since posting the first article in this series, I have conducted many more tests, and discovered that it continues to track Touch Move and Touch Up events even after one or more Touch Points leave the constraints of your Form.

Now, this shouldn’t necessarily be considered a “bug” or even a problem, as it means we can (in theory anyway) use multi-touch input to perform “drag and drop” operations on Windows (possibly also Mac) beyond the constraints of your Form(s).

It is, of course, fairly trivial to determine whether or not a Touch Point is inside the constraints of your Form.

I just wanted to share this observation for the benefit of others, as it may just effect the way you work with multi-touch input in your applications.

Detecting when a Multi-Touch Input Event Begins and Ends

We previously discovered that the Touch Down action seems to occur once for each additional Touch Point (finger) introduced to the touch screen (effectively making that action “re-entrant”), yet the Touch Up action doesn’t necessarily occur separately for each Touch Point removed from the touch screen.

So, since the OnTouch event behaves in a somewhat-inconsistent way, we need an inventive solution to decide when a multi-touch input event begins and ends.

Since we know that a multi-touch input event will begin with a Touch Down action, we can define a Boolean member to act as a “flag”. Also, we can say with relative certainty that a multi-touch input event will end either with a Touch Up action, or a Touch Cancel action, and I’ve determined that, where it ends with a Touch Up action, that action will contain either one or zero items in the Touch Point array (Touches parameter).

This means we have a logical principle on which to base our multi-touch input event detection.

unit Unit2;

interface

uses
  System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
  FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, FMX.StdCtrls;

type
  TForm2 = class(TForm)
    Label1: TLabel;
    procedure FormCreate(Sender: TObject);
    procedure FormTouch(Sender: TObject; const Touches: TTouches; const Action: TTouchAction);
  private
    FTouching: Boolean;
    FTouchHistory: Array of TTouches;
    procedure TouchNone(Sender: TObject; const Touches: TTouches);
    procedure TouchUp(Sender: TObject; const Touches: TTouches);
    procedure TouchDown(Sender: TObject; const Touches: TTouches);
    procedure TouchMove(Sender: TObject; const Touches: TTouches);
    procedure TouchCancel(Sender: TObject; const Touches: TTouches);
  public
    { Public declarations }
  end;

var
  Form2: TForm2;

implementation

{$R *.fmx}

procedure TForm2.FormCreate(Sender: TObject);
begin
  FTouching := False;
end;

procedure TForm2.FormTouch(Sender: TObject; const Touches: TTouches; const Action: TTouchAction);
begin
  case Action of
    TTouchAction.None: TouchNone(Sender, Touches);
    TTouchAction.Up: TouchUp(Sender, Touches);
    TTouchAction.Down: TouchDown(Sender, Touches);
    TTouchAction.Move: TouchMove(Sender, Touches);
    TTouchAction.Cancel: TouchCancel(Sender, Touches);
  end;
end;

procedure TForm2.TouchCancel(Sender: TObject; const Touches: TTouches);
begin
  FTouching := False;
  Label1.Text := 'Touch Cancelled!';
end;

procedure TForm2.TouchDown(Sender: TObject; const Touches: TTouches);
begin
  if (not FTouching) then
  begin
    FTouching := True;
    Label1.Text := Format('Touch Points: %d', [Length(Touches)]);
  end;
end;

procedure TForm2.TouchMove(Sender: TObject; const Touches: TTouches);
begin
  Label1.Text := Format('Touch Points: %d', [Length(Touches)]);
end;

procedure TForm2.TouchNone(Sender: TObject; const Touches: TTouches);
begin
  // I genuinely don't know what this is for!
end;

procedure TForm2.TouchUp(Sender: TObject; const Touches: TTouches);
begin
  if (FTouching) then
  begin
    if Length(Touches) <= 1 then
    begin
      FTouching := False;
      Label1.Text := 'Not Touching';
    end else
      Label1.Text := Format('Touch Points: %d', [Length(Touches)]);
  end;
end;

end.

This code snip (above) works on Win32, Win64, Mac, Android and iOS. It illustrates a way of determining when an individual multi-touch input event begins and ends (in this case, by displaying the current “Touch State” on a TLabel control on the Form).

Pay special attention to the TouchUp procedure, which uses a simple if statement to determine whether or not we should consider the multi-touch input event as completed.

Put simply, while we won’t necessarily always get a Touch Up action when each separate Touch Point is lifted from the screen, we always get at least one (assuming that we don’t get a Touch Cancel event instead, I mean), and this final Touch Up action will only ever contain either one or zero Touch Points in the Touches array.

This gives us a very simple workaround for that inconsistent behaviour, one which (according to my extensive testing, at least) appears to work 100% of the time.

The FTouching boolean member plays a fairly important role, in that it prevents TouchDown from acting in a re-entrant manner. It would be problematic (to say the least) if we allowed the TouchDown procedure to perform its function multiple times during an existing Touch Event… particularly when you consider a “drag and drop” operation as an example.

I also just want to point out that I have been completely unable to get TouchNone to occur, on any platform, under any set of circumstances. At this point, I have genuinely no idea what that particular Touch Action Type is for, or under what set of bizarre circumstances it will occur.

You can download the working demo (tidied up a lot) of this code snip here.

Something Fun: Moving Controls At Runtime (using multi-touch input)

Okay, now that we’ve solved the problem of adequately detecting when a multi-touch event starts and ends, let’s put this solution to use and make something entertaining.

What we’re going to do now is make a little “multi-touch playground” project, allowing us to drag multiple controls around the screen at the same time with individual fingers on a touch screen. At face value, this little “toy project” might not seem all that useful, however this same principal can be applied to a simultaneous drag-and-drop solution that could be useful.

 

Multi-Touch Playground - Moving Controls

Multi-Touch Playground – Moving Controls

Basically, with this demo we can simply drop a bunch of controls onto the Form, and use multi-touch input to move one or more of those controls around.

Here’s the code for this demo:

unit uMainForm;

interface

uses
  System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
  FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, FMX.Objects, FMX.StdCtrls;

type
  TControlTouch = record
    Control: TControl;
    Origin: TPointF;
  end;

  TfrmMain = class(TForm)
    Rectangle1: TRectangle;
    RoundRect1: TRoundRect;
    Ellipse1: TEllipse;
    Pie1: TPie;
    Label1: TLabel;
    Label2: TLabel;
    procedure FormTouch(Sender: TObject; const Touches: TTouches; const Action: TTouchAction);
  private
    FTouching: Boolean;
    FControls: Array of TControlTouch;
    function GetControlAtPoint(const APoint: TPointF): TControl;
    procedure GetControlsAtEachTouchPoint(const Touches: TTouches);
    procedure TouchEnd;
    procedure TouchNone(Sender: TObject; const Touches: TTouches);
    procedure TouchUp(Sender: TObject; const Touches: TTouches);
    procedure TouchDown(Sender: TObject; const Touches: TTouches);
    procedure TouchMove(Sender: TObject; const Touches: TTouches);
    procedure TouchCancel(Sender: TObject; const Touches: TTouches);
  public
    { Public declarations }
  end;

var
  frmMain: TfrmMain;

implementation

{$R *.fmx}

procedure TfrmMain.FormTouch(Sender: TObject; const Touches: TTouches; const Action: TTouchAction);
begin
  case Action of
    TTouchAction.None: TouchNone(Sender, Touches);
    TTouchAction.Up: TouchUp(Sender, Touches);
    TTouchAction.Down: TouchDown(Sender, Touches);
    TTouchAction.Move: TouchMove(Sender, Touches);
    TTouchAction.Cancel: TouchCancel(Sender, Touches);
  end;
end;

function TfrmMain.GetControlAtPoint(const APoint: TPointF): TControl;
var
  I: Integer;
  LObject: TControl;
  LRect: TRectF;
begin
  Result := nil;
  for I := ChildrenCount - 1 downto 0 do
  begin
    if (Children[I] is TControl) then
      if TControl(Children[I]).HitTest then
      begin
        LObject := TControl(Children[I]);
        LRect := RectF(LObject.Position.X,
                       LObject.Position.Y,
                       LObject.Position.X + LObject.Width,
                       LObject.Position.Y + LObject.Height);
        if LRect.Contains(APoint) then
        begin
          Result := LObject;
          Break;
        end;
      end;
  end;
end;

procedure TfrmMain.GetControlsAtEachTouchPoint(const Touches: TTouches);
var
  I: Integer;
begin
  // Hold a reference to whatever controls are under each respective touch point
  SetLength(FControls, Length(Touches));
  for I := Low(Touches) to High(Touches) do
  begin
    FControls[I].Control := GetControlAtPoint(Touches[I].Location);
    FControls[I].Origin := Touches[I].Location;
  end;
end;

procedure TfrmMain.TouchCancel(Sender: TObject; const Touches: TTouches);
begin
  TouchEnd;
end;

procedure TfrmMain.TouchDown(Sender: TObject; const Touches: TTouches);
begin
  if (not FTouching) then
  begin
    FTouching := True;
    // Release any existing history
    SetLength(FControls, 0);
  end;
  GetControlsAtEachTouchPoint(Touches);
end;

procedure TfrmMain.TouchEnd;
begin
  FTouching := False;
end;

procedure TfrmMain.TouchMove(Sender: TObject; const Touches: TTouches);
var
  I: Integer;
  LDifference: TPointF;
begin
  if Length(Touches) = Length(FControls) then
  begin
    // Move the controls
    for I := Low(Touches) to High(Touches) do
      if FControls[I].Control <> nil then
      begin
        LDifference := PointF(Touches[I].Location.X - FControls[I].Origin.X,
                              Touches[I].Location.Y - FControls[I].Origin.Y);
        FControls[I].Control.Position.X := FControls[I].Control.Position.X + LDifference.X;
        FControls[I].Control.Position.Y := FControls[I].Control.Position.Y + LDifference.Y;
        FControls[I].Origin := Touches[I].Location;
      end;
  end else
    GetControlsAtEachTouchPoint(Touches);
end;

procedure TfrmMain.TouchNone(Sender: TObject; const Touches: TTouches);
begin

end;

procedure TfrmMain.TouchUp(Sender: TObject; const Touches: TTouches);
begin
  if (FTouching) and (Length(Touches) <= 1) then
    TouchEnd
  else
    GetControlsAtEachTouchPoint(Touches);
end;

end.

This code snip (above) obtains a handle on whatever control is underneath each Touch Point, and basically moves them by their respective offsets on each Touch Move event.

Please note that this demo source observes the value of each control’s HitTest property, so if you set HitTest to False, you won’t be able to drag it around (such as the two TLabel controls included in the downloadable sample)

When the number of touch points changes, it re-establishes the Array of relevant controls for each Touch Point.

As with all the other demos in this series of articles, this will work on Windows, Mac, iOS and Android.

You can download the working demo of this code snip here.

Coming up next…

In the next part of this article, we’ll take a look at solving the “Order of Contact” problem (discussed in the first part of this series) and take a look at some other fun (and useful) uses for multi-touch input.

FireMonkey Form's OnTouch Event

7th October 2014
by Simon J Stuart
4 Comments

Working with Multi-Touch Input in Delphi XE7 (Part 1)

Introduction

Before the release of XE7, handling multi-touch inputs in Delphi (and C++ Builder) applications was quite a complicated affair. While Delphi 2010 introduced “Gesture” support, this solution was less than ideal for real-time multi-touch input handling, as an action bound to a Gesture only executed after the Gesture had been completed, with no simple method of directly intercepting individual touch points to integrate custom behaviour.

Fortunately, XE7 solves this problem by giving us direct access to real-time multi-touch input data. With this data, we can implement custom touch handling for individual points, including our own Gesture detection solutions.

Best of all, it would appear that this new multi-touch feature works identically across all platforms presently supported by Delphi (Win32, Win64, Mac, Android and iOS).

The “OnTouch” Property

FireMonkey Form's OnTouch Event

FireMonkey Form’s OnTouch Event

By hooking the OnTouch Event of a FireMonkey Form, we can begin to implement our own multi-touch input handler.

procedure TForm2.FormTouch(Sender: TObject; const Touches: TTouches; const Action: TTouchAction);
begin

end;

As you can see from this code snip (above), the OnTouch event provides us with several key parameters:

  • Sender is of course a reference to the Control from whence the OnTouch event was executed.
  • Touches provides us with an Array of Point Information representing each individual touch point.
  • Action tells us whether our touch points have ended (Up), begun (Down), are in motion (Move), have been “cancelled” (Cancel), or something unknown and crazy (None).

You may notice that the multi-touch input handling is handled very differently from keyboard and mouse inputs, where we’re provided with OnKeyDown, OnKeyUp, OnMouseDown, OnMouseMove and OnMouseUp events respectively. I am unsure as to why Embarcadero chose to provide a single “catch-all” event property for all Touch input actions, but we can quite easily separate the singular OnTouch event into its discrete actions of influence.

type
  TForm2 = class(TForm)
    procedure FormTouch(Sender: TObject; const Touches: TTouches; const Action: TTouchAction);
  private
    procedure TouchNone(Sender: TObject; const Touches: TTouches);
    procedure TouchUp(Sender: TObject; const Touches: TTouches);
    procedure TouchDown(Sender: TObject; const Touches: TTouches);
    procedure TouchMove(Sender: TObject; const Touches: TTouches);
    procedure TouchCancel(Sender: TObject; const Touches: TTouches);
  public
    { Public declarations }
  end;

var
  Form2: TForm2;

implementation

{$R *.fmx}

procedure TForm2.FormTouch(Sender: TObject; const Touches: TTouches; const Action: TTouchAction);
begin
  case Action of
    TTouchAction.None: TouchNone(Sender, Touches);
    TTouchAction.Up: TouchUp(Sender, Touches);
    TTouchAction.Down: TouchDown(Sender, Touches);
    TTouchAction.Move: TouchMove(Sender, Touches);
    TTouchAction.Cancel: TouchCancel(Sender, Touches);
  end;
end;

procedure TForm2.TouchCancel(Sender: TObject; const Touches: TTouches);
begin

end;

procedure TForm2.TouchDown(Sender: TObject; const Touches: TTouches);
begin

end;

procedure TForm2.TouchMove(Sender: TObject; const Touches: TTouches);
begin

end;

procedure TForm2.TouchNone(Sender: TObject; const Touches: TTouches);
begin

end;

procedure TForm2.TouchUp(Sender: TObject; const Touches: TTouches);
begin

end;

So we now have (in the code snip above) a separate procedure defined for each multi-touch input action, and a simple “case-of” statement passes the appropriate parameter data along to the appropriate handling procedure. All very basic stuff, so let’s move on…

How Delphi handles Touch Actions

At this point, it is critical to understand when the OnTouch event is called, and what Touch Action is taking place.

Since you can introduce one or more Touch Points subsequent to the start of a one or more previous Touch Points, and likewise you can remove one or more Touch Points at any time while other Touch Points remain in play, it’s important to understand how the OnTouch event responds to changes in the number of Touch Points taking place from the moment a Touch Action begins, and that Touch Action ends.

A Touch Action begins when one Touch Point is placed on the screen. So, if you place one finger on the screen, you have begun a Touch Action. When that finger leaves the screen (either by going outside of the touch area, or by being lifted from the screen entirely) that Touch Action ends.

When it comes to Multi-Touch, however, the rules change.

Problems determining the Start and End of a Multi-Touch Event

For one thing, you cannot assume that a new Touch Event is beginning each time a Touch Down Action occurs, or that a Touch Event is ending each time a Touch Up or Touch Cancel Action occurs. This is because the introduction of a new Touch Point, or removal of an existing Touch Point can occur at any time.

To illustrate this point, I added a second form to the example code snip provided above, dropped a single TMemo control on it, and modified the main unit as follows:

type
  TForm2 = class(TForm)
    procedure FormShow(Sender: TObject);
    procedure FormTouch(Sender: TObject; const Touches: TTouches; const Action: TTouchAction);
  private
    procedure TouchNone(Sender: TObject; const Touches: TTouches);
    procedure TouchUp(Sender: TObject; const Touches: TTouches);
    procedure TouchDown(Sender: TObject; const Touches: TTouches);
    procedure TouchMove(Sender: TObject; const Touches: TTouches);
    procedure TouchCancel(Sender: TObject; const Touches: TTouches);
  public
    { Public declarations }
  end;

var
  Form2: TForm2;

implementation

uses
  Unit3;

{$R *.fmx}

procedure TForm2.FormShow(Sender: TObject);
begin
  Form3.Show;
end;

procedure TForm2.FormTouch(Sender: TObject; const Touches: TTouches; const Action: TTouchAction);
begin
  case Action of
    TTouchAction.None: TouchNone(Sender, Touches);
    TTouchAction.Up: TouchUp(Sender, Touches);
    TTouchAction.Down: TouchDown(Sender, Touches);
    TTouchAction.Move: TouchMove(Sender, Touches);
    TTouchAction.Cancel: TouchCancel(Sender, Touches);
  end;
end;

procedure TForm2.TouchCancel(Sender: TObject; const Touches: TTouches);
var
  I: Integer;
begin
  Form3.Memo1.Lines.Add('Touch Cancel:');
  for I := Low(Touches) to High(Touches) do
    Form3.Memo1.Lines.Add(Format(#9 + #9 + 'Point %d (%n, %n)', [I, Touches[I].Location.X, Touches[I].Location.Y]));
end;

procedure TForm2.TouchDown(Sender: TObject; const Touches: TTouches);
var
  I: Integer;
begin
  Form3.Memo1.Lines.Add('Touch Down:');
  for I := Low(Touches) to High(Touches) do
    Form3.Memo1.Lines.Add(Format(#9 + #9 + 'Point %d (%n, %n)', [I, Touches[I].Location.X, Touches[I].Location.Y]));
end;

procedure TForm2.TouchMove(Sender: TObject; const Touches: TTouches);
var
  I: Integer;
begin
  Form3.Memo1.Lines.Add('Touch Move:');
  for I := Low(Touches) to High(Touches) do
    Form3.Memo1.Lines.Add(Format(#9 + #9 + 'Point %d (%n, %n)', [I, Touches[I].Location.X, Touches[I].Location.Y]));
end;

procedure TForm2.TouchNone(Sender: TObject; const Touches: TTouches);
var
  I: Integer;
begin
  Form3.Memo1.Lines.Add('Touch None:');
  for I := Low(Touches) to High(Touches) do
    Form3.Memo1.Lines.Add(Format(#9 + #9 + 'Point %d (%n, %n)', [I, Touches[I].Location.X, Touches[I].Location.Y]));
end;

procedure TForm2.TouchUp(Sender: TObject; const Touches: TTouches);
var
  I: Integer;
begin
  Form3.Memo1.Lines.Add('Touch Up:');
  for I := Low(Touches) to High(Touches) do
    Form3.Memo1.Lines.Add(Format(#9 + #9 + 'Point %d (%n, %n)', [I, Touches[I].Location.X, Touches[I].Location.Y]));
end;

What this program does is output a dump of the Touch Actions and their respective Touch Point Data into that TMemo control on the other Form so that we can see exactly what Touch Actions are taking place each time OnTouch is called.

This is actually a much more reliable way of debugging multi-touch event data than using the debugger, as the debugger would cause fragmentation of the input data each time the IDE broke out at a breakpoint for introspection.

I ran this program and momentarily touched three fingers onto my multi-touch Windows tablet device for less than one second. From my perspective, all three fingers made contact with the screen at exactly the same moment, and broke contact with the screen again at exactly the same moment, however the data log tells a very different story:

Touch Down:
        Point 0 (196.00, 275.00)
Touch Down:
        Point 0 (196.00, 275.00)
        Point 1 (285.00, 142.00)
Touch Down:
        Point 0 (196.00, 275.00)
        Point 1 (285.00, 142.00)
        Point 2 (416.00, 123.00)
Touch Move:
        Point 0 (196.00, 275.00)
        Point 1 (285.00, 142.00)
        Point 2 (416.00, 125.00)
Touch Move:
        Point 0 (196.00, 275.00)
        Point 1 (285.00, 142.00)
        Point 2 (416.00, 125.00)
Touch Move:
        Point 0 (196.00, 275.00)
        Point 1 (285.00, 142.00)
        Point 2 (416.00, 125.00)
Touch Move:
        Point 0 (196.00, 275.00)
        Point 1 (285.00, 142.00)
        Point 2 (416.00, 125.00)
Touch Move:
        Point 0 (197.00, 276.00)
        Point 1 (285.00, 142.00)
        Point 2 (415.00, 127.00)
Touch Move:
        Point 0 (197.00, 276.00)
        Point 1 (285.00, 142.00)
        Point 2 (415.00, 127.00)
Touch Move:
        Point 0 (285.00, 142.00)
        Point 1 (418.00, 135.00)
Touch Up:
        Point 0 (285.00, 142.00)
        Point 1 (418.00, 135.00)
Touch Move:
        Point 0 (288.00, 141.00)
Touch Up:
        Point 0 (288.00, 141.00)

What we can see in the input dump above is that the Touch Down action was executed three separate times, once for each additional input, suggesting that – despite appearing from my perspective that all three fingers contacted the screen at the same exact moment – there was an imperceptible delay between each of my fingers making contact with the screen.

This means that, if we were to try and determine when a Touch Action begins by toggling its state using Touch Down, we would have problems as this Touch Action is essentially re-entrant (it can occur multiple times without a Touch Up or Touch Cancel action terminating that state).

We can also see from this input dump that the Touch Move action occurs repeatedly for the entire duration of contact with the screen, even if none of the Touch Points have actually changed their respective positions.

Finally, we can see that while three separate Touch Down events occurred at the beginning of our input, only two Touch Up events occurred at the end. Presumably this is because two of my fingers left the surface of the screen at exactly the same time, and the other (most likely my index finger) left the surface a fraction of a second later.

These details are very important, particularly if we want to track a complete multi-touch input event from start to finish, as we need to figure out the most reliable way to determine when a multi-touch input event starts, and when it finishes.

Tracking a multi-touch input event from start to finish is particularly useful if you want to draw multiple paths using the movement data for each respective Touch Point, or (more usefully) for enabling multiple simultaneous drag and drop operations in your applications.

Problems with the Order of Contact

Another even more challenging issue is keeping track of the order in which Touch Points are terminated from a touch event.

Here’s another input log where-in I placed three fingers on the screen in turn (index, middle, ring finger). I then removed my middle finger, followed by my index finger, followed by my ring finger.

Think of this as:

Down: 1, 2, 3
Up: 2, 1, 3

Here’s what that log looks like:

Touch Down:
        Point 0 (200.00, 312.00)
Touch Move:
        Point 0 (200.00, 312.00)
Touch Move:
        Point 0 (200.00, 312.00)
Touch Move:
        Point 0 (200.00, 312.00)
Touch Move:
        Point 0 (200.00, 312.00)
Touch Move:
        Point 0 (200.00, 312.00)
Touch Move:
        Point 0 (200.00, 312.00)
Touch Move:
        Point 0 (200.00, 312.00)
Touch Move:
        Point 0 (200.00, 312.00)
Touch Move:
        Point 0 (200.00, 312.00)
Touch Move:
        Point 0 (200.00, 312.00)
Touch Move:
        Point 0 (200.00, 312.00)
Touch Move:
        Point 0 (200.00, 312.00)
Touch Down:
        Point 0 (200.00, 312.00)
        Point 1 (272.00, 152.00)
Touch Move:
        Point 0 (200.00, 312.00)
        Point 1 (272.00, 152.00)
Touch Move:
        Point 0 (200.00, 312.00)
        Point 1 (272.00, 153.00)
Touch Move:
        Point 0 (200.00, 312.00)
        Point 1 (272.00, 153.00)
Touch Move:
        Point 0 (200.00, 312.00)
        Point 1 (272.00, 153.00)
Touch Move:
        Point 0 (200.00, 312.00)
        Point 1 (272.00, 153.00)
Touch Move:
        Point 0 (200.00, 312.00)
        Point 1 (272.00, 153.00)
Touch Move:
        Point 0 (200.00, 312.00)
        Point 1 (271.00, 154.00)
Touch Move:
        Point 0 (200.00, 312.00)
        Point 1 (271.00, 155.00)
Touch Move:
        Point 0 (200.00, 312.00)
        Point 1 (271.00, 156.00)
Touch Down:
        Point 0 (200.00, 312.00)
        Point 1 (271.00, 156.00)
        Point 2 (390.00, 82.00)
Touch Move:
        Point 0 (200.00, 312.00)
        Point 1 (271.00, 158.00)
        Point 2 (390.00, 82.00)
Touch Move:
        Point 0 (200.00, 312.00)
        Point 1 (271.00, 159.00)
        Point 2 (390.00, 82.00)
Touch Move:
        Point 0 (201.00, 312.00)
        Point 1 (270.00, 159.00)
        Point 2 (390.00, 82.00)
Touch Move:
        Point 0 (201.00, 312.00)
        Point 1 (270.00, 159.00)
        Point 2 (388.00, 82.00)
Touch Move:
        Point 0 (201.00, 312.00)
        Point 1 (270.00, 159.00)
        Point 2 (388.00, 82.00)
Touch Move:
        Point 0 (201.00, 312.00)
        Point 1 (270.00, 159.00)
        Point 2 (387.00, 83.00)
Touch Move:
        Point 0 (201.00, 312.00)
        Point 1 (270.00, 159.00)
        Point 2 (387.00, 83.00)
Touch Move:
        Point 0 (201.00, 312.00)
        Point 1 (269.00, 157.00)
        Point 2 (387.00, 81.00)
Touch Move:
        Point 0 (201.00, 312.00)
        Point 1 (269.00, 157.00)
        Point 2 (387.00, 81.00)
Touch Move:
        Point 0 (201.00, 312.00)
        Point 1 (387.00, 81.00)
Touch Move:
        Point 0 (201.00, 312.00)
        Point 1 (387.00, 81.00)
Touch Move:
        Point 0 (201.00, 312.00)
        Point 1 (387.00, 81.00)
Touch Move:
        Point 0 (201.00, 312.00)
        Point 1 (387.00, 81.00)
Touch Move:
        Point 0 (201.00, 312.00)
        Point 1 (387.00, 81.00)
Touch Move:
        Point 0 (201.00, 312.00)
        Point 1 (387.00, 81.00)
Touch Move:
        Point 0 (201.00, 312.00)
        Point 1 (387.00, 81.00)
Touch Move:
        Point 0 (201.00, 312.00)
        Point 1 (387.00, 81.00)
Touch Move:
        Point 0 (201.00, 312.00)
        Point 1 (387.00, 81.00)
Touch Move:
        Point 0 (201.00, 312.00)
        Point 1 (387.00, 81.00)
Touch Move:
        Point 0 (200.00, 312.00)
        Point 1 (387.00, 81.00)
Touch Move:
        Point 0 (200.00, 312.00)
        Point 1 (387.00, 81.00)
Touch Move:
        Point 0 (387.00, 81.00)
Touch Move:
        Point 0 (387.00, 81.00)
Touch Move:
        Point 0 (387.00, 81.00)
Touch Move:
        Point 0 (387.00, 81.00)
Touch Move:
        Point 0 (387.00, 81.00)
Touch Move:
        Point 0 (387.00, 81.00)
Touch Move:
        Point 0 (387.00, 81.00)
Touch Move:
        Point 0 (387.00, 81.00)
Touch Move:
        Point 0 (387.00, 81.00)
Touch Move:
        Point 0 (387.00, 81.00)
Touch Move:
        Point 0 (387.00, 81.00)
Touch Move:
        Point 0 (387.00, 81.00)
Touch Move:
        Point 0 (387.00, 81.00)
Touch Move:
        Point 0 (387.00, 81.00)
Touch Move:
        Point 0 (387.00, 81.00)
Touch Move:
        Point 0 (387.00, 81.00)
Touch Move:
        Point 0 (387.00, 81.00)
Touch Move:
        Point 0 (387.00, 81.00)
Touch Move:
        Point 0 (388.00, 81.00)
Touch Move:
        Point 0 (388.00, 77.00)
Touch Up:
        Point 0 (388.00, 77.00)

Note that I have not omitted a single piece of information from this input dump, but I have highlighted the Touch Down and Touch Up actions.

This illustrates one shocking (and troublesome) issue: while you can clearly see that I did indeed place three fingers on the screen, only one Touch Up action executed at the very end of the log.

Now, had I removed all three fingers from the screen at exactly the same time, this would make some sense. However, you can clearly see from this input dump when each of my fingers left the screen (based on the reduction in the number of Points being displayed in the log), and that the Touch Up actions did not occur until the very last finger left the screen.

This is particularly confusing when you consider that, in the previous test case, there were two separate Touch Up actions, presumably suggesting that two fingers left the screen simultaneously, with the third following a fraction of a second later. We would have expected there to have been three separate Touch Up actions in this test, as I deliberately removed each finger from the screen in turn.

This means that we cannot rely at all on either the Touch Down or Touch Up actions to determine when a Touch Event begins or ends.

Indeed, we can see from these two tests that the execution of the Touch Down and Touch Up actions cannot be trusted to behave in a consistent way.

Another critical piece of information this test illustrates is that, when you remove a Touch Point, the Array position of all subsequent Touch Points shifts back by 1. This means that the Index of a Touch Point is not necessarily going to be the same at the end of a Touch Action as it was at the beginning.

Coming up next…

In the next part of this article, I’ll show you how to work around the limitations we’ve discussed here, and how to make productive use of multi-touch input for a variety of different purposes.

Visit Us On Google PlusVisit Us On Youtube