6

I need code that is able to intercept several taps on a tablet at the same moment. In a previous question concerning how to handle several OnMouseDown's at the same moment (not possible), a link was provided that answered the question on how to handle multi touch clicks in Delphi-Android. However, this code returns (x, y) positions in screen coordinates and I don't know how to translate these to local coordinates of a specific control. The Delphi documentation refers to a ScreenToClient function but that only translates screen coordinates to form coordinates, which is hardly useful in Android (the documentation is about XE2 but the function still exists in XE5 but the function has been removed from FMX.Platform to FMX.Form).

Is there a simple way to convert screen coordinates to TControl coordinates in FMX like in VCL? Of course I can 'de-parent' a control, notate its top-left coordinates and do so for each parent until arrived at the base form, but that's quite tedious.

Edit 1

My current approach is to get the top (x, y) coordinates of the TControl (a TPanel) that is parent to the controls to be tapped (TRectangle's in fact) and add these to check whether a click is inside that rectangle. See sample code below.

procedure TKeyBoard.process_touch (Event: TTouchEvent; status_byte: Int32);
var
  key: TKey;
  p, q: TPointF;
  i: Integer;
  x, y: single;
begin
// Check whether at least one event is present. If so, i points to the last event
   i := Length (Event.Points) - 1;
   if i < 0 then Exit;

// Get (x, y) coordinates from event. It's in screen coordinates
   x := Event.Points [i].Position.X;
   y := Event.Points [i].Position.Y;

// FControl is a reference to the panel holding the keys
// Find its rectangle position and convert to screen coordinates
   p := TPointF.Create (FControl.Position.X, FControl.Position.Y);
   q := TPointF.Create (p.X + FControl.Width, p.Y + FControl.Height);
   p := Application.MainForm.ClientToScreen (p);
   q := Application.MainForm.ClientToScreen (q);

   logd ('control [%.0f %.0f - %.0f, %.0f]', [FControl.Position.X, FControl.Position.Y, FControl.Width, FControl.Height]);
   logd ('to screen [%.0f %.0f - %.0f, %.0f]', [p.X, p.Y, q.X, q.Y]);

// Determine whether a black key has been pressed
   for i := Low (Fkeys) to High (FKeys) do
   begin
      if not cOctave_Major [i mod nOctave] then
      begin
         key := FKeys [i];

         logd ('%d (%.0f, %.0f) - (%.0f, %.0f) (%.0f, %.0f)', [i, x, y,
                  key.Position.X + p.X,             key.Position.Y + p.Y,
                  key.Position.X + P.X + Key.Width, key.Position.Y + p.Y + key.Height]);

         if (x >= key.Position.X + p.X) and (x <= key.Position.X + p.X + Key.Width) and
            (y >= key.Position.Y + p.Y) and (y <= key.Position.Y + p.Y + key.Height)
            then break;
         key := nil;
      end; // if
   end; // for

// if not, check whether a white key has been pressed
   if key = nil then
   begin
      logd ('Major');
      for i := Low (Fkeys) to High (FKeys) do
      begin
         if cOctave_Major [i mod nOctave] then
         begin
            key := FKeys [i];

            logd ('%d (%.0f, %.0f) - (%.0f, %.0f) (%.0f, %.0f)', [i, x, y,
                  key.Position.X + p.X,             key.Position.Y + p.Y,
                  key.Position.X + P.X + Key.Width, key.Position.Y + p.Y + key.Height]);

            if (x >= key.Position.X + p.X) and (x <= key.Position.X + p.X + Key.Width) and
               (y >= key.Position.Y + p.Y) and (y <= key.Position.Y + p.Y + key.Height)
               then break;
            key := nil;
         end; // if
      end; // for
   end; // if


   if key <> nil
      then putShort (status_byte, key.Note, 127);
   if key <> nil
      then logd (' found %s', [key.Text.Text]);
end; // process_touch //

This code is in fact very untidy because it assumes that the Parent control has the Application.MainForm as its parent which need not be the case. Another observation is that the taps might still be slightly wrong in the Y-position. For that reason i'd like to transfer the screen coordinates directly to the control's coordinates.

Edit 2

I tried to use the IsMouseOver check for each key control as suggested by @Sentient but strangely enough that only yields true when a MouseUp event is being processed.

Community
  • 1
  • 1
Arnold
  • 4,578
  • 6
  • 52
  • 91
  • 1
    Would using the .IsMouseOver of your controls work? I don't know if that registers correctly when there are multiple touches at the same time. – Sentient Feb 10 '14 at 20:55
  • Alas, that does not work on Android. It is triggered during a MouseUp event and not during the MouseDown. – Arnold Feb 11 '14 at 20:13

1 Answers1

13

I am the author of the multi-touch code you are using. When I saw you struggle with coordinates I looked what can be done and updated the code so it now gives you the touched control and the relative coordinates. Also if you are wondering how to get that its simple.

The blog post about it is here:

http://www.cromis.net/blog/2014/02/multi-touch-touched-control-and-relative-coordinates/

The code to solve it look like this:

  if Screen.ActiveForm <> nil then
  begin
    for I := 0 to Length(Event.Points) - 1 do
    begin
      Control := Screen.ActiveForm.ObjectAtPoint(Event.Points[I].Position);

      if Control <> nil then
      begin
        Event.Points[I].Control := Control as TFmxObject;
        Event.Points[I].RelPosition := Control.ScreenToLocal(Event.Points[I].Position);
      end
      else
      begin
        Event.Points[I].Control := Screen.ActiveForm;
        Event.Points[I].RelPosition := Screen.ActiveForm.ScreenToClient(Event.Points[I].Position);
      end;
    end;
  end;
Runner
  • 6,073
  • 26
  • 38
  • 2
    "I am the author..." is always a good start to a SO answer. +1. Interesting associated series of blog posts, too. – David Feb 17 '14 at 14:39
  • Thanks very much for this elaboration. Seems you solved the problem of translating to and from screen coordinates. As far as I understood your blog you use this code for a music keyboard as well. Do you have the impression that the reaction of this code is actually slower the the OnMouseDown event handler? – Arnold Feb 17 '14 at 19:57
  • Yes a used it for a music keyboard. I was making a port of application called Piano Wizard to iOS. As for speed, I think it should not be any slower then OnMouseDown. – Runner Feb 18 '14 at 06:36
  • This code works fine! My thanks for your work, it solved my problem of finding the correct key. I was not aware of an ObjectAtPoint method. Very useful indeed. A very minor detail: when compiling under windows or macos there is a `uses` clause without any units which leads to a compiler error. Comment out the `uses` and it compiles fine. One question out of curiosity: I do not need the history you keep track of, the last element in the Points array is sufficient. What is the reason you keep track of the history? – Arnold Feb 18 '14 at 20:25
  • 1
    Glad I could help. I will check the Windows and MacOS compilation. Thanks for the tip. As for the history, there is no particular reason. I am not keeping the history myself, both iOS and Android have that stored as part of the system and you get that data along with the touch event. So I provided that data to the user in case it is useful sometime. – Runner Feb 18 '14 at 20:57
  • @Runner I am trying to use the TFrame.LocalToScreen passing a TButton.Position. But it is expecting a TPointF instead. What am I messing from your example that I could not use on Firemonkey (it is not related to your library, just following your usage) – Eduardo Elias Jun 09 '14 at 12:58
  • @EduardoElias try TButton.Position.Point - https://docwiki.embarcadero.com/Libraries/Sydney/en/FMX.Types.TPosition.Point – George Birbilis Oct 26 '21 at 11:24