{****************************************************
 This file is part of the Eye Candy Controls (EC-C)
 
  Copyright (C) 2013 Vojtěch Čihák, Czech Republic
 
  This library is free software.

  See the file COPYING.LGPL.txt, 
  included in this distribution,
  for details about the license.
****************************************************}   

unit ECProgressBar;
{$mode objfpc}{$H+}

//{$DEFINE DBGPROGBAR}  {don't remove, just comment}

interface

uses
  Classes, SysUtils, Controls, Graphics, LCLIntf, LCLProc, LResources,
  Math, ECSlider, ECTypes, Types;

type  
  {$PACKENUM 2}
  TProgressTextStyle = (eptNone, eptSolid, eptInverted);
  
  { TCustomECProgressBar }
  TCustomECProgressBar = class(TBaseECSlider)
  private
    FCaptionInline: Boolean;
    FProgressDigits: Word;
    FProgressFontOptions: TFontOptions;
    FProgressTextAlign: SmallInt;
    FProgressTextStyle: TProgressTextStyle;
    FUnits: string;
    procedure SetCaptionInline(AValue: Boolean);
    procedure SetProgressDigits(const AValue: Word); virtual;
    procedure SetProgressTextAlign(AValue: SmallInt);
    procedure SetProgressTextStyle(const AValue: TProgressTextStyle);
    procedure SetUnits(AValue: string);
  protected const
    cDefGrooveWidth = 16;
    cDefProgMarkSize = 3;
    cDefProgressText = eptInverted;
  protected
    procedure CalcGrooveMiddle; override;
    procedure CalcInvalidRectDyn; override;
    procedure CalcInvalidRectStat; override;
    procedure CalcProgressInvRect; virtual;
    procedure CorrectGrooveLength(var z1, z2: Integer; AVertical: Boolean); override;
    procedure DrawGroove; override;
    function GetRelGroovePos: Integer; override;
    function HasCaption: Boolean; override;
    procedure OrientationChanged(AValue: TObjectOrientation); override;
    procedure PaintSelf(AEnabled: Boolean); override;
    procedure SetPosition(AValue: Double); override;
  public
    constructor Create(TheOwner: TComponent); override;
    destructor Destroy; override;
    property CaptionInline: Boolean read FCaptionInline write SetCaptionInline default False;
    property ProgressDigits: Word read FProgressDigits write SetProgressDigits default 0;
    property ProgressFontOptions: TFontOptions read FProgressFontOptions write FProgressFontOptions;
    property ProgressTextAlign: SmallInt read FProgressTextAlign write SetProgressTextAlign default 0;
    property ProgressTextStyle: TProgressTextStyle read FProgressTextStyle write SetProgressTextStyle default cDefProgressText;
    property Units: string read FUnits write SetUnits;
  end;

  { TECProgressBar }
  TECProgressBar = class(TCustomECProgressBar)
  published
    property Align;
    property Anchors;
    property AutoSize default True;
    property BevelInner;
    property BevelOuter;
    property BevelSpace;
    property BevelWidth;
    property BorderSpacing;
    property Caption;
    property CaptionInline;
    property CaptionPos;
    property Color;
    property Color3DLight;
    property Color3DDark;
    property Constraints;
    property DragCursor;
    property DragMode;
    property Enabled;
    property Font;
    property GrooveBevelWidth;
    property GrooveColor;
    property GrooveStyle;
    property GrooveTransparent;
    property GrooveWidth default cDefGrooveWidth;
    property ImageIndex;
    property ImagePos;
    property Images;
    property Indent;
    property Max;
    property Min;
    property Orientation default eooHorizontal;
    property ParentColor;
    property ParentFont;
    property ParentShowHint;
    property PopupMenu;
    property Position;
    property PositionToHint;
    property ProgressColor;
    property ProgressColor2;
    property ProgressDigits;
    property ProgressFontOptions;
    property ProgressFromMiddle;
    property ProgressMark;
		property ProgressMarkSize default cDefProgMarkSize;
    property ProgressMiddlePos;
    property ProgressParameter;
    property ProgressStyle;
		property ProgressTextAlign;
    property ProgressTextStyle;
    property Reversed;
    property Scale;
    property ScaleFontOptions;
    property ScaleTickPos;
    property ScaleValuePos;
    property ShowHint;
    property Style;
    property Units;
    property Visible;
    property Width;
    property OnChange;
    property OnChangeBounds;
    property OnDrawProgressBMP;
    property OnClick;
    property OnContextPopup;
    property OnDblClick;
    property OnDragDrop;
    property OnDragOver;
    property OnEndDrag;
    property OnMouseDown;
    property OnMouseEnter;
    property OnMouseLeave;
    property OnMouseMove;
    property OnMouseUp;
    property OnResize;
    property OnStartDock;
    property OnStartDrag;
  end;  

  { TCustomECPositionBar }
  TCustomECPositionBar = class(TCustomECProgressBar)
  private
    FCursorLock: Boolean;
    FMouseDragPx: Integer;
    FMouseDragPxFine: Integer;
    FProgressSign: Boolean;
    procedure SetProgressSign(AValue: Boolean);
  protected const
    cDefIndent = 2;
    cDefMouseDragPxFine = 10;
    cDefMouseDragPx = 1;     
  protected
    FCursorBkgnd: TCursor;
    FDragAreaEntered: Boolean;
    FDragState: Boolean;
    FPrevCTRLDown: Boolean;  { wheter CTRL was pressed in previous MouseMove }
    InitCoord: Integer;
    InitDelta: Double;     
    procedure CalcInvalidRectDyn; override;
    procedure CalcProgressInvRect; override;
    procedure ChangeCursors(AMouseHoverDragArea: Boolean);
    function DoMouseWheelDown(Shift: TShiftState; MousePos: TPoint): Boolean; override;
    function DoMouseWheelUp(Shift: TShiftState; MousePos: TPoint): Boolean; override;
    procedure DrawGroove; override;
    procedure MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Integer); override;
    procedure MouseMove(Shift: TShiftState; X, Y: Integer); override;
    procedure MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: Integer); override;
    procedure SetCursor(Value: TCursor); override;
  public
    constructor Create(TheOwner: TComponent); override;    
    property MouseDragPixels: Integer read FMouseDragPx write FMouseDragPx default cDefMouseDragPx;
    property MouseDragPixelsFine: Integer read FMouseDragPxFine write FMouseDragPxFine default cDefMouseDragPxFine;
    property ProgressSign: Boolean read FProgressSign write SetProgressSign default False;
  end; 

  { TECPositionBar }
  TECPositionBar = class(TCustomECPositionBar)
  published
    property Align;
    property Anchors;
    property AutoSize default True;
    property BevelInner;
    property BevelOuter;
    property BevelSpace;
    property BevelWidth;
    property BiDiMode;
    property BorderSpacing;
    property Caption;
    property CaptionInline;
    property CaptionPos;
    property Color;
    property Color3DLight;
    property Color3DDark;
    property Constraints;
    property DragCursor;
    property DragMode;
    property Enabled;
    property Font;
    property GrooveBevelWidth;
    property GrooveColor;
    property GrooveStyle;
    property GrooveTransparent;
    property GrooveWidth default cDefGrooveWidth;
    property ImageIndex;
    property ImagePos;
    property Images;
    property Indent default cDefIndent;
    property Max;
    property Min;
    property MouseDragPixels;
    property MouseDragPixelsFine;
    property Orientation default eooHorizontal;
    property ParentBiDiMode;
    property ParentColor;
    property ParentFont;
    property ParentShowHint;
    property PopupMenu;
    property Position;
    property PositionToHint;
    property ProgressColor;
    property ProgressColor2;
    property ProgressDigits;
    property ProgressFontOptions;
    property ProgressFromMiddle;
    property ProgressMark;
    property ProgressMarkSize default cDefProgMarkSize;
    property ProgressMiddlePos;
    property ProgressParameter;
    property ProgressSign;
    property ProgressStyle;
    property ProgressTextAlign;
    property ProgressTextStyle;
    property ProgressVisible;
    property Reversed;
    property Scale;
    property ScaleFontOptions;
    property ScaleTickPos;
    property ScaleValuePos;
    property ShowHint;
    property Style;
    property Units;
    property Visible;
    property Width;
    property OnChange;
    property OnChangeBounds;
    property OnClick;
    property OnContextPopup;
    property OnDblClick;
    property OnDragDrop;
    property OnDragOver;
    property OnDrawProgressBMP;
    property OnEndDrag;
    property OnMouseDown;
    property OnMouseEnter;
    property OnMouseLeave;
    property OnMouseMove;
    property OnMouseUp;
    property OnResize;
    property OnStartDock;
    property OnStartDrag;           
  end;

procedure Register;

implementation

{ TCustomECProgressBar }

constructor TCustomECProgressBar.Create(TheOwner: TComponent);
begin
  inherited Create(TheOwner);
  ControlStyle:=ControlStyle+[csNoFocus];
  FGrooveWidth:=cDefGrooveWidth;
  FOrientation:=eooHorizontal;
  FProgressFontOptions:=TFontOptions.Create(self);
  with FProgressFontOptions do
    begin
      FontStyles:=[fsBold];
      OnRecalcRedraw:=@RecalcRedraw;
      OnRedraw:=@Redraw;
    end;
  FProgressMarkSize:=cDefProgMarkSize;
  FProgressTextStyle:=cDefProgressText;
  SetInitialBounds(0, 0, 240, 80);
end;

destructor TCustomECProgressBar.Destroy;
begin
  FreeAndNil(FProgressFontOptions);
  inherited Destroy;
end;

procedure TCustomECProgressBar.CalcGrooveMiddle;
begin
  if ProgressMiddlePos<Min 
    then FGrooveMiddle:=0
    else if ProgressMiddlePos>Max 
           then FGrooveMiddle:=FGrooveInnerLength-1
           else FGrooveMiddle:=trunc(((ProgressMiddlePos-Min)/(Max-Min))*FGrooveInnerLength);
end;

procedure TCustomECProgressBar.CalcInvalidRectDyn;
var aRect: TRect;
    aCurrentPosition: Integer;
begin
  {$IFDEF DBGPROGBAR} DebugLn('TCustomECProgressBar.CalcInvalidRectDyn'); {$ENDIF}
  if Orientation=eooHorizontal
    then FInvalidRect.Bottom:=FInvRectLimit       
    else FInvalidRect.Right:=FInvRectLimit;
  aRect:=FInvalidRect;
  aCurrentPosition:=round(((Position-Min)/(Max-Min))*FGrooveInnerLength);
  if not RealReversed 
    then aCurrentPosition:=FGrooveMin+aCurrentPosition
    else aCurrentPosition:=FGrooveMax-aCurrentPosition-1;
  if Orientation=eooHorizontal then
    begin
      if aRect.Left<aCurrentPosition then
        begin  { Moves Right }
          if aRect.Right<aCurrentPosition then FInvalidRect.Right:=aCurrentPosition
        end else  { Moves Left }
        FInvalidRect.Left:=aCurrentPosition;
    end else     
    begin
      if aRect.Top<aCurrentPosition then
        begin  { Moves Down }
          if aRect.Bottom<aCurrentPosition then FInvalidRect.Bottom:=aCurrentPosition;
        end else  { Moves Up }
        FInvalidRect.Top:=aCurrentPosition;
    end;    
  if not FPrevInvRectPainted then UnionRect(FInvalidRect, aRect, FInvalidRect);
  inc(FInvalidRect.Right);
  inc(FInvalidRect.Bottom);
end;

procedure TCustomECProgressBar.CalcInvalidRectStat;
begin
  {$IFDEF DBGPROGBAR} DebugLn('CalcInvalidRectStat'); {$ENDIF}
  if FOrientation=eooHorizontal then
    begin
      FInvalidRect.Top:=FGrooveRect.Top+FGrooveBevelWidth;
      FInvalidRect.Bottom:=FGrooveRect.Bottom-FGrooveBevelWidth;
      FInvRectLimit:=FInvalidRect.Bottom;
    end else    
    begin
      FInvalidRect.Left:=FGrooveRect.Left+FGrooveBevelWidth;
      FInvalidRect.Right:=FGrooveRect.Right-FGrooveBevelWidth;
      FInvRectLimit:=FInvalidRect.Right;
    end;    
end; 

procedure TCustomECProgressBar.CalcProgressInvRect;
var aHelp: Integer;
    
  procedure CalcProgressInvRectLimit;
  begin
    if not RealReversed           
      then aHelp:=FGrooveMin+round(GetRelPxPos)
      else aHelp:=FGrooveMax-round(GetRelPxPos)-1;        
  end;    
    
begin
  if Orientation=eooHorizontal then 
    begin
      if (ProgressTextStyle=eptNone) and (ProgressVisible=epvProgress) then
        begin
          CalcProgressInvRectLimit;
          FInvalidRect.Left:=aHelp;
          FInvalidRect.Right:=aHelp;
        end else 
        begin
          FInvalidRect.Left:=FGrooveMin;
          FInvalidRect.Right:=FGrooveMax;
        end;
    end else
    begin
      if (ProgressTextStyle=eptNone) and (ProgressVisible=epvProgress) then  
        begin
          CalcProgressInvRectLimit;
          FInvalidRect.Top:=aHelp;
          FInvalidRect.Bottom:=aHelp;
        end else  
        begin
          FInvalidRect.Top:=FGrooveMin;
          FInvalidRect.Bottom:=FGrooveMax;
        end;
    end;                        
end;    

procedure TCustomECProgressBar.CorrectGrooveLength(var z1, z2: Integer; AVertical: Boolean);
var aHalfSize: SmallInt;
begin  
  if Scale.ValueVisible<>evvNone then
    begin
      if AVertical 
        then aHalfSize:=Background.Canvas.TextHeight('0,9') div 2
        else aHalfSize:=(Math.max(Background.Canvas.TextWidth(Scale.GetStringMin),
                                 Background.Canvas.TextWidth(Scale.GetStringMax))-1) div 2;
      
      dec(aHalfSize, GrooveBevelWidth);  
      inc(z1, aHalfSize);
      dec(z2, aHalfSize);  
    end;
end;         

procedure TCustomECProgressBar.DrawGroove;  { must be called from within Paint or PaintSelf! }
var aColor: TColor;
    bHorizontal: Boolean;
    aMiddlePos, aMin, aMax, aProgressPos, aStart, aStop, aTextX, aTextY: Integer;
    aRect, aFullGrooveRect: TRect;
    aSize: TSize;
    aStr: string;
begin
  inherited DrawGroove;
  if ProgressTextStyle>eptNone then
    with Canvas do
      begin
        Font.Size:=ProgressFontOptions.FontSize;
        Font.Style:=ProgressFontOptions.FontStyles;
        bHorizontal:= (Orientation=eooHorizontal);
        if bHorizontal 
          then Font.Orientation:=0
          else Font.Orientation:=900;
        Brush.Style:=bsClear;
        aStr:=Units;
        DeleteAmpersands(aStr);
        if aStr<>'' then aStr:=' '+aStr;
        aStr:=Scale.GetStringPosition(Position, ProgressDigits)+aStr;
        if CaptionInline and (Caption<>'') then aStr:=Caption+' '+aStr;
        aSize:=TextExtent(aStr);
        aFullGrooveRect:=FGrooveRect;
        InflateRect(aFullGrooveRect, -GrooveBevelWidth, -GrooveBevelWidth);
        aRect:=aFullGrooveRect;
        if bHorizontal then
          begin
            if ProgressTextAlign=0 
              then aRect.Left:=(aRect.Right+aRect.Left-aSize.cx) div 2  { Align to H-Center }
              else if ProgressTextAlign>0                                  
                     then aRect.Left:=aRect.Right-ProgressTextAlign-aSize.cx  { Align to Right }
                     else dec(aRect.Left, ProgressTextAlign);  { Align to Left }                    
            aRect.Top:=(aRect.Bottom+aRect.Top-aSize.cy) div 2;
            aRect.Right:=aRect.Left+aSize.cx;
            aRect.Bottom:=aRect.Top+aSize.cy; 
            aTextX:=aRect.Left;
            aTextY:=aRect.Top;
          end else
          begin
            aRect.Left:=(aRect.Right+aRect.Left-aSize.cy) div 2;
            if ProgressTextAlign=0           
              then aRect.Top:=(aRect.Bottom+aRect.Top-aSize.cx) div 2  { Align to V-Center }
              else if ProgressTextAlign>0
                     then inc(aRect.Top, ProgressTextAlign)  { Align to Top }
                     else aRect.Top:=aRect.Bottom+ProgressTextAlign-aSize.cx;  { Align to Bottom }
            aRect.Right:=aRect.Left+aSize.cy;
            aRect.Bottom:=aRect.Top+aSize.cx+2;    
            aTextX:=aRect.Left;
            aTextY:=aRect.Bottom-1;  { necessary for Font.Ori = 900 }
          end;
        IntersectRect(aRect, aRect, aFullGrooveRect);
        case ProgressTextStyle of
          eptSolid:
            begin
              aColor:=ProgressFontOptions.FontColor;
              if aColor=clDefault then
                begin
                  aColor:=GetColorResolvingDefault(GrooveColor, clBtnText);
                  if not GrooveTransparent then aColor:=InvertColor(ColorToRGB(aColor));
                end; 
              if not IsEnabled then aColor:=GetMonochromaticColor(aColor);
              Font.Color:=aColor; 
              TextOut(aTextX, aTextY, aStr);
            end;
          eptInverted: 
            begin
              Clipping:=True;
              case ProgressVisible of
                epvNone:
                  begin
                    aColor:=GetColorResolvingDefault(ProgressColor, clHighlight);
                    if not IsEnabled then aColor:=GetMonochromaticColor(aColor);
                    Font.Color:=aColor;
                    TextOut(aTextX, aTextY, aStr);
                  end;
                epvProgress:
                  begin
                    if GrooveTransparent
                      then aColor:=clBtnFace
                      else aColor:=GetColorResolvingDefault(GrooveColor, cl3DDkShadow);
                    if not IsEnabled then aColor:=GetMonochromaticColor(aColor);
                    if not RealReversed then 
                      begin
                        aProgressPos:=FGrooveMin;
                        aMiddlePos:=aProgressPos+FGrooveMiddle;
                        inc(aProgressPos, GetRelGroovePos);
                      end else
                      begin
                        aProgressPos:=FGrooveMax-GetRelGroovePos;
                        aMiddlePos:=FGrooveMax-FGrooveMiddle;
                      end;
                    if not ProgressFromMiddle then
                      begin  { Normal Progress }
                        if bHorizontal
                          then aStart:=aRect.Left
                          else aStart:=aRect.Top; 
                        if aStart<aProgressPos then
                          begin
                            if not RealReversed 
                              then Font.Color:=aColor
                              else Font.Color:=GetColorResolvingDefAndEnabled(ProgressColor, clHighlight, IsEnabled); 
                            if bHorizontal
                              then ClipRect:=Rect(aRect.Left, aRect.Top, aProgressPos, aRect.Bottom)
                              else ClipRect:=Rect(aRect.Left, aRect.Top, aRect.Right, aProgressPos);
                            TextOut(aTextX, aTextY, aStr);
                          end;
                        if bHorizontal
                          then aStart:=aRect.Right
                          else aStart:=aRect.Bottom;  
                        if aProgressPos<aStart then
                          begin
                            if not RealReversed
                              then Font.Color:=GetColorResolvingDefAndEnabled(ProgressColor, clHighlight, IsEnabled)
                              else Font.Color:=aColor;
                            if bHorizontal
                              then ClipRect:=Rect(aProgressPos, aRect.Top, aRect.Right, aRect.Bottom)
                              else ClipRect:=Rect(aRect.Left, aProgressPos, aRect.Right, aRect.Bottom);
                           TextOut(aTextX, aTextY, aStr);
                          end;
                      end else
                      begin  { Progress from Middle }
                        if bHorizontal then 
                          begin
                            aStart:=aRect.Left;
                            aStop:=aRect.Right;
                          end else 
                          begin
                            aStart:=aRect.Top;
                            aStop:=aRect.Bottom;
                          end;
                        aMin:=Math.min(aProgressPos, aMiddlePos);
                        if aStart<aMin then 
                          begin
                            Font.Color:=GetColorResolvingDefAndEnabled(ProgressColor, clHighlight, IsEnabled);
                            if bHorizontal
                              then ClipRect:=Rect(aRect.Left, aRect.Top, aMin, aRect.Bottom)
                              else ClipRect:=Rect(aRect.Left, aRect.Top, aRect.Right, aMin);
                            TextOut(aTextX, aTextY, aStr);
                          end;
                        aMax:=Math.max(aProgressPos, aMiddlePos);
                        if (aStart<aMax) and (aStop>aMin) and (aProgressPos<>aMiddlePos) then
                          begin  
                            Font.Color:=aColor;
                            if bHorizontal
                              then ClipRect:=Rect(Math.max(aStart, aMin), aRect.Top, Math.min(aStop, aMax), aRect.Bottom)
                              else ClipRect:=Rect(aRect.Left, Math.max(aStart, aMin), aRect.Right, Math.min(aStop, aMax));
                            TextOut(aTextX, aTextY, aStr);
                          end;
                        if aStop>aMax then
                          begin
                            Font.Color:=GetColorResolvingDefAndEnabled(ProgressColor, clHighlight, IsEnabled);
                            if bHorizontal
                              then ClipRect:=Rect(aMax, aRect.Top, aRect.Right, aRect.Bottom)
                              else ClipRect:=Rect(aRect.Left, aMax, aRect.Right, aRect.Bottom);
                            TextOut(aTextX, aTextY, aStr);
                          end;
                      end;
                  end;
                epvFull:
                  begin
                    if GrooveTransparent
                      then aColor:=clBtnFace
                      else aColor:=GetColorResolvingDefault(GrooveColor, cl3DDkShadow);
                    if not IsEnabled then aColor:=GetMonochromaticColor(aColor);
                    Font.Color:=aColor;
                    TextOut(aTextX, aTextY, aStr);
                  end;
              end;
              Clipping:=False;
            end;
        end;
      end;
end;

function TCustomECProgressBar.GetRelGroovePos: Integer;
begin
  Result:=round(((Position-Min)/(Max-Min))*FGrooveInnerLength);
end;

function TCustomECProgressBar.HasCaption: Boolean;
begin
  Result:= ((Caption<>'') and not CaptionInline);
end;  

procedure TCustomECProgressBar.OrientationChanged(AValue: TObjectOrientation);
begin
  if not (csLoading in ComponentState) then SetBounds(Left, Top, Height, Width);
  inherited OrientationChanged(AValue);
end;

procedure TCustomECProgressBar.PaintSelf(AEnabled: Boolean);
begin
  if WasEnabled<>AEnabled then
    if RedrawMode<ermRedrawBkgnd then RedrawMode:=ermRedrawBkgnd;
  if RedrawMode=ermRecalcRedraw then Calculate;
  if RedrawMode>=ermRedrawBkgnd then
    begin
      DrawBackground;
      DrawGrooveBMP;
    end;
  if RedrawMode>=ermFreeRedraw then
    begin
      Canvas.Draw(0, 0, Background);
      DrawGroove;
    end;
  if RedrawMode=ermMoveKnob then
    begin
      Canvas.CopyRect(FInvalidRect, Background.Canvas, FInvalidRect);
      DrawGroove;
    end;
  FPrevInvRectPainted:=True;
  CalcProgressInvRect;
end;

procedure TCustomECProgressBar.SetPosition(AValue: Double);
begin
  if [csLoading, csDestroying]*ComponentState=[] then
    if AValue<Min 
      then AValue:=Min
      else if AValue>Max then AValue:=Max;
  if FPosition=AValue then exit;
  FPosition:=AValue;
  if PositionToHint then Hint:=Scale.GetStringPosition(Position, ProgressDigits);
  InvalidateCustomRect(True);
  if assigned(FOnChange) then FOnChange(self);
end;

{ Setters }

procedure TCustomECProgressBar.SetCaptionInline(AValue: Boolean);
begin
  if FCaptionInline=AValue then exit;
  FCaptionInline:=AValue;
  RecalcRedraw;
end;      

procedure TCustomECProgressBar.SetProgressDigits(const AValue: Word);
begin
  if FProgressDigits=AValue then exit;
  FProgressDigits:=AValue;
  if ProgressTextStyle>eptNone then InvalidateNonUpdated;
end;       

procedure TCustomECProgressBar.SetProgressTextAlign(AValue: SmallInt);
begin
  if FProgressTextAlign=AValue then exit;
  FProgressTextAlign:=AValue;
  if ProgressTextStyle<>eptNone then InvalidateNonUpdated;
end;           

procedure TCustomECProgressBar.SetProgressTextStyle(const AValue: TProgressTextStyle);
begin
  if FProgressTextStyle=AValue then exit;
  FProgressTextStyle:=AValue;
  InvalidateNonUpdated;
end;

procedure TCustomECProgressBar.SetUnits(AValue: string);
begin
  if FUnits=AValue then exit;
  FUnits:=AValue;
  InvalidateNonUpdated;
end;      

{ TCustomECPositionBar }

constructor TCustomECPositionBar.Create(TheOwner: TComponent);
begin
  inherited Create(TheOwner);
  FCursorBkgnd:=Cursor;
  FMouseDragPxFine:=cDefMouseDragPxFine;
  FMouseDragPx:=cDefMouseDragPx;
  Indent:=cDefIndent;
end;

procedure TCustomECPositionBar.CalcInvalidRectDyn;
var aRect: TRect;
    aCurrentPosition: Integer;
begin
  {$IFDEF DBGPROGBAR} DebugLn('TCustomECPositionBar.CalcInvalidRectDyn'); {$ENDIF}
  if Orientation=eooHorizontal
    then FInvalidRect.Bottom:=FInvRectLimit       
    else FInvalidRect.Right:=FInvRectLimit;
  aRect:=FInvalidRect;
  aCurrentPosition:=round(((Position-Min)/(Max-Min))*FGrooveInnerLength);
  if not RealReversed 
    then aCurrentPosition:=FGrooveMin+aCurrentPosition
    else aCurrentPosition:=FGrooveMax-aCurrentPosition-1;
  if (ProgressTextStyle=eptNone) and (ProgressSign or (ProgressVisible=epvProgress)) then
    if Orientation=eooHorizontal then
      begin  { Horizontal }
        {$IFDEF DBGPROGBAR} WriteLn('aR.L ', aRect.Left, ' aR.R ', aRect.Right, ' aCP ', aCurrentPosition, ' aR.C ', (aRect.Left+aRect.Right) div 2); {$ENDIF}
        if ((aRect.Left+aRect.Right) div 2)<aCurrentPosition then
          begin  { Moves Right }
            {$IFDEF DBGPROGBAR} DebugLn('MoveRight'); {$ENDIF}
            FInvalidRect.Right:=aCurrentPosition+ProgressMarkSize;
          end else  { Moves Left }
          begin
            {$IFDEF DBGPROGBAR} DebugLn('MoveLeft'); {$ENDIF}
            FInvalidRect.Left:=aCurrentPosition-ProgressMarkSize;
          end;
      end else     
      begin  { Vertical }
        if ((aRect.Top+aRect.Bottom) div 2)<aCurrentPosition 
          then FInvalidRect.Bottom:=aCurrentPosition+ProgressMarkSize  { Moves Down }  
          else FInvalidRect.Top:=aCurrentPosition-ProgressMarkSize;  { Moves Up }
      end;    
  if not FPrevInvRectPainted then UnionRect(FInvalidRect, aRect, FInvalidRect);
  inc(FInvalidRect.Right); 
  inc(FInvalidRect.Bottom);
end;                   
              
procedure TCustomECPositionBar.CalcProgressInvRect;
var aMin, aMax, i: Integer;
    
  procedure CalcProgressInvRectLimit;
  begin
    if not RealReversed then 
        begin
          aMin:=FGrooveMin+round(GetRelPxPos);
          aMax:=aMin;
        end else 
        begin
          aMin:=FGrooveMax-round(GetRelPxPos)-1;
          aMax:=aMin;
        end;
    if ProgressSign then
      begin
        i:=ProgressMarkSize;
        dec(aMin, i);
        inc(i);
        inc(aMax, i);
      end;
  end;    
    
begin
  if Orientation=eooHorizontal then 
    begin
      if (ProgressTextStyle=eptNone) and (ProgressSign or (ProgressVisible=epvProgress)) then
        begin
          CalcProgressInvRectLimit;
          FInvalidRect.Left:=aMin;
          FInvalidRect.Right:=aMax;
        end else 
        begin
          FInvalidRect.Left:=FGrooveMin;
          FInvalidRect.Right:=FGrooveMax;
        end;
    end else
    begin
      if (ProgressTextStyle=eptNone) and (ProgressSign or (ProgressVisible=epvProgress)) then  
        begin
          CalcProgressInvRectLimit;
          FInvalidRect.Top:=aMin;
          FInvalidRect.Bottom:=aMax;
        end else  
        begin
          FInvalidRect.Top:=FGrooveMin;
          FInvalidRect.Bottom:=FGrooveMax;
        end;
    end;          
end;   

procedure TCustomECPositionBar.ChangeCursors(AMouseHoverDragArea: Boolean);
begin           
  FCursorLock:=True;
  if AMouseHoverDragArea 
    then if Orientation=eooHorizontal 
           then Cursor:=crSizeWE
           else Cursor:=crSizeNS
    else Cursor:=FCursorBkgnd;
  FCursorLock:=False;                    
end;  

function TCustomECPositionBar.DoMouseWheelDown(Shift: TShiftState; MousePos: TPoint): Boolean;
var d: Double;
begin
  Result:=inherited DoMouseWheelDown(Shift, MousePos);
  if not (ssCtrl in Shift)
    then d:=Mouse.WheelScrollLines*MouseDragPixels
    else d:=Mouse.WheelScrollLines*MouseDragPixels/MouseDragPixelsFine;
  if not RealReversed
    then Position:=Position+d
    else Position:=Position-d; 
end;

function TCustomECPositionBar.DoMouseWheelUp(Shift: TShiftState; MousePos: TPoint): Boolean;
var d: Double;
begin
  Result:=inherited DoMouseWheelUp(Shift, MousePos);
  if not (ssCtrl in Shift)
    then d:=Mouse.WheelScrollLines*MouseDragPixels
    else d:=Mouse.WheelScrollLines*MouseDragPixels/MouseDragPixelsFine;
  if not RealReversed
    then Position:=Position-d
    else Position:=Position+d;
end;      

procedure TCustomECPositionBar.DrawGroove;
var aColor, aInvColor: TColor;
    aPos, i, j: Integer;
    aRect: TRect;
begin
  inherited DrawGroove;
  if ProgressSign and (ProgressMarkSize>=1) then
    with Canvas do
      begin              
        aColor:=GetColorResolvingDefault(ProgressFontOptions.FontColor, clBtnText);
        aInvColor:=GetMergedColor(aColor, InvertColor(aColor), 0.25);
        Pen.Color:=aColor;
        Pen.Style:=psSolid;
        Pen.Width:=1;
        aPos:=GetRelGroovePos;
        aRect:=FGrooveRect;
        i:=GrooveBevelWidth;
        InflateRect(aRect, -i, -i);
        ClipRect:=aRect;
        Clipping:=True;
        if Orientation=eooHorizontal then
          begin  { Horizontal }
            if not RealReversed
              then inc(aPos, aRect.Left)
              else aPos:=aRect.Right-aPos;
            for i:=1 to ProgressMarkSize-1 do
              begin
                j:=aRect.Top+i+1;
                Line(aPos-i, j, aPos+i+1, j);
                Pixels[aPos-i-1, j]:=aInvColor;
                Pixels[aPos+i+1, j]:=aInvColor;
                j:=aRect.Bottom-i-2;
                Line(aPos-i, j, aPos+i+1, j);
                Pixels[aPos-i-1, j]:=aInvColor;
                Pixels[aPos+i+1, j]:=aInvColor; 
              end;
            Pen.Color:=aInvColor;
            i:=ProgressMarkSize;
            j:=aRect.Top+1;
            Pixels[aPos, j]:=aColor;
            Pixels[aPos-1, j]:=aInvColor;
            Pixels[aPos+1, j]:=aInvColor;
            inc(j, i);
            Line(aPos-i+1, j, aPos+i, j);
            j:=aRect.Bottom-2;
            Pixels[aPos, j]:=aColor;
            Pixels[aPos-1, j]:=aInvColor;
            Pixels[aPos+1, j]:=aInvColor;    
            dec(j, i);
            Line(aPos-i+1, j, aPos+i, j); 
          end else
          begin  { Vertical }
            if not Reversed
              then inc(aPos, aRect.Top)
              else aPos:=aRect.Bottom-aPos;
            for i:=1 to ProgressMarkSize-1 do
              begin
                j:=aRect.Left+i+1;
                Line(j, aPos-i, j, aPos+i+1);
                Pixels[j, aPos-i-1]:=aInvColor;
                Pixels[j, aPos+i+1]:=aInvColor;
                j:=aRect.Right-i-2;
                Line(j, aPos-i, j, aPos+i+1);
                Pixels[j, aPos-i-1]:=aInvColor;
                Pixels[j, aPos+i+1]:=aInvColor;
              end;
            Pen.Color:=aInvColor;
            j:=ProgressMarkSize; 
            i:=aRect.Left+1;
            Pixels[i, aPos]:=aColor;
            Pixels[i, aPos-1]:=aInvColor;
            Pixels[i, aPos+1]:=aInvColor;
            inc(i, j);
            Line(i, aPos-j+1, i, aPos+j);
            i:=aRect.Right-2;
            Pixels[i, aPos]:=aColor;
            Pixels[i, aPos-1]:=aInvColor;
            Pixels[i, aPos+1]:=aInvColor;
            dec(i, j);
            Line(i, aPos-j+1, i, aPos+j);
          end;
        Clipping:=False;
      end;
end;     

procedure TCustomECPositionBar.MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
var aMousePos: Double;

  procedure SetMousePos(ACoord: Integer);
  begin
    aMousePos:=GetPosFromCoord(ACoord);
    if not RealReversed
      then aMousePos:=Min+aMousePos
      else aMousePos:=Max-aMousePos;
  end;
                   
begin
  inherited MouseDown(Button, Shift, X, Y);
  case Button of 
    mbLeft:
      begin
        if Orientation=eooHorizontal
          then SetMousePos(X)
          else SetMousePos(Y);
        if FDragAreaEntered then
          begin  { Left click on Drag Area}
            if Orientation=eooHorizontal then 
              begin
                InitCoord:=X;
                InitDelta:=GetPosFromCoord(X)-Position
              end else 
              begin
                InitCoord:=Y;
                InitDelta:=GetPosFromCoord(Y)-Position;
              end;
            FDragState:=True;
          end else  { Middle or Double click }
          Position:=aMousePos;               
      end;
    mbMiddle: 
      if not ProgressFromMiddle
        then Position:=0.5*(Max-Min)
        else Position:=ProgressMiddlePos;
  end;
end;

procedure TCustomECPositionBar.MouseMove(Shift: TShiftState; X, Y: Integer);
const cTolerance = 5;
var aInit, aPosition: Integer;
    bCTRLDown: Boolean;
    bPrevDragAreaEntered: Boolean;
begin
  inherited MouseMove(Shift, X, Y);
  if IsEnabled then
    begin  
      aPosition:=GetRelGroovePos+GrooveBevelWidth;
      if not FDragState then
        begin
          if Orientation=eooHorizontal then
            begin  { Horizontal }
              if not RealReversed 
                then aPosition:=aPosition+FGrooveRect.Left
                else aPosition:=FGrooveRect.Right-aPosition;
              bPrevDragAreaEntered:=FDragAreaEntered;
              FDragAreaEntered:=(Y>=FGrooveRect.Top) and (Y<=FGrooveRect.Bottom)
                and (X>=(aPosition-cTolerance)) and (X<=(aPosition+cTolerance));
            end else
            begin  { Vertical }
              if not Reversed
                then aPosition:=aPosition+FGrooveRect.Top
                else aPosition:=FGrooveRect.Bottom-aPosition;
              bPrevDragAreaEntered:=FDragAreaEntered;
              FDragAreaEntered:= (X>=FGrooveRect.Left) and (X<=FGrooveRect.Right)
                and (Y>=(aPosition-cTolerance)) and (Y<=(aPosition+cTolerance));
            end;
          if FDragAreaEntered<>bPrevDragAreaEntered then ChangeCursors(FDragAreaEntered);
        end else
        begin
          bCTRLDown:= (ssCtrl in Shift);
          if bCTRLDown<>FPrevCTRLDown then
            begin
              if Orientation=eooHorizontal then 
                begin
                  InitCoord:=X;
                  InitDelta:=GetPosFromCoord(X)-Position
                end else
                begin
                  InitCoord:=Y;
                  InitDelta:=GetPosFromCoord(Y)-Position;
                end;
            end; 
          aInit:=InitCoord;
          if Orientation=eooHorizontal 
            then aPosition:=X-aInit
            else aPosition:=Y-aInit;
          if not bCTRLDown 
            then aPosition:=aPosition div MouseDragPixels
            else aPosition:=aPosition div MouseDragPixelsFine;
          if not RealReversed
            then Position:=GetPosFromCoord(aInit+aPosition)-InitDelta
            else Position:=GetPosFromCoord(aInit-aPosition)-InitDelta;
          FPrevCTRLDown:=bCTRLDown;
        end;   
    end;
end;

procedure TCustomECPositionBar.MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
  inherited MouseUp(Button, Shift, X, Y);
  if FDragState then
    begin
      FDragState:=False;
      if not FDragAreaEntered then ChangeCursors(False);
    end;        
end;                 

procedure TCustomECPositionBar.SetCursor(Value: TCursor);
begin
  inherited SetCursor(Value);
  if not FCursorLock then FCursorBkgnd:=Value;   
end;     

{ Setters }

procedure TCustomECPositionBar.SetProgressSign(AValue: Boolean);
begin
  if FProgressSign=AValue then exit;
  FProgressSign:=AValue;
  InvalidateNonUpdated;
end;          

procedure Register;
begin
  {$I ecprogressbar.lrs}
  RegisterComponents('EC-C', [TECProgressBar, TECPositionBar]);
end;

end.


