{%MainUnit ../extctrls.pp}

{******************************************************************************
                            TCustomFlowPanel
 ******************************************************************************
                             Ondrej Pokorny
 ******************************************************************************
  This file is part of the Lazarus Component Library (LCL)

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

  Delphi compatibility:

  - TFlowPanel is compatible with Delphi implementation
}

{ TFlowPanelControlList }

constructor TFlowPanelControlList.Create(AOwner: TPersistent);
begin
  Assert(AOwner is TCustomFlowPanel);
  inherited Create(AOwner, TFlowPanelControl);
end;

function TFlowPanelControlList.Add: TFlowPanelControl;
begin
  Result := TFlowPanelControl(inherited Add);
end;

procedure TFlowPanelControlList.AddControl(AControl: TControl; AIndex: Integer);
var
  I: Integer;
  Item: TFlowPanelControl;
begin
  if IndexOf(AControl) >= 0 then
    Exit;

  if AIndex = -1 then
    for I := 0 to Count-1 do
      if not Assigned(Items[I].Control) then
      begin
        AIndex := I;
        break;
      end;

  if AIndex = -1 then
    Item := Add
  else
    Item := Items[AIndex];

  Item.FControl := AControl;
end;

function TFlowPanelControlList.AllowAdd: Boolean;
begin
  Result := false;
end;

function TFlowPanelControlList.AllowDelete: Boolean;
begin
  Result := false;
end;

function TFlowPanelControlList.FPOwner: TCustomFlowPanel;
begin
  Result := TCustomFlowPanel(GetOwner);
end;

function TFlowPanelControlList.GetItem(Index: Integer): TFlowPanelControl;
begin
  Result := TFlowPanelControl(inherited GetItem(Index));
end;

function TFlowPanelControlList.IndexOf(AControl: TControl): Integer;
begin
  for Result := 0 to Count - 1 do
    if Items[Result].Control = AControl then
      Exit;
  Result := -1;
end;

procedure TFlowPanelControlList.RemoveControl(AControl: TControl);
var
  I: Integer;
begin
  for I := Count - 1 downto 0 do
    if Items[I].Control = AControl then
    begin
      Items[I].FControl := nil;
      Delete(I);
      Exit;
    end;
end;

procedure TFlowPanelControlList.SetItem(Index: Integer;
  const AItem: TFlowPanelControl);
begin
  inherited SetItem(Index, AItem);
end;

{ TFlowPanelControl }

procedure TFlowPanelControl.AssignTo(Dest: TPersistent);
var
  xDest: TFlowPanelControl;
begin
  if Dest is TFlowPanelControl then
  begin
    xDest := TFlowPanelControl(Dest);
    xDest.FWrapAfter := Self.FWrapAfter;
  end else
    inherited AssignTo(Dest);
end;

function TFlowPanelControl.AllowAdd: Boolean;
begin
  Result := false;
end;

function TFlowPanelControl.AllowDelete: Boolean;
begin
  Result := false;
end;

function TFlowPanelControl.FPCollection: TFlowPanelControlList;
begin
  Result := Collection as TFlowPanelControlList;
end;

function TFlowPanelControl.FPOwner: TCustomFlowPanel;
begin
  Result := FPCollection.FPOwner;
end;

function TFlowPanelControl.GetDisplayName: String;
begin
  if (FControl is TComponent) then
    Result := '[' + TComponent(FControl).Name + ']'
  else
    Result := inherited;
end;

procedure TFlowPanelControl.SetControl(const aControl: TControl);
begin
  if FControl = aControl then Exit;
  Assert(FControl = nil);
  FControl := aControl;
  if FControl <> nil then
    FControl.Parent := FPOwner;
end;

procedure TFlowPanelControl.SetIndex(Value: Integer);
begin
  inherited SetIndex(Value);

  if FPOwner.ComponentState * [csLoading, csUpdating,  csDestroying] = [] then
    FPOwner.ReAlign;
end;

procedure TFlowPanelControl.SetWrapAfter(const AWrapAfter: TWrapAfter);
begin
  if FWrapAfter = AWrapAfter then exit;
  FWrapAfter := AWrapAfter;

  if FPOwner.ComponentState * [csLoading, csUpdating,  csDestroying] = [] then
    FPOwner.ReAlign;
end;

{ TCustomFlowPanel }

constructor TCustomFlowPanel.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);

  FControlList := TFlowPanelControlList.Create(Self);
  FAutoWrap := True;
end;

procedure TCustomFlowPanel.AlignControls(AControl: TControl;
  var RemainingClientRect: TRect);

var
  xNewBounds: array of TRect; // right=width, bottom=height

  procedure AlignLayout(const BStartControl, BEndControl, BTopLeft, BSize: Integer);
  var
    I: Integer;
    xControl: TControl;
    xConBS: TControlBorderSpacing;
  begin
    if FFlowLayout = tlTop then
      Exit;

    for I := BStartControl to BEndControl do
    begin
      xControl := FControlList[I].Control;
      if xControl = nil then
        Continue;
      if not xControl.Visible and not (csDesigning in ComponentState) then
        Continue;

      xConBS := xControl.BorderSpacing;
      if FFlowStyle in [fsLeftRightTopBottom, fsLeftRightBottomTop, fsRightLeftTopBottom, fsRightLeftBottomTop] then
      begin
        case FFlowLayout of
          tlCenter: xNewBounds[I].Top := BTopLeft + (BSize - xConBS.ControlHeight) div 2 + xConBS.Top + xConBS.Around;
          tlBottom: xNewBounds[I].Top := BTopLeft + BSize - xControl.Height - xConBS.Bottom - xConBS.Around;
        end;
      end else
      begin
        case FFlowLayout of
          tlCenter: xNewBounds[I].Left := BTopLeft + (BSize - xConBS.ControlWidth) div 2 + xConBS.Left + xConBS.Around;
          tlBottom: xNewBounds[I].Left := BTopLeft + BSize - xControl.Width - xConBS.Right - xConBS.Around;
        end;
      end;
    end;
  end;
const
  cXIncDir:     array[TFlowStyle] of Integer = (1, -1,  1, -1,  1,  1, -1, -1);
  cYIncDir:     array[TFlowStyle] of Integer = (1,  1, -1, -1,  1, -1,  1, -1);
  cYDeltaConst: array[TFlowStyle] of Integer = (0,  0, -1, -1,  0,  0,  0,  0);
  cXDeltaConst: array[TFlowStyle] of Integer = (0,  0,  0,  0,  0,  0, -1, -1);
var
  I, L: Integer;
  xMaxHeight, xMaxWidth, xRowStart: Integer;
  xPosition: TPoint;
  xSize, xGroupSize: TSize;
  xControl: TControl;
  xConBS: TControlBorderSpacing;
  xForceWrap, xForbidWrap: Boolean;
begin
  if ControlCount = 0 then
    Exit;

  DisableAlign;
  try
    xMaxHeight := 0;
    xMaxWidth := 0;
    AdjustClientRect(RemainingClientRect);
    case FFlowStyle of
      fsLeftRightTopBottom,
      fsTopBottomLeftRight:
        xPosition := RemainingClientRect.TopLeft;
      fsRightLeftTopBottom,
      fsTopBottomRightLeft:
        xPosition := Point(RemainingClientRect.Right, RemainingClientRect.Top);
      fsLeftRightBottomTop,
      fsBottomTopLeftRight:
        xPosition := Point(RemainingClientRect.Left, RemainingClientRect.Bottom);
      fsRightLeftBottomTop,
      fsBottomTopRightLeft:
        xPosition := RemainingClientRect.BottomRight;
    end;

    xRowStart := 0;
    SetLength(xNewBounds, FControlList.Count);
    for I := 0 to FControlList.Count-1 do
    begin
      xControl := FControlList[I].Control;
      if xControl = nil then
        Continue;
      xConBS := xControl.BorderSpacing;
      if not xControl.Visible and not (csDesigning in ComponentState) then
        continue;

      xSize.cx := xConBS.ControlWidth;
      xSize.cy := xConBS.ControlHeight;
      xGroupSize := xSize;

      xForceWrap := (I > 0) and (FControlList[I-1].WrapAfter = waForce);
      xForbidWrap := (I > 0) and (FControlList[I-1].WrapAfter = waForbid);
      if not xForceWrap
      and ((I = 0) or not(FControlList[I-1].WrapAfter in [waAvoid, waForbid])) then
      begin
        for L := I to FControlList.Count-2 do
        begin
          if FControlList[L].WrapAfter in [waAvoid, waForbid] then
          begin
            case FFlowStyle of
              fsLeftRightTopBottom, fsLeftRightBottomTop, fsRightLeftTopBottom, fsRightLeftBottomTop:
                Inc(xGroupSize.cx, FControlList[L+1].Control.BorderSpacing.ControlWidth);
              fsTopBottomLeftRight, fsTopBottomRightLeft, fsBottomTopLeftRight, fsBottomTopRightLeft:
                Inc(xGroupSize.cy, FControlList[L+1].Control.BorderSpacing.ControlHeight);
            end;
          end else
            break;
        end;
      end;
      case FFlowStyle of
        fsLeftRightTopBottom,
        fsLeftRightBottomTop:
          if (xMaxHeight > 0) and FAutoWrap and not xForbidWrap
          and (xForceWrap or (xPosition.X + xGroupSize.cx >= RemainingClientRect.Right)) then
          begin
            AlignLayout(xRowStart, I-1, xPosition.Y, xMaxHeight);
            Inc(xPosition.Y, xMaxHeight * cYIncDir[FFlowStyle]);
            xMaxHeight := 0;
            xRowStart := I;
            xPosition.X := RemainingClientRect.Left;
          end;
        fsRightLeftTopBottom,
        fsRightLeftBottomTop:
          begin
            Dec(xPosition.X, xGroupSize.cx);
            if (xMaxHeight > 0) and FAutoWrap and not xForbidWrap
            and (xForceWrap or (xPosition.X <= 0)) then
            begin
              AlignLayout(xRowStart, I-1, xPosition.Y, xMaxHeight);
              Inc(xPosition.Y, xMaxHeight * cYIncDir[FFlowStyle]);
              xMaxHeight := 0;
              xRowStart := I;
              xPosition.X := RemainingClientRect.Right - xSize.cx;
            end;
          end;
        fsTopBottomLeftRight,
        fsTopBottomRightLeft:
          if (xMaxWidth > 0) and FAutoWrap and not xForbidWrap
          and (xForceWrap or (xPosition.Y + xGroupSize.cy >= RemainingClientRect.Bottom)) then
          begin
            AlignLayout(xRowStart, I-1, xPosition.X, xMaxWidth);
            Inc(xPosition.X, xMaxWidth * cXIncDir[FFlowStyle]);
            xMaxWidth := 0;
            xRowStart := I;
            xPosition.Y := RemainingClientRect.Top;
          end;
        fsBottomTopLeftRight,
        fsBottomTopRightLeft:
          begin
            Dec(xPosition.Y, xGroupSize.cy);
            if (xMaxWidth > 0) and FAutoWrap and not xForbidWrap
            and (xForceWrap or (xPosition.Y <= 0)) then
            begin
              AlignLayout(xRowStart, I-1, xPosition.X, xMaxWidth);
              Inc(xPosition.X, xMaxWidth * cXIncDir[FFlowStyle]);
              xMaxWidth := 0;
              xRowStart := I;
              xPosition.Y := RemainingClientRect.Bottom - xSize.cy;
            end;
          end;
      end;
      if xSize.cy > xMaxHeight then
        xMaxHeight := xSize.cy;
      if xSize.cx > xMaxWidth then
        xMaxWidth := xSize.cx;
      xNewBounds[I] := Rect(
        xPosition.X + xConBS.Left + xConBS.Around + cXDeltaConst[FFlowStyle] * xSize.cx,
        xPosition.Y + xConBS.Top + xConBS.Around + cYDeltaConst[FFlowStyle] * xSize.cy,
        xSize.cx - (xConBS.Left + xConBS.Right + xConBS.Around*2),
        xSize.cy - (xConBS.Top + xConBS.Bottom + xConBS.Around*2));
      if FFlowStyle in [fsLeftRightTopBottom, fsLeftRightBottomTop] then
        Inc(xPosition.X, xSize.cx * cXIncDir[FFlowStyle])
      else if FFlowStyle in [fsTopBottomLeftRight, fsTopBottomRightLeft] then
        Inc(xPosition.Y, xSize.cy + cYIncDir[FFlowStyle]);
    end;

    if FFlowStyle in [fsLeftRightTopBottom, fsLeftRightBottomTop, fsRightLeftTopBottom, fsRightLeftBottomTop] then
      AlignLayout(xRowStart, FControlList.Count-1, xPosition.Y, xMaxHeight)
    else
      AlignLayout(xRowStart, FControlList.Count-1, xPosition.X, xMaxWidth);

    for I := 0 to FControlList.Count-1 do
    begin
      xControl := FControlList[I].Control;
      if xControl = nil then
        continue;
      xConBS := xControl.BorderSpacing;
      if not xControl.Visible and not (csDesigning in ComponentState) then
        continue;

      xControl.SetBounds(
        xNewBounds[I].Left, xNewBounds[I].Top,
        xNewBounds[I].Right, xNewBounds[I].Bottom); // right=width, bottom=height
    end;
  finally
    EnableAlign;
  end;
end;

procedure TCustomFlowPanel.CalculatePreferredSize(var PreferredWidth,
  PreferredHeight: integer; WithThemeSpace: Boolean);
var
  xControl: TControl;
  xTestRect, xClientRect: TRect;
  I: Integer;
begin
  inherited CalculatePreferredSize(PreferredWidth, PreferredHeight,
    WithThemeSpace);

  if FControlList.Count > 0 then
  begin
    xTestRect := Rect(0, 0, 100, 100);
    xClientRect := xTestRect;
    AdjustClientRect(xClientRect);
    for I := 0 to ControlCount-1 do
    begin
      xControl := FControlList.Items[I].Control;
      if not xControl.Visible then
        Continue;
      if FFlowStyle in [fsLeftRightTopBottom, fsRightLeftTopBottom, fsLeftRightBottomTop, fsRightLeftBottomTop] then
      begin
        PreferredHeight := Max(PreferredHeight,
          xControl.BoundsRect.Bottom+xControl.BorderSpacing.Around+xControl.BorderSpacing.Bottom +
          xTestRect.Bottom-xClientRect.Bottom-xTestRect.Top+xClientRect.Top);
        PreferredWidth := Max(PreferredWidth,
          xControl.Width + xControl.BorderSpacing.AroundLeft + xControl.BorderSpacing.AroundRight
        );
      end else
      begin
        PreferredWidth := Max(PreferredWidth,
          xControl.BoundsRect.Right+xControl.BorderSpacing.Around+xControl.BorderSpacing.Right +
          xTestRect.Right-xClientRect.Right-xTestRect.Left+xClientRect.Left);
        PreferredHeight := Max(PreferredHeight,
          xControl.Height + xControl.BorderSpacing.AroundTop + xControl.BorderSpacing.AroundBottom
        );
      end;
    end;
  end;
end;

procedure TCustomFlowPanel.CMControlChange(var Message: TCMControlChange);
begin
  //inherited CMControlChange(Message); uncomment if CMControlChange should appear in parent classes

  if (csLoading in ComponentState) then
    Exit;

  if Message.Inserting and (Message.Control.Parent = Self) then
  begin
    DisableAlign;
    try
      Message.Control.Anchors := [];
      Message.Control.Align := alNone;
      FControlList.AddControl(Message.Control);
      ReAlign;
    finally
      EnableAlign;
    end;
  end else
    FControlList.RemoveControl(Message.Control);
end;

destructor TCustomFlowPanel.Destroy;
begin
  inherited Destroy;
  FControlList.Free;
end;

function TCustomFlowPanel.GetControlIndex(AControl: TControl): Integer;
begin
  Result := FControlList.IndexOf(AControl);
end;

procedure TCustomFlowPanel.SetAutoWrap(const AAutoWrap: Boolean);
begin
  if FAutoWrap = AAutoWrap then Exit;
  FAutoWrap := AAutoWrap;

  ReAlign;
end;

procedure TCustomFlowPanel.SetControlIndex(AControl: TControl; Index: Integer);
var
  CurIndex: Integer;
begin
  CurIndex := GetControlIndex(AControl);
  if (CurIndex > -1) and (CurIndex <> Index) and (Index < FControlList.Count) then
  begin
    FControlList.Items[CurIndex].Index := Index;
    Realign;
  end;
end;

procedure TCustomFlowPanel.SetControlList(
  const AControlList: TFlowPanelControlList);
begin
  FControlList.Assign(AControlList);
end;

procedure TCustomFlowPanel.SetFlowLayout(const aFlowLayout: TTextLayout);
begin
  if FFlowLayout = aFlowLayout then Exit;
  FFlowLayout := aFlowLayout;

  ReAlign;
end;

procedure TCustomFlowPanel.SetFlowStyle(const AFlowStyle: TFlowStyle);
begin
  if FFlowStyle = AFlowStyle then Exit;
  FFlowStyle := AFlowStyle;

  ReAlign;
end;


// included by extctrls.pp
