GR32.Paint.MouseController.pas 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333
  1. unit GR32.Paint.MouseController;
  2. (* ***** BEGIN LICENSE BLOCK *****
  3. * Version: MPL 1.1 or LGPL 2.1 with linking exception
  4. *
  5. * The contents of this file are subject to the Mozilla Public License Version
  6. * 1.1 (the "License"); you may not use this file except in compliance with
  7. * the License. You may obtain a copy of the License at
  8. * http://www.mozilla.org/MPL/
  9. *
  10. * Software distributed under the License is distributed on an "AS IS" basis,
  11. * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  12. * for the specific language governing rights and limitations under the
  13. * License.
  14. *
  15. * Alternatively, the contents of this file may be used under the terms of the
  16. * Free Pascal modified version of the GNU Lesser General Public License
  17. * Version 2.1 (the "FPC modified LGPL License"), in which case the provisions
  18. * of this license are applicable instead of those above.
  19. * Please see the file LICENSE.txt for additional information concerning this
  20. * license.
  21. *
  22. * The Original Code is Paint tools for Graphics32
  23. *
  24. * The Initial Developer of the Original Code is
  25. * Anders Melander, [email protected]
  26. *
  27. * Portions created by the Initial Developer are Copyright (C) 2008-2025
  28. * the Initial Developer. All Rights Reserved.
  29. *
  30. * ***** END LICENSE BLOCK ***** *)
  31. interface
  32. {$INCLUDE GR32.inc}
  33. uses
  34. Classes,
  35. Controls,
  36. GR32,
  37. GR32_Layers,
  38. GR32.Paint.Host.API,
  39. GR32.Paint.Tool.API,
  40. GR32.Paint.Controller.API,
  41. GR32.Paint.MouseController.API;
  42. //------------------------------------------------------------------------------
  43. //
  44. // TBitmap32PaintMouseController
  45. //
  46. //------------------------------------------------------------------------------
  47. // An example implementation of a mouse controller.
  48. // The mouse controller processes mouse event and passes them on to the
  49. // controller.
  50. //------------------------------------------------------------------------------
  51. type
  52. TBitmap32PaintMouseController = class(TInterfacedObject, IBitmap32PaintMouseController)
  53. strict private
  54. FController: IBitmap32PaintController;
  55. FPaintHost: IBitmap32PaintHost;
  56. strict private
  57. // IBitmap32PaintMouseController
  58. procedure HandleMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer; Layer: TCustomLayer);
  59. procedure HandleMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer; Layer: TCustomLayer);
  60. procedure HandleMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer; Layer: TCustomLayer);
  61. procedure MouseEnter;
  62. procedure MouseExit;
  63. strict private
  64. // Mouse state
  65. FMouseShift: TShiftState;
  66. FLastMousePos: TPoint;
  67. FLastMouseMessageTime: Cardinal;
  68. public
  69. constructor Create(const APaintHost: IBitmap32PaintHost; const APaintController: IBitmap32PaintController);
  70. destructor Destroy; override;
  71. end;
  72. //------------------------------------------------------------------------------
  73. //------------------------------------------------------------------------------
  74. //------------------------------------------------------------------------------
  75. implementation
  76. uses
  77. {$if defined(MSWINDOWS)}
  78. Windows,
  79. {$ifend}
  80. {$if defined(UseInlining)}
  81. Types,
  82. {$ifend}
  83. SysUtils,
  84. GR32_System;
  85. {$if not defined(MSWINDOWS)}
  86. type
  87. TMouseMovePoint = record
  88. x, y: integer;
  89. time: cardinal;
  90. end;
  91. const
  92. MaxMouseMovePointCount = 1;
  93. GMMP_USE_DISPLAY_POINTS = 1;
  94. function GetMessageTime: integer;
  95. begin
  96. Result := GR32_System.GetTickCount;
  97. end;
  98. function GetMouseMovePointsEx(cbSize: Cardinal; var lppt, lpptBuf: TMouseMovePoint; nBufPoints: Integer; resolution: Cardinal): Integer;
  99. begin
  100. Result := -1;
  101. end;
  102. {$ifend}
  103. //------------------------------------------------------------------------------
  104. //
  105. // TBitmap32PaintMouseController
  106. //
  107. //------------------------------------------------------------------------------
  108. constructor TBitmap32PaintMouseController.Create(const APaintHost: IBitmap32PaintHost; const APaintController: IBitmap32PaintController);
  109. begin
  110. inherited Create;
  111. FPaintHost := APaintHost;
  112. FController := APaintController;
  113. end;
  114. destructor TBitmap32PaintMouseController.Destroy;
  115. begin
  116. inherited;
  117. end;
  118. //------------------------------------------------------------------------------
  119. procedure TBitmap32PaintMouseController.HandleMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer;
  120. Layer: TCustomLayer);
  121. var
  122. ToolContext: IBitmap32PaintToolContext;
  123. begin
  124. if (Layer <> FPaintHost.PaintLayer) then
  125. exit;
  126. // Save double-click state for use in MouseMove, MouseUp
  127. FMouseShift := Shift * [ssDouble];
  128. // Save time of last mouse down message (for use in mouse movement history)
  129. FLastMouseMessageTime := Cardinal(GetMessageTime);
  130. ToolContext := FController.CreateToolContext;
  131. if (ToolContext = nil) then
  132. exit;
  133. ToolContext.Update(GR32.Point(X, Y), ToolContext.PaintTool.SnapMouse);
  134. ToolContext.MouseParams.ShiftState := Shift;
  135. ToolContext.MouseParams.MouseMessageTime := FLastMouseMessageTime;
  136. // Save last mouse pos in screen coordinates for use with GetMouseMovePointsEx stuff
  137. FLastMousePos := ToolContext.MouseParams.ScreenPos;
  138. FController.MouseDown(ToolContext, Button);
  139. // Prevent nested operations. Happens if you start an operation with mbLeft and
  140. // then press mbRight during the operation.
  141. if (FController.ActivePaintTool <> nil) then
  142. exit;
  143. // BeginOperation will save ToolContext as FActivePaintToolContext if the operation is accepted
  144. if (not FController.BeginOperation(ToolContext)) then
  145. FMouseShift := [];
  146. end;
  147. //------------------------------------------------------------------------------
  148. procedure TBitmap32PaintMouseController.HandleMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer; Layer: TCustomLayer);
  149. const
  150. MaxMouseMovePointCount = 64;
  151. var
  152. MouseMovePoint: TMouseMovePoint;
  153. MouseMovePoints: array[0..MaxMouseMovePointCount-1] of TMouseMovePoint;
  154. MouseMovePointCount: integer;
  155. MouseMovePointIndex: integer;
  156. ScreenPos: TPoint;
  157. LastMouseMessageTime: Cardinal;
  158. LastViewPortPos: TPoint;
  159. LastShiftState: TShiftState;
  160. begin
  161. if (Layer <> FPaintHost.PaintLayer) then
  162. exit;
  163. if (FController.ActivePaintTool = nil) then
  164. exit;
  165. Assert(FController.ActivePaintToolContext <> nil);
  166. LastMouseMessageTime := Cardinal(GetMessageTime);
  167. ScreenPos := FPaintHost.ViewPortToScreen(GR32.Point(X, Y));
  168. // Ignore if mouse didn't move and shift state didn't change.
  169. // Note: There is an ABA race condition here if mouse moves from A to B to A.
  170. if (ScreenPos.X = FLastMousePos.X) and (ScreenPos.Y = FLastMousePos.Y) and (Shift = FController.ActivePaintToolContext.MouseParams.ShiftState) then
  171. exit;
  172. // Fetch history of mouse movement.
  173. // Get mouse coordinates up to and including the current movement message.
  174. // Note: GetMouseMovePointsEx is theoretically subject to ABA race condition.
  175. MouseMovePoint := Default(TMouseMovePoint);
  176. MouseMovePoint.x := ScreenPos.X and $0000FFFF; // Ensure that this number will pass through.
  177. MouseMovePoint.y := ScreenPos.Y and $0000FFFF;
  178. Cardinal(MouseMovePoint.time) := LastMouseMessageTime;
  179. MouseMovePointCount := GetMouseMovePointsEx(SizeOf(MouseMovePoint), MouseMovePoint, MouseMovePoints[0], MaxMouseMovePointCount, GMMP_USE_DISPLAY_POINTS);
  180. if (MouseMovePointCount = -1) then
  181. begin
  182. // If no history was retrieved we just store the current position in the history and proceed with that.
  183. MouseMovePointCount := 1;
  184. MouseMovePoints[0].x := ScreenPos.X;
  185. MouseMovePoints[0].y := ScreenPos.Y;
  186. Cardinal(MouseMovePoints[0].time) := LastMouseMessageTime;
  187. end else
  188. begin
  189. // Discard history older than last point we processed. Entries are stored most recent first.
  190. MouseMovePointIndex := 0;
  191. while (MouseMovePointIndex < MouseMovePointCount) do
  192. begin
  193. // Handle negative coordinates - required for multi monitor
  194. // TODO : Better handling of this; See GetMouseMovePointsEx documentation
  195. if (DWORD(MouseMovePoints[MouseMovePointIndex].x) >= $8000) then
  196. Dec(MouseMovePoints[MouseMovePointIndex].x, $00010000);
  197. if (DWORD(MouseMovePoints[MouseMovePointIndex].y) >= $8000) then
  198. Dec(MouseMovePoints[MouseMovePointIndex].y, $00010000);
  199. if (Cardinal(MouseMovePoints[MouseMovePointIndex].time) < FLastMouseMessageTime) or
  200. ((Cardinal(MouseMovePoints[MouseMovePointIndex].time) = FLastMouseMessageTime) and
  201. (MouseMovePoints[MouseMovePointIndex].x = FLastMousePos.X) and (MouseMovePoints[MouseMovePointIndex].y = FLastMousePos.Y)) then
  202. begin
  203. MouseMovePointCount := MouseMovePointIndex+1;
  204. break;
  205. end;
  206. Inc(MouseMovePointIndex);
  207. end;
  208. end;
  209. FLastMousePos := ScreenPos;
  210. FLastMouseMessageTime := LastMouseMessageTime;
  211. LastViewPortPos := FController.ActivePaintToolContext.MouseParams.ViewPortPos;
  212. LastShiftState := FController.ActivePaintToolContext.MouseParams.ShiftState;
  213. FController.ActivePaintToolContext.MouseParams.ShiftState := Shift + FMouseShift;
  214. MouseMovePointIndex := MouseMovePointCount-1;
  215. while (MouseMovePointIndex >= 0) do
  216. begin
  217. ScreenPos.X := MouseMovePoints[MouseMovePointIndex].x;
  218. ScreenPos.Y := MouseMovePoints[MouseMovePointIndex].y;
  219. FController.ActivePaintToolContext.Update(FPaintHost.ScreenToViewPort(ScreenPos), FController.ActivePaintTool.SnapMouse);
  220. FController.ActivePaintToolContext.MouseParams.MouseMessageTime := Cardinal(MouseMovePoints[MouseMovePointIndex].time);
  221. if (FController.ActivePaintToolContext.MouseParams.ViewPortPos <> LastViewPortPos) or (FController.ActivePaintToolContext.MouseParams.ShiftState <> LastShiftState) then
  222. begin
  223. FController.MouseMove(FController.ActivePaintToolContext);
  224. if (FController.ActivePaintTool <> nil) then
  225. begin
  226. if (not FController.ContinueOperation(FController.ActivePaintToolContext)) then
  227. begin
  228. FMouseShift := [];
  229. break;
  230. end;
  231. end;
  232. end;
  233. Dec(MouseMovePointIndex);
  234. end;
  235. end;
  236. //------------------------------------------------------------------------------
  237. procedure TBitmap32PaintMouseController.HandleMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer;
  238. Layer: TCustomLayer);
  239. begin
  240. // TCustomImage32.DblClick calls MouseUp(mbLeft, [], 0, 0) - ignore this
  241. if (Button = mbLeft) and (Shift = []) and (X = 0) and (Y = 0) then
  242. exit;
  243. if (FController.ActivePaintTool = nil) then
  244. exit;
  245. FController.ActivePaintToolContext.Update(GR32.Point(X, Y), FController.ActivePaintTool.SnapMouse);
  246. FController.ActivePaintToolContext.MouseParams.ShiftState := Shift + FMouseShift;
  247. FController.ActivePaintToolContext.MouseParams.MouseMessageTime := Cardinal(GetMessageTime);
  248. FLastMousePos := FController.ActivePaintToolContext.MouseParams.ScreenPos;
  249. FController.MouseUp(FController.ActivePaintToolContext, Button);
  250. if (FController.ActivePaintTool <> nil) then
  251. begin
  252. FController.EndOperation(True);
  253. FMouseShift := [];
  254. end;
  255. end;
  256. //------------------------------------------------------------------------------
  257. procedure TBitmap32PaintMouseController.MouseEnter;
  258. begin
  259. FController.MouseEnter;
  260. end;
  261. procedure TBitmap32PaintMouseController.MouseExit;
  262. begin
  263. FController.MouseExit;
  264. end;
  265. //------------------------------------------------------------------------------
  266. end.