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