Home > Net >  How do I define an area and check if the mouse is in it?
How do I define an area and check if the mouse is in it?

Time:12-04

I am trying to define an area in the shape of a triangle and check if the mouse is in it. I can find if the mouse is in a certain square area using the code below. My program needs to detect the mouse in a triangle or a more complex shape.

if (Mouse.CursorPos.X < 20) or (50 > tbmn.Left   tbmn.Width) or (Mouse.CursorPos.Y < 20) or (Mouse.CursorPos.Y > tbmn.Top   60) then 
begin

end;

So basically, what I want to do is have a shape anywhere on the screen and check if the mouse is in it.

Is there a way to easily calculate a region of the screen and detect if the mouse is present in it?

CodePudding user response:

Asuming you have a component where you draw a triangle inside and only want to have the component detect mouse hit when the cursor is over the visible part of the shape then you could do something like this:

Have an alpha layer on the component being drawn. Then intercept the Windows CM_HITTEST message. in the hit test message procedure you then check if the alpha value is 0. If it is 0 then the mouse is over an area with some visible color value.

Type  
  TSomeComponent = class(TGraphicControl)
  private
    FPNG : TGraphic;
    procedure CMHitTest(var Message: TCMHitTest); message CM_HITTEST;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    procedure Paint; Override;
  end;

procedure Register;

implementation
uses
  GR32,  GR32_Brushes,Winapi.Windows;   

procedure TSomeComponent.CMHitTest(var Message: TCMHitTest);
var
  colorEntry: TColor32Entry;
  bmp : TBitmap32;
begin
  bmp := TBitmap32.Create();
  try
    try
      bmp.Assign(FPNG);
      colorEntry := TColor32Entry(bmp.Pixels[Message.XPos,Message.YPos]);

      if colorEntry.A <> 0 then
           Message.Result := HTCLIENT
      else
           Message.Result := HTNOWHERE;
    except
      Message.Result := HTCLIENT;
    end;
  finally
    bmp.Free;
  end;
end;

CodePudding user response:

You can use region functions from WinApi. Here are example for simple triangle:

function PtInTriangle(ptX,ptY,X1,Y1,X2,Y2,X3,Y3:integer):Boolean;
var rgn:THandle; pts:array [0..2] of TPoint;
begin
  pts[0].X:=X1; pts[0].Y:=Y1;
  pts[1].X:=X2; pts[1].Y:=Y2;
  pts[2].X:=X3; pts[2].Y:=Y3;
  rgn := CreatePolygonRgn( pts[0], 3, WINDING);
  Result := PtInRegion(rgn, ptX, ptY);
  DeleteObject(rgn);
end;

This function takes about ~30..40us on my machine, and PtInRegion() takes only ~10% of this time (so, you can optimize it by caching Region object). Here are code with simple bencmark:

function PtInTriangle(ptX,ptY,X1,Y1,X2,Y2,X3,Y3:integer):Boolean;
var rgn:THandle; pts:array [0..2] of TPoint;
    t,t1,t2,t3:Int64;
begin
  // Create region
  QueryPerformanceCounter(t);
    pts[0].X:=X1; pts[0].Y:=Y1;
    pts[1].X:=X2; pts[1].Y:=Y2;
    pts[2].X:=X3; pts[2].Y:=Y3;
    rgn := CreatePolygonRgn( pts[0], 3, WINDING);
  QueryPerformanceCounter(t1); Dec(t1,t);
  // Check point
  QueryPerformanceCounter(t);
    Result := PtInRegion(rgn, ptX, ptY);
  QueryPerformanceCounter(t2); Dec(t2,t);
  // Delete region
  QueryPerformanceCounter(t);
    DeleteObject(rgn);
  QueryPerformanceCounter(t3); Dec(t3,t);
  // Debug output
  QueryPerformanceFrequency(t);
  OutputDebugString(PChar(Format('All:%d(%.1fus)  Create:%d  PtInRect:%d(%.1f%%)  Delete:%d',
    [t1 t2 t3,(t1 t2 t3)/t*1E6,t1,t2,t2*100/(t1 t2 t3),t3])));
end;

Also, you can create complex regions with CreatePolyPolygonRgn() or CombineRgn().

  • Related