Bladeren bron

gtk3: started mouse capture

mattias 5 maanden geleden
bovenliggende
commit
3f82b2a7d2

+ 4 - 0
src/base/fresnel.classes.pas

@@ -31,6 +31,10 @@ type
   RTLString = string;
   {$ENDIF}
 
+  TFreHandle = Pointer;
+  TFreHandleArray = array of TFreHandle;
+  PFreHandle = ^TFreHandle;
+
   { EFresnel }
 
   EFresnel = class(Exception)

+ 116 - 54
src/base/fresnel.dom.pas

@@ -1239,33 +1239,6 @@ type
     Procedure Render(aRenderer : IFresnelRenderer);
   end;
 
-  { TFresnelScrollBar }
-
-  TFresnelScrollBar = class
-  private
-    FElement: TFresnelElement;
-    FHorizontal: boolean;
-  public type
-    {$ScopedEnums on}
-    THit = (hNone, hButtonLT, hButtonRB, hThumb, hTrackLT, hTrackRB);
-    {$ScopedEnums off}
-  public
-    Position: TFresnelLength; // ScrollLeft/Top, When direction=rtl: Position=Size-Page+ScrollLeft
-                              // can be negative or bigger than Size
-    Size: TFresnelLength; // ScrollWidth/Height
-    Page: TFresnelLength; // ClientWidth/Height
-    Color: TFPColor;
-    Box: TFresnelRect; // relative to layoutnode parent's RenderedContentBox
-    MouseDownHit: THit;
-    MouseDownPos: TFresnelLength;
-    constructor Create(TheElement: TFresnelElement; aHorizontal: boolean);
-    destructor Destroy; override;
-    function GetHit(const X, Y: TFresnelLength; out aPosition: TFresnelLength): THit; virtual; abstract;
-    procedure MouseHandler(WSData: TFresnelMouseEventInit; MouseEventId: TEventID); virtual; abstract;
-    property Element: TFresnelElement read FElement;
-    property Horizontal: boolean read FHorizontal;
-  end;
-
   { TFresnelFontDesc - font descriptor }
 
   TFresnelFontDesc = record
@@ -1306,6 +1279,7 @@ type
   end;
 
   TPseudoElement = class;
+  TFresnelScrollBar = class;
 
   { TFresnelElement }
 
@@ -1357,10 +1331,10 @@ type
     function GetViewportConnected: boolean;
     function GetEventHandler(AIndex: Integer): TFresnelEventHandler;
     function GetFocusEventHandler(AIndex: Integer): TFresnelFocusEventHandler;
-    function GetMouseEventHandler(AIndex: Integer): TFresnelMouseEventHandler;
+    function GetPointerEventHandler(AIndex: Integer): TFresnelPointerEventHandler;
     procedure SetEventHandler(AIndex: Integer; const AValue: TFresnelEventHandler);
     procedure SetFocusEventHandler(AIndex: Integer; AValue: TFresnelFocusEventHandler);
-    procedure SetMouseEventHandler(AIndex: Integer; const AValue: TFresnelMouseEventHandler);
+    procedure SetPointerEventHandler(AIndex: Integer; const AValue: TFresnelPointerEventHandler);
     procedure SetScrollLeft(const AValue: TFresnelLength);
     procedure SetScrollTop(const AValue: TFresnelLength);
   protected
@@ -1553,6 +1527,8 @@ type
     function AddEventListener(aID : TEventID; aHandler : TFresnelEventHandler) : Integer;
     function AddEventListener(Const aName: TEventName; aHandler : TFresnelEventHandler) : Integer;
     Function DispatchEvent(aEvent : TAbstractEvent) : Integer;
+    procedure SetPointerCapture(const aPointerId: TFreHandle); virtual;
+    procedure ReleasePointerCapture(const aPointerId: TFreHandle); virtual;
     property EventDispatcher: TFresnelEventDispatcher Read FEventDispatcher;
     // Font
     property Font: IFresnelFont read GetFont write FFont;
@@ -1564,11 +1540,11 @@ type
     property CSSClasses: TStrings read FCSSClasses write SetCSSClasses;
     property Style: string read FStyle write SetStyle;
     property OnClick: TFresnelEventHandler Index evtClick Read GetEventHandler Write SetEventHandler;
-    property OnMouseDown: TFresnelMouseEventHandler Index evtMouseDown Read GetMouseEventHandler Write SetMouseEventHandler;
-    property OnMouseMove: TFresnelMouseEventHandler Index evtMouseMove Read GetMouseEventHandler Write SetMouseEventHandler;
-    property OnMouseEnter: TFresnelMouseEventHandler Index evtMouseEnter Read GetMouseEventHandler Write SetMouseEventHandler;
-    property OnMouseLeave: TFresnelMouseEventHandler Index evtMouseLeave Read GetMouseEventHandler Write SetMouseEventHandler;
-    property OnMouseUp: TFresnelMouseEventHandler Index evtMouseUp Read GetMouseEventHandler Write SetMouseEventHandler;
+    property OnMouseDown: TFresnelPointerEventHandler Index evtMouseDown Read GetPointerEventHandler Write SetPointerEventHandler;
+    property OnMouseMove: TFresnelPointerEventHandler Index evtMouseMove Read GetPointerEventHandler Write SetPointerEventHandler;
+    property OnMouseEnter: TFresnelPointerEventHandler Index evtMouseEnter Read GetPointerEventHandler Write SetPointerEventHandler;
+    property OnMouseLeave: TFresnelPointerEventHandler Index evtMouseLeave Read GetPointerEventHandler Write SetPointerEventHandler;
+    property OnMouseUp: TFresnelPointerEventHandler Index evtMouseUp Read GetPointerEventHandler Write SetPointerEventHandler;
     property OnFocus: TFresnelFocusEventHandler Index evtFocus Read GetFocusEventHandler Write SetFocusEventHandler;
     property OnEnter: TFresnelFocusEventHandler Index evtFocusIn Read GetFocusEventHandler Write SetFocusEventHandler;
     property OnLeave: TFresnelFocusEventHandler Index evtFocusOut Read GetFocusEventHandler Write SetFocusEventHandler;
@@ -1609,6 +1585,33 @@ type
     function GetCSSPseudoElementName: TCSSString; override;
   end;
 
+  { TFresnelScrollBar }
+
+  TFresnelScrollBar = class(TPseudoElement)
+  private
+    FHorizontal: boolean;
+  protected
+    procedure SetParent(const AValue: TFresnelElement); override;
+  public type
+    {$ScopedEnums on}
+    THit = (hNone, hButtonLT, hButtonRB, hThumb, hTrackLT, hTrackRB);
+    {$ScopedEnums off}
+  public
+    Position: TFresnelLength; // ScrollLeft/Top, When direction=rtl: Position=Size-Page+ScrollLeft
+                              // can be negative or bigger than Size
+    Size: TFresnelLength; // ScrollWidth/Height
+    Page: TFresnelLength; // ClientWidth/Height
+    Color: TFPColor;
+    Box: TFresnelRect; // relative to layoutnode parent's RenderedContentBox
+    MouseDownHit: THit;
+    MouseDownPos: TFresnelLength;
+    constructor Create(TheElement: TFresnelElement; aHorizontal: boolean);
+    function GetHit(const X, Y: TFresnelLength; out aPosition: TFresnelLength): THit; virtual; abstract;
+    procedure MouseHandler(WSData: TFresnelMouseEventInit; MouseEventId: TEventID); virtual; abstract;
+    property Horizontal: boolean read FHorizontal;
+  end;
+
+
   { TReplacedElement - base class for elements with special content and no child elements, e.g. label, video }
 
   TReplacedElement = class(TFresnelElement)
@@ -1643,6 +1646,11 @@ type
     procedure SetMouseDownElement(El: TFresnelElement; const PageXY: TFresnelPoint);
   end;
 
+  TFresnelVPPointerCapture = record
+    Id: TFreHandle;
+    Element: TFresnelElement;
+  end;
+
   { TFresnelViewport }
 
   TFresnelViewport = class(TFresnelElement)
@@ -1661,6 +1669,7 @@ type
     FStylesheetResolverStamp: TCSSNumericalID;
     FStylesheetStamp: TCSSNumericalID;
     FWidth: TFresnelLength;
+    FPointerCaptures: array of TFresnelVPPointerCapture;
     procedure CSSResolverLog(Sender: TObject; Entry: TCSSResolverLogEntry);
     function GetFocusedElement: TFresnelElement;
     procedure SetFocusedElement(const aValue: TFresnelElement);
@@ -1774,6 +1783,8 @@ type
     function ContentToPagePos(El: TFresnelElement; const p: TFresnelPoint): TFresnelPoint; overload; // content box of El to content of viewport
     function PageToContentPos(El: TFresnelElement; const x, y: TFresnelLength): TFresnelPoint; virtual; overload; // content of viewport to content box of El
     function PageToContentPos(El: TFresnelElement; const p: TFresnelPoint): TFresnelPoint; virtual; overload; // content of viewport to content box of El
+    procedure SetPointerCapture(El: TFresnelElement; const aPointerId: TFreHandle); virtual; overload;
+    procedure ReleasePointerCapture(El: TFresnelElement; const aPointerId: TFreHandle); virtual; overload;
     procedure WSMouseXY(WSData: TFresnelMouseEventInit; MouseEventId: TEventID); virtual;
     // Return true if default was prevented
     function WSKey(WSData: TFresnelKeyEventInit; KeyEventId: TEventID) : boolean; virtual;
@@ -6586,26 +6597,27 @@ end;
 
 { TFresnelScrollBar }
 
-constructor TFresnelScrollBar.Create(TheElement: TFresnelElement; aHorizontal: boolean);
+procedure TFresnelScrollBar.SetParent(const AValue: TFresnelElement);
 begin
-  FElement:=TheElement;
-  FHorizontal:=aHorizontal;
-  if Horizontal then
-    FElement.FScrollBarHorizontal:=Self
-  else
-    FElement.FScrollBarVertical:=Self;
-end;
-
-destructor TFresnelScrollBar.Destroy;
-begin
-  if FElement<>nil then
+  if Parent<>nil then
   begin
     if FHorizontal then
-      FElement.FScrollBarHorizontal:=nil
+      Parent.FScrollBarHorizontal:=nil
     else
-      FElement.FScrollBarVertical:=nil;
+      Parent.FScrollBarVertical:=nil;
   end;
-  inherited Destroy;
+  inherited SetParent(AValue);
+end;
+
+constructor TFresnelScrollBar.Create(TheElement: TFresnelElement; aHorizontal: boolean);
+begin
+  inherited Create(TheElement);
+  FHorizontal:=aHorizontal;
+  Parent:=TheElement;
+  if Horizontal then
+    Parent.FScrollBarHorizontal:=Self
+  else
+    Parent.FScrollBarVertical:=Self;
 end;
 
 { TFresnelFontDesc }
@@ -7191,6 +7203,47 @@ begin
   Result:=PageToContentPos(El,p.X,p.Y);
 end;
 
+procedure TFresnelViewport.SetPointerCapture(El: TFresnelElement; const aPointerId: TFreHandle);
+var
+  i: Integer;
+  Item: TFresnelVPPointerCapture;
+begin
+  if El=nil then
+    raise Exception.Create('20250324190651');
+  if (El=Self) or (El.Viewport=Self) then
+  else
+    raise Exception.Create('20250324191044');
+  if aPointerId=nil then
+    raise Exception.Create('20250324190659');
+  for i:=0 to length(FPointerCaptures)-1 do
+    if FPointerCaptures[i].Id=aPointerId then
+    begin
+      // already capturing
+      if FPointerCaptures[i].Element=El then exit;
+      FPointerCaptures[i].Element:=El;
+      // todo: tell element is has lost the capture
+      exit;
+    end;
+  SetPointerCapture(aPointerId);
+  Item.Id:=aPointerId;
+  Item.Element:=El;
+  Insert(Item,FPointerCaptures,length(FPointerCaptures));
+end;
+
+procedure TFresnelViewport.ReleasePointerCapture(El: TFresnelElement; const aPointerId: TFreHandle);
+var
+  i: Integer;
+begin
+  for i:=0 to length(FPointerCaptures)-1 do
+    if FPointerCaptures[i].Id=aPointerId then
+    begin
+      if FPointerCaptures[i].Element<>El then exit;
+      System.Delete(FPointerCaptures,i,1);
+      ReleasePointerCapture(aPointerId);
+      exit;
+    end;
+end;
+
 procedure TFresnelViewport.WSMouseXY(WSData: TFresnelMouseEventInit; MouseEventId: TEventID);
 var
   El, BubbleEl, OldMouseDownEl: TFresnelElement;
@@ -7573,12 +7626,11 @@ begin
     Result:=TFresnelFocusEventHandler(GetEventHandler(AIndex));
 end;
 
-function TFresnelElement.GetMouseEventHandler(AIndex: Integer
-  ): TFresnelMouseEventHandler;
+function TFresnelElement.GetPointerEventHandler(AIndex: Integer): TFresnelPointerEventHandler;
 begin
   Result:=nil;
   if AIndex in evtAllMouse then
-    Result:=TFresnelMouseEventHandler(GetEventHandler(AIndex));
+    Result:=TFresnelPointerEventHandler(GetEventHandler(AIndex));
 end;
 
 procedure TFresnelElement.SetEventHandler(AIndex: Integer;
@@ -7596,8 +7648,8 @@ begin
     SetEventHandler(AIndex,TFresnelEventHandler(AValue));
 end;
 
-procedure TFresnelElement.SetMouseEventHandler(AIndex: Integer;
-  const AValue: TFresnelMouseEventHandler);
+procedure TFresnelElement.SetPointerEventHandler(AIndex: Integer;
+  const AValue: TFresnelPointerEventHandler);
 begin
   if AIndex in evtAllMouse then
     SetEventHandler(AIndex,TFresnelEventHandler(AValue));
@@ -8930,6 +8982,16 @@ begin
   Result:=DoDispatchEvent(aEvent);
 end;
 
+procedure TFresnelElement.SetPointerCapture(const aPointerId: TFreHandle);
+begin
+  Viewport.SetPointerCapture(Self,aPointerId);
+end;
+
+procedure TFresnelElement.ReleasePointerCapture(const aPointerId: TFreHandle);
+begin
+  Viewport.ReleasePointerCapture(Self,aPointerId);
+end;
+
 function TFresnelElement.GetFont: IFresnelFont;
 
   function GetViewportFontSize: TFresnelLength;

+ 29 - 23
src/base/fresnel.events.pas

@@ -21,7 +21,7 @@ unit Fresnel.Events;
 interface
 
 uses
-  Classes, SysUtils,
+  Classes, SysUtils, TypInfo,
   {$IF FPC_FULLVERSION>30300}
   system.UITypes,
   {$ENDIF}
@@ -175,28 +175,36 @@ Type
   TFLMouseEvent = TFresnelMouseEvent;
   TFresnelMouseEventHandler = Procedure(Event: TFresnelMouseEvent) of object;
 
+  { TFresnelPointerEvent }
+
+  TFresnelPointerEvent = class(TFresnelMouseEvent)
+  public
+    PointerId: TFreHandle;
+  end;
+  TFresnelPointerEventHandler = Procedure(Event: TFresnelPointerEvent) of object;
+
   { TFresnelMouseClickEvent }
 
-  TFresnelMouseClickEvent = class(TFresnelMouseEvent)
+  TFresnelMouseClickEvent = class(TFresnelPointerEvent)
     class function FresnelEventID : TEventID; override;
   end;
   TFLMouseClickEvent = TFresnelMouseClickEvent;
 
-  TFresnelMouseDoubleClickEvent = class(TFresnelMouseEvent)
+  TFresnelMouseDoubleClickEvent = class(TFresnelPointerEvent)
     class function FresnelEventID : TEventID; override;
   end;
   TFLMouseDoubleClickEvent = TFresnelMouseDoubleClickEvent;
 
   { TFresnelMouseDownEvent }
 
-  TFresnelMouseDownEvent = class(TFresnelMouseEvent)
+  TFresnelMouseDownEvent = class(TFresnelPointerEvent)
     class function FresnelEventID : TEventID; override;
   end;
   TFLMouseDownEvent = TFresnelMouseDownEvent;
 
   { TFresnelMouseMoveEvent }
 
-  TFresnelMouseMoveEvent = class(TFresnelMouseEvent)
+  TFresnelMouseMoveEvent = class(TFresnelPointerEvent)
     class function FresnelEventID : TEventID; override;
   end;
   TFLMouseMoveEvent = TFresnelMouseMoveEvent;
@@ -210,7 +218,7 @@ Type
 
   { TFresnelMouseEnterEvent }
 
-  TFresnelMouseEnterEvent = class(TFresnelMouseEvent)
+  TFresnelMouseEnterEvent = class(TFresnelPointerEvent)
   private
     FRelated: TObject;
   Public
@@ -221,7 +229,7 @@ Type
 
   { TFresnelMouseLeaveEvent }
 
-  TFresnelMouseLeaveEvent = class(TFresnelMouseEvent)
+  TFresnelMouseLeaveEvent = class(TFresnelPointerEvent)
   private
     FRelated: TObject;
   Public
@@ -387,24 +395,22 @@ Type
   end;
   TFLWheelEvent = TFresnelWheelEvent;
 
-   { TFresnelEventDispatcher }
-
-   TFresnelEventDispatcher = Class(TEventDispatcher)
-   Private
-     Class Var _Registry : TEventRegistry;
-   Protected
-     class Function CreateFresnelRegistry : TEventRegistry; virtual;
-     Function GetRegistry: TEventRegistry; override;
-   Public
-     Class Function FresnelRegistry : TEventRegistry;
-     Class Procedure RegisterFresnelEvents;
-     Class Destructor Done;
-   end;
-   TFLEventDispatcher = TFresnelEventDispatcher;
+  { TFresnelEventDispatcher }
 
-implementation
+  TFresnelEventDispatcher = Class(TEventDispatcher)
+  Private
+    Class Var _Registry : TEventRegistry;
+  Protected
+    class Function CreateFresnelRegistry : TEventRegistry; virtual;
+    Function GetRegistry: TEventRegistry; override;
+  Public
+    Class Function FresnelRegistry : TEventRegistry;
+    Class Procedure RegisterFresnelEvents;
+    Class Destructor Done;
+  end;
+  TFLEventDispatcher = TFresnelEventDispatcher;
 
-uses TypInfo;
+implementation
 
 Const
   FresnelEventNames : Array[0..evtLastEvent] of TEventName = (

+ 16 - 0
src/base/fresnel.forms.pas

@@ -102,6 +102,8 @@ type
     procedure Show; virtual;
     procedure Invalidate; override;
     procedure InvalidateRect(const aRect: TFresnelRect); virtual;
+    procedure SetPointerCapture(const aPointerId: TFreHandle); override; overload;
+    procedure ReleasePointerCapture(const aPointerId: TFreHandle); override; overload;
     function IsDrawing: boolean; override;
     property Designer: IFresnelFormDesigner read FDesigner write SetDesigner;
   public
@@ -625,6 +627,20 @@ begin
     WSForm.InvalidateRect(aRect);
 end;
 
+procedure TFresnelCustomForm.SetPointerCapture(const aPointerId: TFreHandle);
+begin
+  if csDestroying in ComponentState then
+    raise Exception.Create('20250324191514');
+  WSForm.SetPointerCapture(aPointerId);
+end;
+
+procedure TFresnelCustomForm.ReleasePointerCapture(const aPointerId: TFreHandle);
+begin
+  if WSForm=nil then exit;
+  if csDestroying in ComponentState then exit;
+  WSForm.ReleasePointerCapture(aPointerId);
+end;
+
 function TFresnelCustomForm.IsDrawing: boolean;
 begin
   Result:=fsDrawing in FFormStates;

+ 10 - 10
src/base/fresnel.renderer.pas

@@ -396,10 +396,10 @@ var
   r: TFresnelRect;
   ThumbR: TFresnelRoundRect;
   c: TFresnelCSSCorner;
-  IsHovered: Boolean;
+  ElHovered: Boolean;
 begin
   r:=Box;
-  IsHovered:=Element.IsHovered;
+  ElHovered:=Parent.IsHovered;
 
   // draw track
   TrackColor:=colLtGray;
@@ -411,7 +411,7 @@ begin
 
   // draw thumb
   ThumbColor:=colDkGray;
-  if not IsHovered then
+  if not ElHovered then
     ThumbColor.Alpha:=$8000;
   FromPos:=Position/Size;
   if FromPos<0 then FromPos:=0;
@@ -455,7 +455,7 @@ var
   TrackColor: TFPColor;
 begin
   TrackColor:=colLtGray;
-  if not Element.IsHovered then
+  if not Parent.IsHovered then
     TrackColor.Alpha:=$8000;
   Renderer.FillRect(TrackColor,r);
 end;
@@ -498,8 +498,8 @@ procedure TRendererScrollBar.MouseHandler(WSData: TFresnelMouseEventInit; MouseE
 var
   X, Y, NewPos: TFresnelLength;
 begin
-  X:=WSData.ControlPos.X+Element.RenderedContentBox.Left;
-  Y:=WSData.ControlPos.Y+Element.RenderedContentBox.Top;
+  X:=WSData.ControlPos.X+Parent.RenderedContentBox.Left;
+  Y:=WSData.ControlPos.Y+Parent.RenderedContentBox.Top;
   {$IFDEF VerboseFresnelScrolling}
   //writeln('TRendererScrollBar.MouseHandler ',Element.Name,' ControlPos=',WSData.ControlPos.ToString,', RenderBox=',Element.RenderedContentBox.ToString,' X=',FloatToCSSStr(X),',',FloatToCSSStr(Y),' Size=',FloatToCSSStr(Size),' Position=',FloatToCSSStr(Position),' Page=',FloatToCSSStr(Page));
   {$ENDIF}
@@ -510,7 +510,7 @@ begin
     begin
       MouseDownHit:=GetHit(X,Y,MouseDownPos);
       {$IFDEF VerboseFresnelScrolling}
-      writeln('TRendererScrollBar.MouseHandler Mouse Down ',Element.Name,' Hit=',MouseDownHit,' p=',FloatToCSSStr(MouseDownPos));
+      writeln('TRendererScrollBar.MouseHandler Mouse Down ',Parent.Name,' Hit=',MouseDownHit,' p=',FloatToCSSStr(MouseDownPos));
       {$ENDIF}
       case MouseDownHit of
       THit.hButtonLT: ;
@@ -527,12 +527,12 @@ begin
       NewPos:=Max(0,Min(NewPos,Size-Page));
       if Horizontal then
       begin
-        if Element.ComputedDirection=CSSRegistry.kwRTL then
+        if Parent.ComputedDirection=CSSRegistry.kwRTL then
           NewPos:=NewPos-Size+Page;
-        Element.ScrollLeft:=NewPos;
+        Parent.ScrollLeft:=NewPos;
       end
       else
-        Element.ScrollTop:=NewPos;
+        Parent.ScrollTop:=NewPos;
     end;
   evtMouseMove:
     if MouseDownHit>THit.hNone then begin

+ 14 - 1
src/base/fresnel.widgetset.pas

@@ -23,7 +23,6 @@ uses
   Classes, SysUtils, Fresnel.Keys, Fresnel.Events, Fresnel.Classes, Fresnel.DOM, Fresnel.Renderer;
 
 type
-  TFreHandle = Pointer;
 
   { TFresnelWSForm }
 
@@ -44,6 +43,8 @@ type
     function GetClientSize: TFresnelPoint; virtual; abstract;
     procedure Invalidate; virtual;
     procedure InvalidateRect(const aRect: TFresnelRect); virtual; abstract;
+    procedure SetPointerCapture(const aPointerId: TFreHandle); virtual;
+    procedure ReleasePointerCapture(const aPointerId: TFreHandle); virtual;
     property Caption: TFresnelCaption read GetCaption write SetCaption;
     property FormBounds: TFresnelRect read GetFormBounds write SetFormBounds;
     property Renderer: TFresnelRenderer read FRenderer;
@@ -122,6 +123,18 @@ begin
   InvalidateRect(aRect);
 end;
 
+procedure TFresnelWSForm.SetPointerCapture(const aPointerId: TFreHandle);
+begin
+  FLLog(etError,'TFresnelWSForm.SetPointerCapture not yet implemented on this widgetset');
+  if aPointerId=nil then ;
+end;
+
+procedure TFresnelWSForm.ReleasePointerCapture(const aPointerId: TFreHandle);
+begin
+  FLLog(etError,'TFresnelWSForm.ReleasePointerCapture not yet implemented on this widgetset');
+  if aPointerId=nil then ;
+end;
+
 function TFresnelWSForm.NumKeyToInputType(aKey : Integer; aShift: TShiftState; out aType : TFresnelInputType) : boolean;
 
 begin

+ 46 - 3
src/gtk3/fresnel.gtk3.pas

@@ -53,7 +53,6 @@ type
     procedure SetFormBounds(const AValue: TFresnelRect); override;
     procedure SetCaption(AValue: TFresnelCaption); override;
     procedure SetVisible(const AValue: boolean); override;
-
   public
     function GetClientSize: TFresnelPoint; override;
     function GtkEventDraw(AContext: Pcairo_t): Boolean; virtual;
@@ -71,6 +70,8 @@ type
     procedure GtkEventSizeAllocate(aRect: PGdkRectangle); virtual;
     procedure Invalidate; override;
     procedure InvalidateRect(const aRect: TFresnelRect); override;
+    procedure SetPointerCapture(const aPointerId: TFreHandle); override;
+    procedure ReleasePointerCapture(const aPointerId: TFreHandle); override;
   public
     constructor Create(AOwner: TComponent); override;
     destructor Destroy; override;
@@ -152,6 +153,9 @@ function GdkRectFromFresnelRect(const R: TFresnelRect): TGdkRectangle;
 
 function GdkModifierStateToShiftState(const AState: TGdkModifierType): TShiftState;
 
+function GtkWidgetToWSObject(aWidget: PGtkWidget): TObject;
+function GtkWidgetToWSForm(aWidget: PGtkWidget): TGtk3WSForm;
+
 implementation
 
 uses UTF8Utils;
@@ -163,10 +167,10 @@ var
 function ThreadSync_IOCallback({%H-}Source: PGIOChannel; {%H-}Condition: TGIOCondition;
   {%H-}Data: gpointer): gboolean; cdecl;
 var
-  ThrashSpace: array[1..1024] of byte;
+  TrashSpace: array[1..1024] of byte;
 begin
   // read the sent bytes
-  FpRead(ThreadSync_PipeIn, {%H-}ThrashSpace[1], 1);
+  FpRead(ThreadSync_PipeIn, {%H-}TrashSpace[1], 1);
 
   // execute the to-be synchronized method
   if IsMultiThread then
@@ -501,6 +505,23 @@ begin
     Include(Result,ssCtrl);
 end;
 
+function GtkWidgetToWSObject(aWidget: PGtkWidget): TObject;
+begin
+  if aWidget=nil then exit(nil);
+  Result:=TObject(g_object_get_data(AWidget, 'fresnelobject'));
+end;
+
+function GtkWidgetToWSForm(aWidget: PGtkWidget): TGtk3WSForm;
+var
+  o: TObject;
+begin
+  o:=GtkWidgetToWSObject(aWidget);
+  if o is TGtk3WSForm then
+    Result:=TGtk3WSForm(o)
+  else
+    Result:=nil;
+end;
+
 Function GDKKeyEventToKeyEventInit(Event: PGdkEvent) : TFresnelKeyEventInit;
 
 begin
@@ -570,6 +591,7 @@ end;
 procedure TGtk3WSForm.GtkEventDestroy;
 begin
   FWindow:=Nil;
+  g_object_set_data(Window, 'fresnelobject', nil);
   g_object_unref(FIMContext);
   FreeAndNil(FForm);
 end;
@@ -834,6 +856,26 @@ begin
     ceil(aRect.Height));
 end;
 
+procedure TGtk3WSForm.SetPointerCapture(const aPointerId: TFreHandle);
+var
+  w: PGtkWidget;
+begin
+  w:=gtk_grab_get_current;
+  if w=PGtkWidget(Window) then exit;
+  if w<>nil then
+    gtk_grab_remove(w);
+  gtk_grab_add(Window);
+end;
+
+procedure TGtk3WSForm.ReleasePointerCapture(const aPointerId: TFreHandle);
+var
+  w: PGtkWidget;
+begin
+  w:=gtk_grab_get_current;
+  if w=PGtkWidget(Window) then
+    gtk_grab_remove(w);
+end;
+
 procedure TGtk3WSForm.Notification(AComponent: TComponent; Operation: TOperation
   );
 begin
@@ -909,6 +951,7 @@ begin
   //FLLog(etDebug,'TGtk3WSForm.CreateGtkWindow Bounds= %s',[Form.FormBounds.ToString]);
   FWindow := TGtkWindow.new(GTK_WINDOW_TOPLEVEL);
   Result:=Window;
+  g_object_set_data(FWindow, 'fresnelobject', Self);
 
   FWindow^.set_events(GDK_DEFAULT_EVENTS_MASK);