{ TDividerBevel

  Copyright (C) 2010 Lazarus team

  This library is free software; you can redistribute it and/or modify it
  under the same terms as the Lazarus Component Library (LCL)

  See the file COPYING.modifiedLGPL.txt, included in this distribution,
  for details about the license.

}
unit DividerBevel;

{$mode objfpc}{$H+}

interface

uses
  Classes, Types, Math,
  // LCL
  LCLType, LCLIntf, Controls, Graphics, ComCtrls, ExtCtrls, Themes,
  // LazUtils
  GraphType;

type
  { TDividerBevel }

  TDividerBevel = class(TGraphicControl)
  private
    FBevelStyle: TBevelStyle;
    FBevelWidth: Integer;
    FCaptionSpacing: Integer;
    FLeftIndent: Integer;
    FOrientation: TTrackBarOrientation;
    FStyle: TGrabStyle;
    FTransparent: Boolean;
    procedure SetBevelStyle(AValue: TBevelStyle);
    procedure SetBevelWidth(AValue: Integer);
    procedure SetCaptionSpacing(const AValue: Integer);
    procedure SetLeftIndent(const AValue: Integer);
    procedure SetOrientation(AValue: TTrackBarOrientation);
    procedure SetStyle(AValue: TGrabStyle);
    procedure SetTransparent(AValue: Boolean);
  protected
    FBevelHeight: Integer;
    FBevelTop: Integer;
    FNeedCalcSize: Boolean;
    FTextExtent: TSize;
    class function GetControlClassDefaultSize: TSize; override;
    procedure CalcSize;
    procedure Paint; override;
    procedure FontChanged(Sender: TObject); override;
    procedure BoundsChanged; override;
    procedure SetAutoSize(Value: Boolean); override;
    procedure TextChanged; override;
    procedure CalculatePreferredSize(
                         var PreferredWidth, PreferredHeight: Integer;
                         {%H-}WithThemeSpace: Boolean); override;
  public
    constructor Create(AOwner: TComponent); override;
    procedure ShouldAutoAdjust(var AWidth, AHeight: Boolean); override;
  published
    property Caption;
    property Align;
    property AutoSize default True;
    property Anchors;
    property BevelStyle: TBevelStyle read FBevelStyle write SetBevelStyle default bsLowered;
    property BevelWidth: Integer read FBevelWidth write SetBevelWidth default -1;
    property BiDiMode;
    property BorderSpacing;
    property CaptionSpacing: Integer read FCaptionSpacing write SetCaptionSpacing
             default 10;
    property Color;
    property Constraints;
    property DragCursor;
    property DragKind;
    property DragMode;
    property Font;
    property Hint;
    property LeftIndent: Integer read FLeftIndent write SetLeftIndent default 60;
    property Orientation: TTrackBarOrientation read FOrientation write SetOrientation
             default trHorizontal;
    property ParentBiDiMode;
    property ParentColor;
    property ParentFont;
    property ParentShowHint;
    property PopupMenu;
    property ShowHint;
    property Style: TGrabStyle read FStyle write SetStyle default gsSimple;
    property Transparent: Boolean read FTransparent write SetTransparent default True;
    property Visible;
    property OnChangeBounds;
    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 OnStartDrag;
end;

implementation

{ TDividerBevel }

procedure TDividerBevel.SetBevelStyle(AValue: TBevelStyle);
begin
  if FBevelStyle = AValue then Exit;
  FBevelStyle := AValue;
  Invalidate;
end;

procedure TDividerBevel.SetBevelWidth(AValue: Integer);
begin
  if FBevelWidth = AValue then Exit;
  FBevelWidth := AValue;
  if AutoSize then begin
    InvalidatePreferredSize;
    AdjustSize;
  end else
    FNeedCalcSize := True;
  Invalidate;
end;

procedure TDividerBevel.SetCaptionSpacing(const AValue: Integer);
begin
  if FCaptionSpacing = AValue then Exit;
  FCaptionSpacing := AValue;
  Invalidate;
end;

procedure TDividerBevel.SetLeftIndent(const AValue: Integer);
begin
  if FLeftIndent = AValue then Exit;
  FLeftIndent := AValue;
  Invalidate;
end;

procedure TDividerBevel.SetOrientation(AValue: TTrackBarOrientation);
begin
  if FOrientation = AValue then Exit;
  FOrientation := AValue;
  if not (csLoading in ComponentState) then SetBounds(Left, Top, Height, Width);
  if AutoSize then
    begin
      InvalidatePreferredSize;
      AdjustSize;
    end;
  Invalidate;
end;

procedure TDividerBevel.SetStyle(AValue: TGrabStyle);
begin
  if FStyle=AValue then Exit;
  FStyle:=AValue;
  Invalidate;
end;

procedure TDividerBevel.SetTransparent(AValue: Boolean);
begin
  if FTransparent = AValue then Exit;
  FTransparent := AValue;
  Invalidate;
end;

class function TDividerBevel.GetControlClassDefaultSize: TSize;
begin
  Result.CX := 240;
  Result.CY := 17;
end;

procedure TDividerBevel.CalcSize;
begin
  if not FNeedCalcSize then exit;
  FNeedCalcSize := False;

  if FBevelWidth < 0 then begin
    if Orientation = trHorizontal then
      Canvas.Font.Orientation := 0
    else
      Canvas.Font.Orientation := 900;
    if Caption = '' then
      FTextExtent := Canvas.TextExtent(' ')
    else
      FTextExtent := Canvas.TextExtent(Caption);
    FBevelHeight := Max(3, FTextExtent.cy div 5)
  end
  else
    FBevelHeight := FBevelWidth;

  if FOrientation = trHorizontal then
    FBevelTop := Max((1 + ClientHeight - FBevelHeight) div 2, 0)
  else
    FBevelTop := Max((1 + ClientWidth - FBevelHeight) div 2, 0);
end;

procedure TDividerBevel.Paint;
var
  aBevel: TGraphicsBevelCut;
  aHorizontal: Boolean;
  PaintRect: TRect;
  aStyle: TGrabStyle;

  procedure PaintBevel;
  var aDetails: TThemedElementDetails;
      aRect: TRect;
      w, l: Integer;
  begin
    case aStyle of
      gsSimple: Canvas.Frame3D(PaintRect, 1, aBevel);
      gsDouble: if aHorizontal then begin
          aRect.TopLeft := PaintRect.TopLeft;
          aRect.Right := PaintRect.Right;
          w := (PaintRect.Bottom - PaintRect.Top - 2) div 2;
          aRect.Bottom :=  aRect.Top + w;
          Canvas.Frame3D(aRect, 1, aBevel);
          aRect.Left := PaintRect.Left;
          aRect.Top := PaintRect.Bottom - w;
          aRect.BottomRight := PaintRect.BottomRight;
          Canvas.Frame3D(aRect, 1, aBevel);
        end else begin
          aRect.TopLeft := PaintRect.TopLeft;
          w := (PaintRect.Right - PaintRect.Left - 2) div 2;
          aRect.Right :=  aRect.Left + w;
          aRect.Bottom := PaintRect.Bottom;
          Canvas.Frame3D(aRect, 1, aBevel);
          aRect.Left := PaintRect.Right - w;
          aRect.Top := PaintRect.Top;
          aRect.BottomRight := PaintRect.BottomRight;
          Canvas.Frame3D(aRect, 1, aBevel);
        end;
      gsHorLines: begin
          aRect.TopLeft := PaintRect.TopLeft;
          aRect.Right :=  PaintRect.Right;
          l := Max((PaintRect.Bottom - aRect.Top + 1) div 3, 1);
          if l > 1 then
            inc(aRect.Top);
          Canvas.Pen.Color := clBtnShadow;
          for w := 0 to l - 1 do
            Canvas.Line(aRect.Left, aRect.Top + w * 3, aRect.Right, aRect.Top + w * 3);
          Canvas.Pen.Color := clBtnHighlight;
          inc(aRect.Top);
          for w := 0 to l - 1 do
            Canvas.Line(aRect.Left, aRect.Top + w * 3, aRect.Right, aRect.Top + w * 3);
        end;
      gsVerLines: begin
          aRect.TopLeft := PaintRect.TopLeft;
          aRect.Bottom :=  PaintRect.Bottom + 1;
          l := Max((PaintRect.Right - aRect.Left + 1) div 3, 1);
          if l > 1 then
            inc(aRect.Left);
          Canvas.Pen.Color := clBtnShadow;
          for w := 0 to l - 1 do
            Canvas.Line(aRect.Left + w * 3, aRect.Top, aRect.Left + w * 3, aRect.Bottom);
          Canvas.Pen.Color := clBtnHighlight;
          inc(aRect.Left);
          for w := 0 to l - 1 do
            Canvas.Line(aRect.Left + w * 3, aRect.Top, aRect.Left + w * 3, aRect.Bottom);
        end;
      gsGripper: begin
         if aHorizontal then
           aDetails := ThemeServices.GetElementDetails(trGripper)
         else
           aDetails := ThemeServices.GetElementDetails(trGripperVert);
         ThemeServices.DrawElement(Canvas.Handle, aDetails, PaintRect);
        end;
      gsButton: begin
          aDetails := ThemeServices.GetElementDetails(tbPushButtonNormal);
          ThemeServices.DrawElement(Canvas.Handle, aDetails, PaintRect);
        end;
    end;
  end;

var
  aIndent, aRight, j: Integer;
begin
  CalcSize;
  if not FTransparent then begin
    Canvas.Brush.Color := Color;
    Canvas.Brush.Style := bsSolid;
    Canvas.FillRect(ClientRect);
  end;

  if FBevelStyle = bsLowered then
    aBevel := bvLowered
  else
    aBevel := bvRaised;
  aHorizontal := (Orientation = trHorizontal);

  aStyle := Style;
  if not aHorizontal then
    case aStyle of
      gsHorLines: aStyle := gsVerLines;
      gsVerLines: aStyle := gsHorLines;
    end;

  if aHorizontal then begin
    PaintRect.Left := 0;
    PaintRect.Top := FBevelTop;
    PaintRect.Bottom := PaintRect.Top + FBevelHeight;
  end else begin
    PaintRect.Left := FBevelTop;
    PaintRect.Top := 0;
    PaintRect.Right := PaintRect.Left + FBevelHeight;
  end;

  if Caption = '' then begin
    if aHorizontal then
      PaintRect.Right := Width
    else
      PaintRect.Bottom := Height;
    PaintBevel;
    exit;
  end;

  if FLeftIndent > 0 then
    aIndent := FLeftIndent
  else
    if FLeftIndent = 0 then
      aIndent := 0
    else begin
      j := 2*FCaptionSpacing + FTextExtent.cx;
      if aHorizontal then
        aIndent := (Width - j) div 2
      else
        aIndent := (Height - j) div 2;
    end;

  if not IsRightToLeft or not aHorizontal then
    aRight := aIndent
  else begin
    aRight := Width - FTextExtent.cx - FCaptionSpacing - aIndent;
    if aIndent > 0 then dec(aRight, FCaptionSpacing);
  end;
  if aRight > 0 then begin
    if aHorizontal then
      PaintRect.Right := aRight
    else
      PaintRect.Bottom := aRight;
    PaintBevel;
  end;

  if aIndent > 0 then inc(aIndent, FCaptionSpacing);
  if aHorizontal then begin
    PaintRect.Left := aRight + FCaptionSpacing + FTextExtent.cx;
    if aIndent <> 0 then inc(PaintRect.Left, FCaptionSpacing);
    PaintRect.Top := FBevelTop;
    PaintRect.Right := Width;
    PaintRect.Bottom := FBevelTop + FBevelHeight;
  end else begin
    PaintRect.Left := FBevelTop;
    PaintRect.Top := aRight + FCaptionSpacing + FTextExtent.cx;
    if aIndent <> 0 then inc(PaintRect.Top, FCaptionSpacing);
    PaintRect.Right := FBevelTop + FBevelHeight;
    PaintRect.Bottom := Height;
  end;
  PaintBevel;

  Canvas.Brush.Style := bsClear;
  j := Max((FBevelHeight - FTextExtent.cy) div 2, 0);
  if aHorizontal then begin
    j := Max((ClientHeight - FTextExtent.cy) div 2, 0);
    Canvas.Font.Orientation := 0;
    if not IsRightToLeft then
      Canvas.TextOut(aIndent, j, Caption)
    else
      Canvas.TextOut(Width - FTextExtent.cx - aIndent, j, Caption);
  end else begin
    j := Max((ClientWidth - FTextExtent.cy) div 2, 0);
    Canvas.Font.Orientation := 900;
    Canvas.TextOut(j, aIndent + FTextExtent.cx, Caption);
  end;
end;

procedure TDividerBevel.FontChanged(Sender: TObject);
begin
  inherited FontChanged(Sender);
  FNeedCalcSize := True;
  Invalidate;
end;

procedure TDividerBevel.BoundsChanged;
begin
  inherited BoundsChanged;
  FNeedCalcSize := True;
end;

procedure TDividerBevel.SetAutoSize(Value: Boolean);
begin
  inherited SetAutoSize(Value);
  if Value then begin
    InvalidatePreferredSize;
    AdjustSize;
  end;
end;

procedure TDividerBevel.TextChanged;
begin
  inherited TextChanged;
  FNeedCalcSize := True;
  Invalidate;
end;

procedure TDividerBevel.CalculatePreferredSize(var PreferredWidth, PreferredHeight: Integer;
  WithThemeSpace: Boolean);
begin
  if Orientation = trHorizontal then
    Canvas.Font.Orientation := 0
  else
    Canvas.Font.Orientation := 900;
  if Caption = '' then
    FTextExtent := Canvas.TextExtent(' ')
  else
    FTextExtent := Canvas.TextExtent(Caption);

  if Orientation = trHorizontal then begin
    PreferredHeight := Max(FTextExtent.cy, FBevelHeight);
    PreferredWidth := 0;
  end else begin
    PreferredHeight := 0;
    PreferredWidth := Max(FTextExtent.cy, FBevelHeight);
  end;
end;

procedure TDividerBevel.ShouldAutoAdjust(var AWidth,
  AHeight: Boolean);
begin
  AWidth := not (AutoSize and (Orientation = trVertical));
  AHeight := not (AutoSize and (Orientation = trHorizontal));
end;

constructor TDividerBevel.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  FBevelStyle := bsLowered;
  FBevelWidth := -1;
  FCaptionSpacing := 10;
  FLeftIndent := 60;
  FOrientation := trHorizontal;
  FTransparent := True;
  FNeedCalcSize := True;
  if (AOwner = nil) or not (csLoading in AOwner.ComponentState) then
    Font.Style := Font.Style + [fsBold];
  with GetControlClassDefaultSize do
    SetInitialBounds(0, 0, CX, CY);
  AutoSize := True;
end;

end.

