{ fpGUI - Free Pascal GUI Toolkit Copyright (C) 2006 - 2010 See the file AUTHORS.txt, included in this distribution, for details of the copyright. See the file COPYING.modifiedLGPL, included in this distribution, for details about redistributing fpGUI. This program 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. Description: A Gauge component that supports different display styles. eg: Needle, Dial, Pie etc. } unit fpg_gauge; {$mode objfpc}{$H+} interface uses Classes, SysUtils, math, fpg_base, fpg_main, fpg_widget; type TGaugeKind = (gkText, gkHorizontalBar, gkVerticalBar, gkPie, gkNeedle, gkDial); TBorderStyle = (bsNone, bsSingle, bsDouble, bsRaisedPanel, bsSunkenPanel, bsRaised3D, bsSunken3D, bsEtched, bsEmmbossed); TfpgGauge = class(TfpgWidget) private FFont: TfpgFont; FClientRect: TfpgRect; FMin: Longint; FMax: Longint; FPosition: Longint; FKind: TGaugeKind; FShowText: Boolean; { TODO: Implement Border style } FBorderStyle: TBorderStyle; FColor: TfpgColor; // Background color { Currently little used colors, should be derived from style and possibly overriden by user TODO - How to deal with gradients? Starting color and compute ending, or give pair? } FFirstColor: TfpgColor; // Text and Needle color FSecondColor: TfpgColor; // Bar, Pie etc. main color { TODO: Currently unused. Implement Low Watermark and High Watermark } // FLWMColor: TfpgColor; // Low Watermark Color // FLWMValue: Longint; // Low Watermark Value // FHWMColor: TfpgColor; // High Watermark Color // FHWMValue: Longint; // High Watermark Color procedure BackgroundDraw; procedure TextDraw; procedure BarDraw; procedure PieDraw; procedure NeedleDraw; procedure DialDraw; procedure SetGaugeKind(AValue: TGaugeKind); procedure SetShowText(AValue: Boolean); procedure SetBorderStyle(AValue: TBorderStyle); procedure SetFirstColor(AValue: TfpgColor); procedure SetSecondColor(AValue: TfpgColor); procedure SetMin(AValue: Longint); procedure SetMax(AValue: Longint); procedure SetProgress(AValue: Longint); function GetPercentage: Longint; protected procedure HandlePaint; override; public constructor Create(AOwner: TComponent); override; procedure AddProgress(AValue: Longint); property Percentage: Longint read GetPercentage; property Font: TfpgFont read FFont; published property Align; property Anchors; property BorderStyle: TBorderStyle read FBorderStyle write SetBorderStyle default bsSingle; property Color: TfpgColor read FColor write FColor default clButtonFace; property Enabled; property FirstColor: TfpgColor read FFirstColor write SetFirstColor default clBlack; property Hint; property Kind: TGaugeKind read FKind write SetGaugeKind default gkHorizontalBar; property MaxValue: Longint read FMax write SetMax default 100; property MinValue: Longint read FMin write SetMin default 0; property ParentShowHint; property Progress: Longint read FPosition write SetProgress; property SecondColor: TfpgColor read FSecondColor write SetSecondColor default clWhite; property ShowHint; property ShowText: Boolean read FShowText write SetShowText default True; property Visible; property OnShowHint; end; // A convenience function to quickly create a gauge from code function CreateGauge (AOwner: TComponent; ALeft, ATop, AWidth, AHeight: TfpgCoord; AKind: TGaugeKind ): TfpgGauge; implementation uses fpg_wuline; { This procedure draws a filled arc with a color gradient - to be moved in CanvasBase? } procedure FillArcGradient(canvas: TfpgCanvas; X,Y,W,H: TfpgCoord; a1,a2: double; Astart,Astop: TfpgColor); var RGBStart: TFPColor; RGBStop: TFPColor; RDiff, GDiff, BDiff: Integer; count: Integer; i: Integer; newcolor: TFPColor; begin if Astart = Astop then begin { No gradient, just solid color} canvas.SetColor(Astart); canvas.FillArc(X, Y, W, H, a1, a2); Exit; //==> end; RGBStart := fpgColorToFPColor(fpgColorToRGB(AStart)); RGBStop := fpgColorToFPColor(fpgColorToRGB(AStop)); count := min(H,W); count := count div 2; count := count -2 ; RDiff := RGBStop.Red - RGBStart.Red; GDiff := RGBStop.Green - RGBStart.Green; BDiff := RGBStop.Blue - RGBStart.Blue; { X11 draws arcs at one pixel distance without leaving out pixels, so Line Width of 1 would be appropriate, but GDI doesn't, and therefore Line Width 2 is required to make both work} //canvas.SetLineStyle(1,lsSolid); canvas.SetLineStyle(2,lsSolid); for i := 0 to count do begin X := X + 1; Y := Y + 1; W := W - 2; H := H - 2; newcolor.Red := RGBStart.Red + (i * RDiff) div count; newcolor.Green := RGBStart.Green + (i * GDiff) div count; newcolor.Blue := RGBStart.Blue + (i * BDiff) div count; canvas.SetColor(FPColorTofpgColor(newcolor)); canvas.DrawArc(X, Y, W, H, a1, a2); end; end; function CreateGauge(AOwner: TComponent; ALeft, ATop, AWidth, AHeight: TfpgCoord; AKind: TGaugeKind): TfpgGauge; begin Result := TfpgGauge.Create(AOwner); Result.Left := ALeft; Result.Top := ATop; Result.Width := AWidth; Result.Height := AHeight; Result.Kind := AKind; end; { TfpgGauge } { Drawing procedures - they're called from HandlePaint, which takes care of Canvas.BeginDraw and Canvas.EndDraw - Shouldn't be used otherwise. } procedure TfpgGauge.BackgroundDraw; begin {common Background for all kinds } {Client area is Widget area, to start with} FClientRect.SetRect(0, 0, Width, Height); Canvas.ClearClipRect; Canvas.Clear(Color); { This must be adjusted according the selected style } Canvas.SetColor(TfpgColor($999999)); Canvas.SetLineStyle(1, lsSolid); Canvas.DrawRectangle(FClientRect); { This must be completed and adjusted with border style } InflateRect(FClientRect, -1, -1); with FClientRect do begin { Kind specific Bacground } case FKind of { Currently Text doesn't require additional Bacground } { And so horizontal and vertical bar - Unless style requires it} gkHorizontalBar, gkVerticalBar: begin Canvas.SetLineStyle(1, lsSolid); // just in case background changed that end; gkPie: begin { Round frame for the Pie } Canvas.SetLineStyle(2, lsSolid); Canvas.SetColor(TfpgColor($98b2ed)); Canvas.DrawArc(Left, Top, Width, Height, 0, 360); end; gkNeedle: begin { Half a filled circle background for needle } FillArcGradient(Canvas,Left, Top, Width, Height * 2 -1, 0, 180,TfpgColor($425d9b),TfpgColor($98b2ed)); Canvas.SetLineStyle(2, lsSolid); //Canvas.SetColor(TfpgColor($3b4c71)); Canvas.SetColor(TfpgColor($98b2ed)); Canvas.DrawArc(Left, Top, Width, Height * 2 - 1, 0, 180); Canvas.SetLineStyle(1, lsSolid); Canvas.SetColor(TfpgColor($3b4c71)); Canvas.DrawLine(Left, Bottom,Left + Width, Bottom); end; gkDial: begin { 270° pie shaped background for Dial } FillArcGradient (Canvas,Left, Top, Width, Height , 225, -270 ,TfpgColor($425d9b),TfpgColor($98b2ed)); Canvas.SetLineStyle(2, lsSolid); //Canvas.SetColor(TfpgColor($3b4c71)); Canvas.SetColor(TfpgColor($98b2ed)); Canvas.DrawArc(Left,Top,Width,Height,225,-270); end; end; end; { with } end; procedure TfpgGauge.TextDraw; var S: string; X, Y: Integer; begin S := Format('%d%%', [Percentage]); with FClientRect do begin X := (Width - FFont.TextWidth(S)) div 2; Y := (Height - FFont.Height) div 2; if Kind = gkDial then Y := Y + (Y div 2); end; { If contrast is poor we might use a Xor function } Canvas.SetTextColor(FirstColor); Canvas.Font := FFont; Canvas.DrawString(x, y, S); end; procedure TfpgGauge.BarDraw; var BarLength: Longint; SavedRect: TfpgRect; begin SavedRect := FClientRect; // save client rect for text !! with FClientRect do begin case FKind of gkHorizontalBar: begin BarLength := Longint(Trunc( (Width * Percentage) / 100.0 ) ); if BarLength > 0 then begin if BarLength > Width then BarLength := Width; Width := BarLength; // left top Canvas.SetColor(TfpgColor($98b2ed)); Canvas.DrawLine(Left, Bottom, Left, Top); // left Canvas.DrawLine(Left, Top, Right, Top); // top // right bottom Canvas.SetColor(TfpgColor($3b4c71)); Canvas.DrawLine(Right, Top, Right, Bottom); // right Canvas.DrawLine(Right, Bottom, Left, Bottom); // bottom // inside gradient fill InflateRect(FClientRect, -1, -1); Canvas.GradientFill(FClientRect, TfpgColor($425d9b), TfpgColor($97b0e8), gdVertical); end; { if } end; gkVerticalBar: begin BarLength := Longint(Trunc( (Height * Percentage) / 100.0 ) ); if BarLength > 0 then begin if BarLength > Height then BarLength := Height; Top := Height - BarLength+1; Height := BarLength; // left top Canvas.SetColor(TfpgColor($98b2ed)); Canvas.DrawLine(Left, Bottom, Left, Top); // left Canvas.DrawLine(Left, Top, Right, Top); // top // right bottom Canvas.SetColor(TfpgColor($3b4c71)); Canvas.DrawLine(Right, Top, Right, Bottom); // right Canvas.DrawLine(Right, Bottom, Left, Bottom); // bottom // inside gradient fill InflateRect(FClientRect, -1, -1); Canvas.GradientFill(FClientRect, TfpgColor($425d9b), TfpgColor($97b0e8), gdHorizontal); end; end; { if } end; { case } end; { with } FClientRect := SavedRect; end; procedure TfpgGauge.PieDraw; var Angle: Double; begin with FClientRect do begin Angle := Percentage; Angle := Angle * 3.6; // Percentage to degrees Canvas.SetColor(TfpgColor($425d9b)); FillArcGradient (Canvas,Left, Top, Width, Height , 90, -Angle,TfpgColor($425d9b),TfpgColor($98b2ed)); end; end; procedure TfpgGauge.NeedleDraw; var Center: TPoint; Radius: TPoint; Angle: Double; begin with FClientRect do begin if Percentage > 0 then begin { Compute the center } Center := CenterPoint(Rect(Left,Top,Width,Height)); { Make needle 4 pixel shorter than gauge radius to accomodate border } Radius.X := Center.X - 4; Radius.Y := (Bottom - 4); Canvas.SetLineStyle(2,lsSolid); Angle := (Pi * ((Percentage / 100.0))); // percentage to radiants Canvas.SetColor(TfpgColor($3b4c71)); Canvas.SetLineStyle(2,lsSolid); //Canvas.DrawLine(Center.X, FClientRect.Bottom, //Integer(round(Center.X - (Radius.X * Cos(Angle)))), //Integer(round((FClientRect.Bottom) - (Radius.Y * Sin(Angle))))); { *** Experimental *** } WuLine(Canvas, Point(Center.X, FClientRect.Bottom), Point(Integer(round(Center.X - (Radius.X * Cos(Angle)))), Integer(round((FClientRect.Bottom) - (Radius.Y * Sin(Angle))))), Canvas.Color); WuLine(Canvas, Point(Center.X+1, FClientRect.Bottom), Point(Integer(round(Center.X+1 - (Radius.X * Cos(Angle)))), Integer(round((FClientRect.Bottom) - (Radius.Y * Sin(Angle))))), Canvas.Color); end; end; end; procedure TfpgGauge.DialDraw; var Center: TPoint; Radius: TPoint; Angle: Double; CenterDot: Integer; begin with FClientRect do begin if Percentage >= 0 then begin { Compute the center } Center := CenterPoint(Rect(Left,Top,Width,Height)); { Make needle 3 pixel shorter than gauge radius } Radius.X := Center.X -3; Radius.Y := Center.Y -3; {compute centre circle size} CenterDot := (Width + Height) div 40; // approx. scaled to 1/10 of widget size: if CenterDot < 2 then CenterDot := 2; { draw needle centre circle } Canvas.SetColor(TfpgColor($3b4c71)); Canvas.FillArc(Center.X - CenterDot, Center.Y - CenterDot,CenterDot * 2, CenterDot * 2,0,360); { draw needle } Angle := (Pi * ((Percentage / (100 * 2 / 3)) + -0.25)); Canvas.SetLineStyle(2,lsSolid); //Canvas.DrawLine(Center.X, Center.Y, //Integer(round(Center.X - ( Radius.X * cos(Angle)))), //Integer(round((Center.Y) - (Radius.Y * Sin(Angle))))); { *** Experimental *** } WuLine(Canvas, Point(Center.X, Center.Y), Point(Integer(round(Center.X - ( Radius.X * cos(Angle)))), Integer(round((Center.Y) - (Radius.Y * Sin(Angle))))), Canvas.Color); WuLine(Canvas, Point(Center.X+1, Center.Y), Point(Integer(round(Center.X+1 - ( Radius.X * cos(Angle)))), Integer(round((Center.Y) - (Radius.Y * Sin(Angle))))), Canvas.Color); end; { if } end; { with } end; procedure TfpgGauge.HandlePaint; begin inherited HandlePaint; // Canvas.BeginDraw(True); {Paint Background and adjust FClientRect according style and BorderStyle} BackgroundDraw; {Paint foreground according selected Kind} case FKind of gkHorizontalBar, gkVerticalBar: BarDraw; gkPie: PieDraw; gkNeedle: NeedleDraw; gkDial: DialDraw; end; {Add Text if required} if ShowText then TextDraw; // Canvas.EndDraw; end; procedure TfpgGauge.SetGaugeKind(AValue: TGaugeKind); begin if AValue <> FKind then begin FKind := AValue; RePaint; end; end; procedure TfpgGauge.SetShowText(AValue: Boolean); begin if AValue <> FShowText then begin FShowText := AValue; RePaint; end; end; procedure TfpgGauge.SetBorderStyle(AValue: TBorderStyle); begin if AValue <> FBorderStyle then begin FBorderStyle := AValue; { TODO: Implement Border style } // Graeme: Wouldn't descending from TfpgBevel give you this functionality already? // It could be a option. //RePaint; end; end; procedure TfpgGauge.SetFirstColor(AValue: TfpgColor); begin if AValue <> FFirstColor then begin FFirstColor := AValue; { TODO: allow user colors} //RePaint; end; end; procedure TfpgGauge.SetSecondColor(AValue: TfpgColor); begin if AValue <> FSecondColor then begin FSecondColor := AValue; { TODO: allow user colors} //RePaint; end; end; procedure TfpgGauge.SetMin(AValue: Longint); begin if AValue <> FMin then begin // correct input errors if AValue > FMax then if not (csLoading in ComponentState) then FMax := AValue + 1; if FPosition < AValue then FPosition := AValue; // then update FMin := AValue; RePaint; end; end; procedure TfpgGauge.SetMax(AValue: Longint); begin if AValue <> FMax then begin // correct input errors if AValue < FMin then if not (csLoading in ComponentState) then FMin := AValue - 1; if FPosition > AValue then FPosition := AValue; // then update FMax := AValue; RePaint; end; end; procedure TfpgGauge.SetProgress(AValue: Longint); var CurrPercentage: Longint; MustRepaint: Boolean; begin CurrPercentage := GetPercentage; MustRepaint := False; if AValue < FMin then AValue := FMin else if AValue > FMax then AValue := FMax; if FPosition <> AValue then begin // Value has changed FPosition := AValue; if CurrPercentage <> Percentage then // Visible value has changed MustRepaint := True; { TODO: Check against low and high watermarks } end; if MustRepaint then RePaint; end; function TfpgGauge.GetPercentage: Longint; Var V,T: Longint; begin T := FMax - FMin; V := FPosition - FMin; if T = 0 then Result := 0 else Result := Longint(Trunc( (V * 100.0) / T )); end; constructor TfpgGauge.Create(AOwner: TComponent); begin inherited Create(AOwner); Focusable := False; FWidth := 100; FHeight := 25; FKind := gkHorizontalBar; FSecondColor := clWhite; FFirstColor := clBlack; FColor := TfpgColor($c4c4c4); //clInactiveWgFrame; FMax := 100; FMin := 0; FPosition := 0; FShowText := True; FBorderStyle := bsNone; FFont := fpgStyle.DefaultFont; end; procedure TfpgGauge.AddProgress(AValue: Longint); begin Progress := FPosition + AValue; end; end.