1 {
2 Implements non-native regions with support for managing their Z-order
3
4 Author: Felipe Monteiro de Carvalho
5 }
6 unit LazRegions;
7
8 {$mode objfpc}{$H+}
9
10 interface
11
12 uses
13 Classes, SysUtils, fpcanvas,
14 LCLType;
15
16 type
17 TLazRegionFillMode = (rfmOddEven, rfmWinding);
18
19 TPointArray = array of TPoint;
20
21 { TLazRegionPart }
22
23 TLazRegionPart = class
24 public
GetBoundingRectnull25 function GetBoundingRect: TRect; virtual;
IsPointInPartnull26 function IsPointInPart(AX, AY: Integer): Boolean; virtual;
27 end;
28
29 { TLazRegionRect }
30
31 TLazRegionRect = class(TLazRegionPart)
32 public
33 Rect: TRect;
IsPointInPartnull34 function IsPointInPart(AX, AY: Integer): Boolean; override;
35 end;
36
37 { TLazRegionPolygon }
38
39 TLazRegionPolygon = class(TLazRegionPart)
40 public
41 Points: array of TPoint;
42 FillMode: TLazRegionFillMode;
IsPointInPartnull43 function IsPointInPart(AX, AY: Integer): Boolean; override;
44 end;
45
46 { TLazRegionEllipse }
47
48 TLazRegionEllipse = class(TLazRegionPart)
49 public
50 X1, Y1, X2, Y2: Integer;
IsPointInPartnull51 function IsPointInPart(AX, AY: Integer): Boolean; override;
52 end;
53
54 {$if defined(ver2_6)}
55 TFPCustomRegion = class
GetBoundingRectnull56 function GetBoundingRect: TRect; virtual; abstract;
IsPointInRegionnull57 function IsPointInRegion(AX, AY: Integer): Boolean; virtual; abstract;
58 end;
59 {$endif}
60
61 TLazRegion = class(TFPCustomRegion)
62 public
63 // The parts of a region should all be inside valid areas of the region
64 // so if a combination operation removes some areas of the region, then
65 // these areas should be removed from all parts of the region
66 // There is no z-order for the parts, they are all validly inside the region area
67 Parts: TFPList; // of TLazRegionPart
68 IsSimpleRectRegion: Boolean; // Indicates whether this region has only 1 rectangular part
69 Rect: TRect; // Used for performance increase when IsSimpleRectRegion is on
70 constructor Create; virtual;
71 destructor Destroy; override;
72 // Management operations
73 procedure Assign(ASrcRegion: TLazRegion);
74 procedure Clear;
75 procedure CombineWith(ASrcRegion: TLazRegion; AOperation: Longint);
GetRegionKindnull76 function GetRegionKind(): Longint;
IsSimpleRectEmptynull77 function IsSimpleRectEmpty: Boolean;
78 // Setting the contents
79 procedure AddPart(APart: TLazRegionPart);
80 procedure AddRectangle(ARect: TRect);
81 procedure AddPolygon(var APoints: TPointArray; AFillMode: TLazRegionFillMode);
82 procedure AddEllipse(AX1, AY1, AX2, AY2: Integer);
83 procedure SetAsSimpleRectRegion(ARect: TRect);
84 procedure AddPartsFromRegion(ASrcRegion: TLazRegion);
85 procedure DoChangeToComplexRegion;
86 // Overrides of TFPCustomRegion information query routines
GetBoundingRectnull87 function GetBoundingRect: TRect; override;
IsPointInRegionnull88 function IsPointInRegion(AX, AY: Integer): Boolean; override;
89 end;
90
91 { This is a region which can hold other region holders inside it }
92
93 { TLazRegionWithChilds }
94
95 TLazRegionWithChilds = class(TLazRegion)
96 public
97 Parent: TLazRegionWithChilds;
98 // The order in this list is also the Z-Order of the sub regions inside it
99 // The element with index zero is the bottom-most one
100 Childs: TFPList; // of TLazRegionWithChilds
101 UserData: TObject; // available link to another object
102 constructor Create; override;
103 destructor Destroy; override;
IsPointInRegionnull104 function IsPointInRegion(AX, AY: Integer): TLazRegionWithChilds; virtual; reintroduce;
105 end;
106
IsPointInPolygonnull107 function IsPointInPolygon(AX, AY: Integer; const APolygon: array of TPoint): Boolean;
108
109 implementation
110
willnull111 // The function will return True if the point x,y is inside the polygon, or
112 // False if it is not.
113 //
114 // Original C code: http://www.visibone.com/inpoly/inpoly.c.txt
115 //
116 // Translation from C by Felipe Monteiro de Carvalho
117 //
118 // License: Public Domain
119 function IsPointInPolygon(AX, AY: Integer; const APolygon: array of TPoint): Boolean;
120 var
121 xnew, ynew: Cardinal;
122 xold,yold: Cardinal;
123 x1,y1: Cardinal;
124 x2,y2: Cardinal;
125 i, npoints: Integer;
126 inside: Integer = 0;
127 begin
128 Result := False;
129 npoints := Length(APolygon);
130 if (npoints < 3) then Exit;
131 xold := APolygon[npoints-1].X;
132 yold := APolygon[npoints-1].Y;
133 for i := 0 to npoints - 1 do
134 begin
135 xnew := APolygon[i].X;
136 ynew := APolygon[i].Y;
137 if (xnew > xold) then
138 begin
139 x1:=xold;
140 x2:=xnew;
141 y1:=yold;
142 y2:=ynew;
143 end
144 else
145 begin
146 x1:=xnew;
147 x2:=xold;
148 y1:=ynew;
149 y2:=yold;
150 end;
151 if (((xnew < AX) = (AX <= xold)) // edge "open" at left end
152 and ((AY-y1)*(x2-x1) < (y2-y1)*(AX-x1))) then
153 begin
154 inside := not inside;
155 end;
156 xold:=xnew;
157 yold:=ynew;
158 end;
159 Result := inside <> 0;
160 end;
161
162 { TLazRegionEllipse }
163
164 {
165 The equation of the inner area of an axis aligned ellipse:
166
167 (X/a)^2 + (Y/b)^2 <= 1
168 }
IsPointInPartnull169 function TLazRegionEllipse.IsPointInPart(AX, AY: Integer): Boolean;
170 var
171 a, b: Integer;
172 begin
173 a := X2 - X1;
174 b := Y2 - Y1;
175 if (a < 0) or (b < 0) then Exit(False);
176
177 Result := Sqr(AX/a) + Sqr(AY/b) <= 1;
178 end;
179
180 { TLazRegionPart }
181
TLazRegionPart.GetBoundingRectnull182 function TLazRegionPart.GetBoundingRect: TRect;
183 begin
184 Result := Bounds(0, 0, 0, 0);
185 end;
186
IsPointInPartnull187 function TLazRegionPart.IsPointInPart(AX, AY: Integer): Boolean;
188 begin
189 Result := False;
190 end;
191
192 { TLazRegionRect }
193
IsPointInPartnull194 function TLazRegionRect.IsPointInPart(AX, AY: Integer): Boolean;
195 begin
196 Result := (AX >= Rect.Left) and (AX <= Rect.Right) and
197 (AY >= Rect.Top) and (AY <= Rect.Bottom);
198 end;
199
200 { TLazRegionPolygon }
201
TLazRegionPolygon.IsPointInPartnull202 function TLazRegionPolygon.IsPointInPart(AX, AY: Integer): Boolean;
203 begin
204 Result := IsPointInPolygon(AX, AY, Points);
205 end;
206
207 { TLazRegion }
208
209 constructor TLazRegion.Create;
210 begin
211 inherited Create;
212 Parts := TFPList.Create;
213 IsSimpleRectRegion := True;
214 end;
215
216 destructor TLazRegion.Destroy;
217 begin
218 Parts.Free;
219 inherited Destroy;
220 end;
221
222 procedure TLazRegion.Assign(ASrcRegion: TLazRegion);
223 begin
224 Clear;
225 AddPartsFromRegion(ASrcRegion);
226 end;
227
228 procedure TLazRegion.Clear;
229 var
230 i: Integer;
231 begin
232 // Free all items
233 for i := 0 to Parts.Count - 1 do
234 TLazRegionPart(Parts.Items[i]).Free;
235 Parts.Clear;
236
237 IsSimpleRectRegion := True;
238 Rect := Bounds(0, 0, 0, 0);
239 end;
240
241 procedure TLazRegion.CombineWith(ASrcRegion: TLazRegion; AOperation: Longint);
242 begin
243 case AOperation of
244 {RGN_AND:
245 QRegion_intersected(RSrc1, RDest, RSrc2);}
246 RGN_COPY:
247 begin
248 Assign(ASrcRegion);
249 end;
250 { RGN_DIFF:
251 QRegion_subtracted(RSrc1, RDest, RSrc2);}
252 RGN_OR:
253 AddPartsFromRegion(ASrcRegion);
254 {RGN_XOR:
255 QRegion_xored(RSrc1, RDest, RSrc2);}
256 end;
257 end;
258
TLazRegion.GetRegionKindnull259 function TLazRegion.GetRegionKind: Longint;
260 begin
261 if not IsSimpleRectRegion then
262 Result := COMPLEXREGION
263 else if IsSimpleRectEmpty() then
264 Result := NULLREGION
265 else
266 Result := SIMPLEREGION;
267 end;
268
IsSimpleRectEmptynull269 function TLazRegion.IsSimpleRectEmpty: Boolean;
270 begin
271 Result := (Rect.Bottom - Rect.Top <= 0) or (Rect.Right - Rect.Left <= 0);
272 end;
273
274 procedure TLazRegion.AddPart(APart: TLazRegionPart);
275 begin
276 Parts.Add(APart);
277 DoChangeToComplexRegion();
278 end;
279
280 procedure TLazRegion.AddRectangle(ARect: TRect);
281 var
282 lNewRect: TLazRegionRect;
283 begin
284 lNewRect := TLazRegionRect.Create;
285 lNewRect.Rect := ARect;
286 AddPart(lNewRect);
287 end;
288
289 procedure TLazRegion.AddPolygon(var APoints: TPointArray;
290 AFillMode: TLazRegionFillMode);
291 var
292 lNewPolygon: TLazRegionPolygon;
293 begin
294 lNewPolygon := TLazRegionPolygon.Create;
295 lNewPolygon.Points := APoints;
296 lNewPolygon.FillMode := AFillMode;
297 AddPart(lNewPolygon);
298 end;
299
300 procedure TLazRegion.AddEllipse(AX1, AY1, AX2, AY2: Integer);
301 var
302 lNewEllipse: TLazRegionEllipse;
303 begin
304 lNewEllipse := TLazRegionEllipse.Create;
305 lNewEllipse.X1 := AX1;
306 lNewEllipse.Y1 := AY1;
307 lNewEllipse.X2 := AX2;
308 lNewEllipse.Y2 := AY2;
309 AddPart(lNewEllipse);
310 end;
311
312 procedure TLazRegion.AddPartsFromRegion(ASrcRegion: TLazRegion);
313 var
314 i: Integer;
315 begin
316 if ASrcRegion.IsSimpleRectRegion then
317 begin
318 if IsSimpleRectRegion and IsSimpleRectEmpty() then
319 Rect := ASrcRegion.Rect
320 else
321 AddRectangle(ASrcRegion.Rect);
322 end
323 else
324 begin
325 for i := 0 to ASrcRegion.Parts.Count-1 do
326 begin
327 Parts.Add(ASrcRegion.Parts.Items[i]);
328 end;
329 IsSimpleRectRegion := False;
330 end;
331 end;
332
333 procedure TLazRegion.DoChangeToComplexRegion;
334 var
335 OldIsSimpleRectRegion: Boolean;
336 begin
337 OldIsSimpleRectRegion := IsSimpleRectRegion; // This avoids an endless loop when calling AddRectangle
338 IsSimpleRectRegion := False;
339 if OldIsSimpleRectRegion and (not IsSimpleRectEmpty()) then
340 AddRectangle(Rect);
341 end;
342
343 procedure TLazRegion.SetAsSimpleRectRegion(ARect: TRect);
344 begin
345 Clear;
346 IsSimpleRectRegion := True;
347 Rect := ARect;
348 end;
349
TLazRegion.GetBoundingRectnull350 function TLazRegion.GetBoundingRect: TRect;
351 begin
352 Result := Rect;
353 end;
354
355 {
356 Checks if a point is inside this region
357 }
IsPointInRegionnull358 function TLazRegion.IsPointInRegion(AX, AY: Integer): Boolean;
359 var
360 i: Integer;
361 begin
362 if IsSimpleRectRegion then
363 begin
364 Result := (AX >= Rect.Left) and (AX <= Rect.Right) and
365 (AY >= Rect.Top) and (AY <= Rect.Bottom);
366 end
367 else
368 begin
369 Result := False;
370 for i := 0 to Parts.Count-1 do
371 begin
372 // being inside 1 subpart is enough
373 if TLazRegionPart(Parts.Items[i]).IsPointInPart(AX, AY) then
374 begin
375 Result := True;
376 Exit;
377 end;
378 end;
379 end;
380 end;
381
382 { TLazRegionWithChilds }
383
384 constructor TLazRegionWithChilds.Create;
385 begin
386 inherited Create;
387 Childs := TFPList.Create;
388 end;
389
390 destructor TLazRegionWithChilds.Destroy;
391 begin
392 Childs.Free;
393 inherited Destroy;
394 end;
395
396 {
397 Returns itself or a child, depending on where the point was found
398 or nil if the point is neither in the region nor in any children
399
400 Part of the behavior is implemented in TLazRegionWithChilds
401 }
TLazRegionWithChilds.IsPointInRegionnull402 function TLazRegionWithChilds.IsPointInRegion(AX, AY: Integer): TLazRegionWithChilds;
403 var
404 i: Integer;
405 lIsInside: Boolean;
406 begin
407 Result := nil;
408 // First check if it is inside itself
409 lIsInside := inherited IsPointInRegion(AX, AY);
410
411 // If it is, then check if it is in any of the children
412 if lIsInside then
413 begin
414 Result := nil;
415
416 // The order here is important to respect the Z-order of controls
417 for i := Childs.Count-1 downto 0 do
418 begin
419 Result := TLazRegionWithChilds(Childs.Items[i]).IsPointInRegion(AX, AY);
420 if Result <> nil then Break;
421 end;
422
423 // if it wasn't in any sub region, it is really in this region
424 if Result = nil then Result := Self;
425 end;
426 end;
427
428 end.
429
430