BaiduFMX
412 строк · 11.2 Кб
1// ***************************************************************************
2//
3// FMXComponents: Firemonkey Opensource Components Set from China
4//
5// A simple Firemonkey Circle Score Indicator component
6//
7// Copyright 2017 谢顿 (zhaoyipeng@hotmail.com)
8//
9// https://github.com/zhaoyipeng/FMXComponents
10//
11// ***************************************************************************
12// version history
13// 2017-01-20, v0.1.0.0 : first release
14
15unit FMX.CircleScoreIndicator;
16
17interface
18
19uses
20System.Classes,
21System.Types,
22System.Math.Vectors,
23System.UITypes,
24System.Generics.Collections,
25FMX.Controls,
26FMX.Graphics,
27FMX.Objects,
28FMX.ComponentsCommon;
29
30type
31[ComponentPlatformsAttribute(TFMXPlatforms)]
32TFMXCircleScoreIndicator = class(TShape)
33private
34{ Private declarations }
35FMax: Single;
36FMin: Single;
37FStartAngle: Single;
38FThickness: Single;
39FBackStroke: TStrokeBrush;
40FHeadBrush: TStrokeBrush;
41FTailBrush: TStrokeBrush;
42FScoreBrush: TBrush;
43FValue: Single;
44FIsHealthy: Boolean;
45procedure SetMax(const Value: Single);
46procedure SetMin(const Value: Single);
47procedure SetStartAngle(const Value: Single);
48procedure SetThickness(const Value: Single);
49procedure SetBackStroke(const Value: TStrokeBrush);
50procedure SetScoreBrush(const Value: TBrush);
51procedure SetValue(const Value: Single);
52function CreateMatrix(Angle: Single; cp2: TPointF): TMatrix;
53procedure RotateBrush(aBrush: TBrush; Angle: Single);
54procedure SetIsHealthy(const Value: Boolean);
55protected
56procedure Paint; override;
57public
58{ Public declarations }
59constructor Create(AOwner: TComponent); override;
60destructor Destroy; override;
61procedure SetHealthyTheme;
62procedure SetUnhealthyTheme;
63published
64property Align;
65property Anchors;
66property ClipChildren default False;
67property ClipParent default False;
68property Cursor default crDefault;
69property DragMode default TDragMode.dmManual;
70property EnableDragHighlight default True;
71property Enabled default True;
72property Locked default False;
73property Height;
74property HitTest default True;
75property Padding;
76property Opacity;
77property Margins;
78property PopupMenu;
79property Position;
80property RotationAngle;
81property RotationCenter;
82property Scale;
83property Size;
84property Stroke;
85property Visible default True;
86property Width;
87property OnDragEnter;
88property OnDragLeave;
89property OnDragOver;
90property OnDragDrop;
91property OnDragEnd;
92property OnClick;
93property OnDblClick;
94property OnMouseDown;
95property OnMouseMove;
96property OnMouseUp;
97property OnMouseWheel;
98property OnMouseEnter;
99property OnMouseLeave;
100property OnPainting;
101property OnPaint;
102property OnResize;
103property Max: Single read FMax write SetMax;
104property Min: Single read FMin write SetMin;
105property Value: Single read FValue write SetValue;
106property StartAngle: Single read FStartAngle write SetStartAngle;
107property Thickness: Single read FThickness write SetThickness;
108property BackStroke: TStrokeBrush read FBackStroke write SetBackStroke;
109property ScoreBrush: TBrush read FScoreBrush write SetScoreBrush;
110property IsHealthy: Boolean read FIsHealthy write SetIsHealthy;
111end;
112
113implementation
114
115uses
116System.Math;
117
118{ TFMXCircleScore }
119
120constructor TFMXCircleScoreIndicator.Create(AOwner: TComponent);
121var
122g: TGradientPoint;
123begin
124inherited;
125FBackStroke := TStrokeBrush.Create(TBrushKind.Solid, $FFF5E0E2);
126FBackStroke.Thickness := 11;
127FBackStroke.OnChanged := FillChanged;
128FScoreBrush := TBrush.Create(TBrushKind.Gradient, $FFF14D4D);
129FScoreBrush.Gradient.Points.Clear;
130FScoreBrush.Gradient.Points.Add;
131FScoreBrush.Gradient.Points.Add;
132FScoreBrush.Gradient.Points.Add;
133g := FScoreBrush.Gradient.Points[0];
134g.Offset := 0;
135g.Color := $FFF14D4D;
136g := FScoreBrush.Gradient.Points[1];
137g.Offset := 0.5;
138g.Color := $FFE46161;
139g := FScoreBrush.Gradient.Points[2];
140g.Offset := 1.0;
141g.Color := $FFEB8F73;
142FScoreBrush.OnChanged := FillChanged;
143FMin := 0;
144FMax := 100;
145FValue := 45;
146FThickness := 11;
147FHeadBrush := TStrokeBrush.Create(TBrushKind.Solid, $FFF14D4D);
148FTailBrush := TStrokeBrush.Create(TBrushKind.Solid, $FFEB8F73);
149end;
150
151destructor TFMXCircleScoreIndicator.Destroy;
152begin
153FTailBrush.Free;
154FHeadBrush.Free;
155FBackStroke.Free;
156inherited;
157end;
158
159function TFMXCircleScoreIndicator.CreateMatrix(Angle: Single; cp2: TPointF): TMatrix;
160var
161ScaleMatrix: TMatrix;
162M1: TMatrix;
163M2: TMatrix;
164RotMatrix: TMatrix;
165begin
166ScaleMatrix := TMatrix.Identity;
167ScaleMatrix.m11 := Scale.X;
168ScaleMatrix.m22 := Scale.Y;
169Result := ScaleMatrix;
170// rotation
171if Angle <> 0 then
172begin
173M1 := TMatrix.Identity;
174M1.m31 := -cp2.X * Scale.X;
175M1.m32 := -cp2.Y * Scale.Y;
176M2 := TMatrix.Identity;
177M2.m31 := cp2.X * Scale.X;
178M2.m32 := cp2.Y * Scale.Y;
179RotMatrix := M1 * (TMatrix.CreateRotation(DegToRad(Angle)) * M2);
180Result := Result * RotMatrix;
181end;
182end;
183
184procedure TFMXCircleScoreIndicator.Paint;
185var
186r, r1, r2: Single;
187cr: TRectF;
188path: TPathData;
189Angle, delta, stAngle, enAngle: Single;
190Count: Integer;
191I: Integer;
192cp, cp1, cp2, p1, p2, p3, p4: TPointF;
193s, s2: TStrokeBrush;
194v1, v2: Single;
195oldMatrix, M: TMatrix;
196begin
197inherited;
198r := (System.Math.Min(Width, Height) - Thickness) * 0.5;
199r1 := r - Thickness * 0.5;
200r2 := r + Thickness * 0.5;
201
202cr := TRectF.Create(0, 0, 1, 1).FitInto(LocalRect);
203cp := cr.CenterPoint;
204cr.Inflate(-Thickness * 0.5, -Thickness * 0.5);
205Canvas.DrawEllipse(cr, AbsoluteOpacity, FBackStroke);
206
207Angle := 360 * (FValue - FMin) / FMax;
208if SameValue(Angle, 0.0, 1E-4) then
209Exit;
210
211Count := Ceil(Angle / 5.0);
212if Odd(Count) then
213Count := Count + 1;
214delta := Angle / Count;
215path := TPathData.Create;
216s := TStrokeBrush.Create(TBrushKind.Gradient, TAlphaColors.Black);
217s2 := TStrokeBrush.Create(TBrushKind.Solid, TAlphaColors.Black);
218try
219s.Gradient.Points.Clear;
220s.Gradient.Points.Add;
221s.Gradient.Points.Add;
222
223cp1 := PointF(cp.X, cp.Y - r);
224Canvas.FillArc(cp1, PointF(Thickness * 0.5, Thickness * 0.5), -90, -180,
225AbsoluteOpacity, FHeadBrush);
226Canvas.DrawArc(cp1, PointF(Thickness * 0.5, Thickness * 0.5), -90, -180,
227AbsoluteOpacity, FHeadBrush);
228for I := 0 to Count - 1 do
229begin
230v1 := I / Count;
231v2 := (I + 1) / Count;
232
233stAngle := -90 + I * delta;
234enAngle := -90 + (I + 1) * delta;
235
236s.Gradient.Points[0].Color := FScoreBrush.Gradient.InterpolateColor(v1);
237s.Gradient.Points[1].Color := FScoreBrush.Gradient.InterpolateColor(v2);
238RotateBrush(s, stAngle + delta * 0.5);
239
240p1 := PointF(cp.X + r1 * Sin(stAngle), cp.Y + r1 * Cos(stAngle));
241p2 := PointF(cp.X + r1 * Sin(enAngle), cp.Y + r1 * Cos(enAngle));
242p3 := PointF(cp.X + r2 * Sin(enAngle), cp.Y + r2 * Cos(enAngle));
243p4 := PointF(cp.X + r2 * Sin(stAngle), cp.Y + r2 * Cos(stAngle));
244
245path.AddArc(cp, PointF(r1, r1), stAngle, delta);
246path.AddArc(cp, PointF(r2, r2), enAngle, -delta);
247path.ClosePath;
248Canvas.FillPath(path, AbsoluteOpacity, s);
249{$IFDEF MSWINDOWS}
250Canvas.DrawArc(cp, PointF(r1, r1), stAngle, delta, AbsoluteOpacity, s);
251Canvas.DrawArc(cp, PointF(r2, r2), stAngle, delta, AbsoluteOpacity, s);
252s2.Color := FScoreBrush.Gradient.InterpolateColor(v1);
253Canvas.DrawLine(path.Points[0].Point, path.Points[7].Point,
254AbsoluteOpacity, s2);
255{$ENDIF}
256path.Clear;
257end;
258
259cp2 := PointF(cp.X + r * Cos(DegToRad(Angle - 90)),
260cp.Y + r * Sin(DegToRad(Angle - 90)));
261M := CreateMatrix(Angle, cp2);
262oldMatrix := Canvas.Matrix;
263Canvas.MultiplyMatrix(M);
264Canvas.FillArc(cp2, PointF(Thickness * 0.5, Thickness * 0.5), -90, 180,
265AbsoluteOpacity, FTailBrush);
266Canvas.DrawArc(cp2, PointF(Thickness * 0.5, Thickness * 0.5), -90, 180,
267AbsoluteOpacity, FTailBrush);
268Canvas.SetMatrix(oldMatrix);
269finally
270path.Free;
271s.Free;
272s2.Free;
273end;
274end;
275
276procedure TFMXCircleScoreIndicator.RotateBrush(aBrush: TBrush; Angle: Single);
277var
278M1, M2, RotMatrix: TMatrix;
279p1, p2: TPointF;
280begin
281M1 := TMatrix.Identity;
282M1.m31 := -0.5;
283M1.m32 := -0.5;
284M2 := TMatrix.Identity;
285M2.m31 := 0.5;
286M2.m32 := 0.5;
287RotMatrix := M1 * (TMatrix.CreateRotation(DegToRad(Angle)) * M2);
288p1 := PointF(0, 0.5);
289p2 := PointF(1, 0.5);
290p1 := p1 * RotMatrix;
291p2 := p2 * RotMatrix;
292aBrush.Gradient.StartPosition.Point := p1;
293aBrush.Gradient.StopPosition.Point := p2;
294end;
295
296procedure TFMXCircleScoreIndicator.SetBackStroke(const Value: TStrokeBrush);
297begin
298FBackStroke := Value;
299end;
300
301procedure TFMXCircleScoreIndicator.SetHealthyTheme;
302var
303g: TGradientPoint;
304begin
305BeginUpdate;
306FBackStroke.Color := $FFe0f5ef;
307FScoreBrush.Gradient.Points.Clear;
308FScoreBrush.Gradient.Points.Add;
309FScoreBrush.Gradient.Points.Add;
310FScoreBrush.Gradient.Points.Add;
311g := FScoreBrush.Gradient.Points[0];
312g.Offset := 0;
313g.Color := $FF14b98c;
314g := FScoreBrush.Gradient.Points[1];
315g.Offset := 0.5;
316g.Color := $FF0cd49e;
317g := FScoreBrush.Gradient.Points[2];
318g.Offset := 1.0;
319g.Color := $FF73eba0;
320FHeadBrush := TStrokeBrush.Create(TBrushKind.Solid, FScoreBrush.Gradient.Points[0].Color);
321FTailBrush := TStrokeBrush.Create(TBrushKind.Solid, FScoreBrush.Gradient.Points[2].Color);
322EndUpdate;
323end;
324
325procedure TFMXCircleScoreIndicator.SetIsHealthy(const Value: Boolean);
326begin
327if FIsHealthy <> Value then
328begin
329FIsHealthy := Value;
330if FIsHealthy then
331SetHealthyTheme
332else
333SetUnhealthyTheme;
334end;
335end;
336
337procedure TFMXCircleScoreIndicator.SetMax(const Value: Single);
338begin
339if FMax <> Value then
340begin
341FMax := Value;
342Repaint;
343end;
344end;
345
346procedure TFMXCircleScoreIndicator.SetMin(const Value: Single);
347begin
348if FMin <> Value then
349begin
350FMin := Value;
351Repaint;
352end;
353end;
354
355procedure TFMXCircleScoreIndicator.SetScoreBrush(const Value: TBrush);
356begin
357FScoreBrush := Value;
358end;
359
360procedure TFMXCircleScoreIndicator.SetStartAngle(const Value: Single);
361begin
362if FStartAngle <> Value then
363begin
364FStartAngle := Value;
365Repaint;
366end;
367end;
368
369procedure TFMXCircleScoreIndicator.SetThickness(const Value: Single);
370begin
371if FThickness <> Value then
372begin
373FThickness := Value;
374FBackStroke.Thickness := Value;
375Repaint;
376end;
377end;
378
379procedure TFMXCircleScoreIndicator.SetUnhealthyTheme;
380var
381g: TGradientPoint;
382begin
383BeginUpdate;
384FBackStroke.Color := $FFF5E0E2;
385FScoreBrush.Gradient.Points.Clear;
386FScoreBrush.Gradient.Points.Add;
387FScoreBrush.Gradient.Points.Add;
388FScoreBrush.Gradient.Points.Add;
389g := FScoreBrush.Gradient.Points[0];
390g.Offset := 0;
391g.Color := $FFF14D4D;
392g := FScoreBrush.Gradient.Points[1];
393g.Offset := 0.5;
394g.Color := $FFE46161;
395g := FScoreBrush.Gradient.Points[2];
396g.Offset := 1.0;
397g.Color := $FFEB8F73;
398FHeadBrush := TStrokeBrush.Create(TBrushKind.Solid, FScoreBrush.Gradient.Points[0].Color);
399FTailBrush := TStrokeBrush.Create(TBrushKind.Solid, FScoreBrush.Gradient.Points[2].Color);
400EndUpdate;
401end;
402
403procedure TFMXCircleScoreIndicator.SetValue(const Value: Single);
404begin
405if FValue <> Value then
406begin
407FValue := Value;
408Repaint;
409end;
410end;
411
412end.
413