-
-
Notifications
You must be signed in to change notification settings - Fork 156
Expand file tree
/
Copy pathcastlecontrols_shape.inc
More file actions
375 lines (325 loc) · 12 KB
/
castlecontrols_shape.inc
File metadata and controls
375 lines (325 loc) · 12 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
{%MainUnit castlecontrols.pas}
{
Copyright 2010-2024 Michalis Kamburelis.
This file is part of "Castle Game Engine".
"Castle Game Engine" is free software; see the file COPYING.txt,
included in this distribution, for details about the copyright.
"Castle Game Engine" is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
----------------------------------------------------------------------------
}
{$ifdef read_interface}
{ Possible shape type, for @link(TCastleShape.ShapeType). }
TShapeType = (
stRectangle,
stCircle,
stTriangleUp,
stTriangleDown,
stTriangleRight,
stTriangleLeft
);
{ Draw a simple shape (rectangle, circle, triangle) with given color
and optional outline. }
TCastleShape = class(TCastleUserInterface)
strict private
FFilled, FOutline, FOutlineThick: boolean;
FColor, FOutlineColor: TCastleColor;
FOutlineWidth: Single;
FShapeType: TShapeType;
procedure SetShapeType(const Value: TShapeType);
procedure SetFilled(const Value: boolean);
procedure SetColor(const Value: TCastleColor);
procedure SetOutline(const Value: boolean);
procedure SetOutlineColor(const Value: TCastleColor);
procedure SetOutlineWidth(const Value: Single);
procedure SetOutlineThick(const Value: boolean);
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
procedure Render; override;
function PropertySections(const PropertyName: String): TPropertySections; override;
{ The fill color, used if @link(Filled). By default, opaque white. }
property Color: TCastleColor read FColor write SetColor;
{ The outline color, used if @link(Outline). By default, opaque black. }
property OutlineColor: TCastleColor read FOutlineColor write SetOutlineColor;
published
{ The shape (rectangle, circle, triangle) to be drawn. }
property ShapeType: TShapeType read FShapeType write SetShapeType default stRectangle;
{ Determines the drawing method of the outline, used if @link(Outline).
@definitionList(
@itemLabel(@false (default))
@item(Draw the outline using lines, and apply OutlineWidth by changing
line width.
Disadvantage:
@bold(outline widths thicker than 1 pixel are not guaranteed
to be supported. In particular they will almost never work on mobile
(OpenGLES).)
Consider using other methods if you need to draw a thick shape outline
in a reliable manner.
For example, set @link(OutlineThick) to @true.
Or draw the shape as an image with a frame,
using @link(TCastleImageControl) and @link(TCastleImageControl.ProtectedSides).)
@itemLabel(@true)
@item(Draw the outline by first drawing a larger shape with OutlineColor
underneath the smaller shape with Color.
Disadvantages:
@unorderedList(
@item(Cannot work sensibly if @link(Filled) is @false,
so it's disabled then. When @link(Filled) is @false,
it's like OutlineThick was always also @false.)
@item(The alpha blending may not be exactly what you want,
since the pixels inside are overdrawn with both OutlineColor
and then with Color.)
@item(May look a little worse in case of small OutlineWidth
and non-rectangular shapes.)
)
Advantage: thick OutlineWidth works reliably.)
)
}
property OutlineThick: boolean read FOutlineThick write SetOutlineThick default false;
{ The outline width, used if @link(Outline).
It is affected by UI scaling.
If OutlineThick is @false, then
@bold(outline widths thicker than 1 pixel are not guaranteed
to be supported. In particular they will almost never work on mobile (OpenGLES).)
Change OutlineThick to @true to have reliable thick outlines. }
property OutlineWidth: Single read FOutlineWidth write SetOutlineWidth
{$ifdef FPC}default 1.0{$endif};
{ Display outline of the shape using @link(OutlineColor). }
property Outline: boolean read FOutline write SetOutline default false;
{ Display inside of the shape using @link(Color). }
property Filled: boolean read FFilled write SetFilled default true;
{$define read_interface_class}
{$I auto_generated_persistent_vectors/tcastleshape_persistent_vectors.inc}
{$undef read_interface_class}
end;
{$endif read_interface}
{$ifdef read_implementation}
{ TCastleShape --------------------------------------------------------------- }
constructor TCastleShape.Create(AOwner: TComponent);
begin
inherited;
FShapeType := stRectangle;
FFilled := true;
FColor := White;
FOutline := false;
FOutlineWidth := 1.0;
FOutlineColor := Black;
{$define read_implementation_constructor}
{$I auto_generated_persistent_vectors/tcastleshape_persistent_vectors.inc}
{$undef read_implementation_constructor}
end;
destructor TCastleShape.Destroy;
begin
{$define read_implementation_destructor}
{$I auto_generated_persistent_vectors/tcastleshape_persistent_vectors.inc}
{$undef read_implementation_destructor}
inherited;
end;
procedure TCastleShape.SetShapeType(const Value: TShapeType);
begin
if FShapeType <> Value then
begin
FShapeType := Value;
VisibleChange([chRender]);
end;
end;
procedure TCastleShape.SetFilled(const Value: boolean);
begin
if FFilled <> Value then
begin
FFilled := Value;
VisibleChange([chRender]);
end;
end;
procedure TCastleShape.SetColor(const Value: TCastleColor);
begin
if not TCastleColor.PerfectlyEquals(FColor, Value) then
begin
FColor := Value;
VisibleChange([chRender]);
end;
end;
procedure TCastleShape.SetOutline(const Value: boolean);
begin
if FOutline <> Value then
begin
FOutline := Value;
VisibleChange([chRender]);
end;
end;
procedure TCastleShape.SetOutlineColor(const Value: TCastleColor);
begin
if not TCastleColor.PerfectlyEquals(FOutlineColor, Value) then
begin
FOutlineColor := Value;
VisibleChange([chRender]);
end;
end;
procedure TCastleShape.SetOutlineWidth(const Value: Single);
begin
if FOutlineWidth <> Value then
begin
FOutlineWidth := Value;
VisibleChange([chRender]);
end;
end;
procedure TCastleShape.SetOutlineThick(const Value: boolean);
begin
if FOutlineThick <> Value then
begin
FOutlineThick := Value;
VisibleChange([chRender]);
end;
end;
type
TShapeTypeTriangle = stTriangleUp..stTriangleLeft;
{ Draw triangle type indicated by current ShapeType.
Use only when ShapeType is stTriangleXxx.
@param R Rectangle to draw the triangle inside.
@param Color Color of the triangle.
@param Filled Whether we should fill the triangle, otherwise we draw only lines.
@param LineWidth Line width used when Filled = @false.
Note: This is internal, and deliberately not a method of TCastleShape,
to not use some TCastleShape property by accident. }
procedure DrawTriangleCore(const ShapeType: TShapeTypeTriangle;
const R: TFloatRectangle; const Color: TCastleColor; const Filled: Boolean;
const LineWidth: Single = 1.0);
var
Mode: TPrimitiveMode;
Points: array [0..2] of TVector2;
begin
if Filled then
Mode := pmTriangles
else
Mode := pmLineLoop;
case ShapeType of
stTriangleUp:
begin
Points[0] := Vector2(R.Left, R.Bottom);
Points[1] := Vector2(R.Right, R.Bottom);
Points[2] := Vector2((R.Left + R.Right) / 2, R.Top);
end;
stTriangleDown:
begin
Points[0] := Vector2(R.Right, R.Top);
Points[1] := Vector2(R.Left, R.Top);
Points[2] := Vector2((R.Left + R.Right) / 2, R.Bottom);
end;
stTriangleRight:
begin
Points[0] := Vector2(R.Left, R.Bottom);
Points[1] := Vector2(R.Right, (R.Top + R.Bottom) / 2);
Points[2] := Vector2(R.Left, R.Top);
end;
stTriangleLeft:
begin
Points[0] := Vector2(R.Right, R.Top);
Points[1] := Vector2(R.Left, (R.Top + R.Bottom) / 2);
Points[2] := Vector2(R.Right, R.Bottom);
end;
{$ifndef COMPILER_CASE_ANALYSIS}
else raise EInternalError.Create('DrawTriangleCore only useful for triangle shapes');
{$endif}
end;
DrawPrimitive2D(Mode, Points, Color,
{ We specify BlendingSourceFactor, BlendingDestinationFactor, ForceBlending
equal to default, only to be able to provide explicit LineWidth. }
bsSrcAlpha, bdOneMinusSrcAlpha, false, LineWidth);
end;
procedure TCastleShape.Render;
function RectGrowForTriangleOutline(const RR: TFloatRectangle;
const OutlineOut: Single): TFloatRectangle;
var
ExtraGrowTip: Single;
begin
Result := RR.Grow(OutlineOut);
{ Expand RR more in the direction of the triangle, makes the outline look
even in typical cases.
TODO: Use trigonometry to calculate this more precisely. }
ExtraGrowTip := 2 * OutlineOut;
case ShapeType of
stTriangleUp: Result := Result.GrowTop(ExtraGrowTip);
stTriangleDown: Result := Result.GrowBottom(ExtraGrowTip);
stTriangleRight: Result := Result.GrowRight(ExtraGrowTip);
stTriangleLeft: Result := Result.GrowLeft(ExtraGrowTip);
else raise EInternalError.Create('Only triangle ShapeType should be here');
end;
end;
var
RR: TFloatRectangle;
OutlineWidthScaled, OutlineIn, OutlineOut: Single;
begin
inherited;
RR := RenderRect;
OutlineWidthScaled := UIScale * OutlineWidth;
if Filled and Outline and OutlineThick then
begin
{ special case when we use OutlineThick drawing mode }
OutlineIn := -OutlineWidthScaled / 2;
OutlineOut := OutlineWidthScaled / 2;
case ShapeType of
stRectangle:
begin
DrawRectangle(RR.Grow(OutlineOut), OutlineColor);
DrawRectangle(RR.Grow(OutlineIn ), Color);
end;
stCircle:
begin
DrawCircle(RR.Center, RR.Width / 2 + OutlineOut, RR.Height / 2 + OutlineOut, OutlineColor);
DrawCircle(RR.Center, RR.Width / 2 + OutlineIn , RR.Height / 2 + OutlineIn , Color);
end;
stTriangleUp..stTriangleLeft:
begin
DrawTriangleCore(ShapeType, RectGrowForTriangleOutline(RR, OutlineOut), OutlineColor, true);
DrawTriangleCore(ShapeType, RR.Grow(OutlineIn), Color, true);
end;
{$ifndef COMPILER_CASE_ANALYSIS}
else raise EInternalError.Create('TCastleShape.Render: ShapeType not implemented (Outline thick)');
{$endif}
end;
end else
begin
if Filled then
case ShapeType of
stRectangle:
DrawRectangle(RR, Color);
stCircle:
DrawCircle(RR.Center, RR.Width / 2, RR.Height / 2, Color);
stTriangleUp..stTriangleLeft:
DrawTriangleCore(ShapeType, RR, Color, true);
{$ifndef COMPILER_CASE_ANALYSIS}
else raise EInternalError.Create('TCastleShape.Render: ShapeType not implemented');
{$endif}
end;
if Outline then
case ShapeType of
stRectangle:
DrawRectangleOutline(RR, OutlineColor, OutlineWidthScaled);
stCircle:
DrawCircleOutline(RR.Center, RR.Width / 2, RR.Height / 2, OutlineColor, OutlineWidthScaled);
stTriangleUp..stTriangleLeft:
DrawTriangleCore(ShapeType, RR, OutlineColor, false, OutlineWidthScaled);
{$ifndef COMPILER_CASE_ANALYSIS}
else raise EInternalError.Create('TCastleShape.Render: ShapeType not implemented (Outline, not thick)');
{$endif}
end;
end;
end;
function TCastleShape.PropertySections(
const PropertyName: String): TPropertySections;
begin
if ArrayContainsString(PropertyName, [
'ShapeType',
'ColorPersistent', 'OutlineColorPersistent',
'Filled', 'Outline', 'OutlineWidth', 'OutlineThick'
]) then
Result := [psBasic]
else
Result := inherited PropertySections(PropertyName);
end;
{$define read_implementation_methods}
{$I auto_generated_persistent_vectors/tcastleshape_persistent_vectors.inc}
{$undef read_implementation_methods}
{$endif read_implementation}