unit dgui_place;

//
//  Implements computation of positions and resizing GUI objects.
//

{$MODE FPC}
{$MODESWITCH DEFAULTPARAMETERS}
{$MODESWITCH OUT}
{$MODESWITCH RESULT}

interface

uses
  dvector2i,
  dvector2f;

type
PGUITransform = ^TGUITransform;
TGUITransform = object
private
  FPos: TVec2i;
  FAngle: Single;
  FScale: TVec2i;
public
  // property Pos
  // property X
  // property Y
  // property Angle
  // property Scale
end;

PGUIPlace = ^TGUIPlace;
TGUIPlace = object
private
  FTransform: TGUITransform;
  FAbsTransform: TGUITransform;
  FDirtyFlag: Boolean;
  FSize: TVec2i;
  FProportionalPos: TVec2f;
      // Pos in relative coordinates.
  FProportionalSize: TVec2f;
      // Proportions of parent's size
  FMinSize: TVec2i;
      // If widget can be autosized, it tries to stay at least at Min Size.
  FPivot: TVec2f;
      //  Center of the element in relative coordinates.
      //  (0, 0) -- top left                              (1, 0) -- top right
      //
      //                          (0.5, 0.5) -- center
      //
      //  (0, 1) -- bottom left                           (1, 1) -- bottom right
  FAnchorX: PtrInt;
      //  0 -- save x position relative to parent's left
      //  1 -- save x position relative to parent's center
      //  2 -- save x position relative to parent's right
  FAnchorY: PtrInt;
      //  0 -- save y position relative to parent's top
      //  1 -- save y position relative to parent's center
      //  2 -- save y position relative to parent's bottom
  FAdjustMode: PtrInt;
      //  0 -- Fit: scale the node to maximum available space
      //  1 -- Zoom: scale the node to maximize one size
      //  2 -- Stretch: non-proportional scale
  FAutoSize: Boolean;
      // automatically resize Width and Height
  FSiblings: array[0 .. 3] of Pointer;
  FAnchorSide: array[0 .. 3] of PtrInt; // 0 -- None, 1-4 -- side
      // Anchoring means "keeping the distance". Each side of a node can be
      // anchored to the parent or the side of another sibling node.
  FSpacingAround: Single;
  FSpacingLeft: Single;
  FSpacingTop: Single;
  FSpacingRight: Single;
  FSpacingBotton: Single;
      // Spacing between elements after anchoring.
  function GetMinSize: TVec2i;
      // returns preffered size (e.g. for an image it may be width and height
      // of the image)
public
  procedure Init;
  procedure Done;

  procedure OnParentResized(const NewParentSize: TVec2i);
      // Computes relative transform

  function IsPointInAbs(const P: TVec2i): Boolean;

  procedure SetProportionalPos(const NewProportionalPos: TVec2f; const ParentSize: TVec2i);
  procedure SetPos(const NewPos: TVec2i; const ParentSize: TVec2i);

  procedure SetProportionalSize(const NewProportionalSize: TVec2f; const ParentSize: TVec2i);
  procedure SetSize(const NewSize: TVec2i; const ParentSize: TVec2i);

  procedure AlwaysFitToParent;
  procedure AlignTopLeft;

  property Pos: TVec2i read FTransform.FPos write FTransform.FPos;
  property AbsPos: TVec2i read FAbsTransform.FPos write FAbsTransform.FPos;
  property Size: TVec2i read FSize;
  property MinSize: TVec2i read FMinSize write FMinSize;
  property AutoSize: Boolean read FAutoSize write FAutoSize;

  // property Transform
  // property X
  // property Y
  // property Angle
  // property ScaleX
  // property ScaleY
  // property AbsTransform
  // property Size
  // property Pivot
end;

implementation

function TGUIPlace.GetMinSize: TVec2i;
begin
  Exit(FMinSize);
end;

procedure TGUIPlace.SetProportionalPos(const NewProportionalPos: TVec2f; const ParentSize: TVec2i);
begin
  FProportionalPos := NewProportionalPos;
  FTransform.FPos := Round2f(FProportionalPos * Vec2f(ParentSize));
  FAbsTransform.FPos := FTransform.FPos;
end;

procedure TGUIPlace.SetPos(const NewPos: TVec2i; const ParentSize: TVec2i);
begin
  FTransform.FPos := NewPos;
  FAbsTransform.FPos := NewPos;
  FProportionalPos := Vec2f(NewPos.X / ParentSize.X, NewPos.Y / ParentSize.Y);
end;

procedure TGUIPlace.SetProportionalSize(const NewProportionalSize: TVec2f; const ParentSize: TVec2i);
begin
  FProportionalSize := NewProportionalSize;
  FSize := Round2f(FProportionalSize * Vec2f(ParentSize));
end;

procedure TGUIPlace.SetSize(const NewSize: TVec2i; const ParentSize: TVec2i);
begin
  FSize := NewSize;
  FProportionalSize := Vec2f(NewSize.X / ParentSize.X, NewSize.Y / ParentSize.Y);
end;

procedure TGUIPlace.AlwaysFitToParent;
begin
  FAdjustMode := 0;
end;

procedure TGUIPlace.AlignTopLeft;
begin
  FAdjustMode := 2;
end;

procedure TGUIPlace.Init;
begin
  FMinSize := Vec2i(0, 0);
  FAdjustMode := 1;
  FAutoSize := True;
end;

procedure TGUIPlace.Done;
begin
end;

procedure TGUIPlace.OnParentResized(const NewParentSize: TVec2i);
begin
  if FAdjustMode = 1 then begin
    FTransform.FPos := Round2f(FProportionalPos * Vec2f(NewParentSize));
    FAbsTransform.FPos := Round2f(FProportionalPos * Vec2f(NewParentSize));
    if FAutoSize then begin
      FSize := Round2f(FProportionalSize * Vec2f(NewParentSize));
      if FSize.X < FMinSize.X then
        FSize.X := FMinSize.X;
      if FSize.Y < FMinSize.Y then
        FSize.Y := FMinSize.Y;
    end;
  end else if FAdjustMode = 0 then begin
    FTransform.FPos := Vec2i(0, 0);
    FAbsTransform.FPos := Vec2i(0, 0);
    FSize := NewParentSize;
  end;
end;

function TGUIPlace.IsPointInAbs(const P: TVec2i): Boolean;
begin
  Result := (FAbsTransform.FPos.X <= P.X) and
            (FAbsTransform.FPos.Y <= P.Y) and
            (P.X <= FAbsTransform.FPos.X + FSize.X) and
            (P.Y <= FAbsTransform.FPos.Y + FSize.Y);
end;

end.
