Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to convert screen coordinates to control coordinates

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.

like image 569
Arnold Avatar asked Feb 10 '14 20:02

Arnold


1 Answers

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;
like image 66
Runner Avatar answered Sep 18 '22 12:09

Runner