unit sparta_BasicResizeFrame;

{$mode delphi}{$H+}

interface

uses
  Classes, Types, contnrs, SysUtils, Math,
  FileUtil,
  LCLType, LCLIntf, LMessages, Forms, Controls, ExtCtrls, StdCtrls, Graphics, Menus,
  sparta_InterfacesMDI;

type
  TPositioningCode = (pcPositioning, pcPositioningEnd);
  TPositioningKind = set of (pkBottom, pkRight);
  TPositioningEvent = procedure(Sender: TObject; PositioningKind: TPositioningKind; PositioningCode: TPositioningCode) of object;

  { TBasicResizeFrame }

  TResizerFrameClass = class of TBasicResizeFrame;
  TBasicResizeFrame = class(TFrame, IResizeFrame)
    iResizerLineImg: TImage;
    pFormHandler: TPanel;
    pFakeMenu: TPanel;
    pBG: TPanel;
    pB: TPanel;
    pClient: TPanel;
    pL: TPanel;
    pMarginB: TPanel;
    pMarginL: TPanel;
    pMarginR: TPanel;
    pMarginT: TPanel;
    pR: TPanel;
    pT: TPanel;
    procedure pBGPaint(Sender: TObject);
    procedure pFakeMenuPaint(Sender: TObject);
    procedure sbVerticalScroll(Sender: TObject; ScrollCode: TScrollCode;
      var ScrollPos: Integer);
    procedure sbHorizontalScroll(Sender: TObject; ScrollCode: TScrollCode;
      var ScrollPos: Integer);
  public const
    SIZER_RECT_SIZE = 8;
    SIZER_LINE_WIDTH = 8;
  private
    FVerticalScrollPos: Integer;
    FHorizontalScrollPos: Integer;
    FDesignedForm: IDesignedForm;
    FBackground: IDesignedFormBackground;
    FFakeFocusControl: TWinControl;

    procedure FakeExitEnter(Sender: TObject);
    procedure FakeKeyDown(Sender: TObject; var Key: Word; {%H-}Shift: TShiftState);
    procedure FakeKeyUp(Sender: TObject; var Key: Word; {%H-}Shift: TShiftState);
    procedure FakeUTF8KeyPress(Sender: TObject; var UTF8Key: TUTF8Char);
  private
    FOnNodePositioning: TPositioningEvent;
    FOnHorizontalScroll, FOnVerticalScroll: TScrollEvent;
    FLastRightMarign: Integer;
    FLastBottomMarign: Integer;
    FNodePositioning: Boolean;
    FOldPos, FDelta: TPoint;
    FPositioningKind: TPositioningKind;
    FMaxWidth, FMaxHeight: Integer;
    FLastClientWidth, FLastClientHeight: Integer;
    FLastDesignedWidthToScroll, FLastDesignedHeightToScroll: Integer;
    FOldHasMainMenu: Boolean;
    FDesignerModified: Boolean;
    FSizerLineWidth: Integer;
    FSizerRectSize: Integer;

    function HasMainMenu: Boolean;
    procedure AppOnIdle(Sender: TObject; var {%H-}Done: Boolean);

    procedure PanelPaint(Sender: TObject);
    procedure BGChangeBounds(Sender: TObject);

    procedure CreateNodes;
    procedure NodeMouseDown(Sender: TObject; {%H-}Button: TMouseButton; {%H-}Shift: TShiftState; {%H-}X, {%H-}Y: Integer);
    procedure NodeMouseMove(Sender: TObject; {%H-}Shift: TShiftState; {%H-}X, {%H-}Y: Integer);
    procedure NodeMouseUp(Sender: TObject; {%H-}Button: TMouseButton; {%H-}Shift: TShiftState; {%H-}X, {%H-}Y: Integer);

    function GetRightMargin: Integer;
    function GetBottomMargin: Integer;

    // dependent on scroll position
    // for Vertical
    function BottomSizerRectHeight: Integer;
    function BottomSizerLineWidth: Integer;
    function TopSizerRectTop: Integer;
    function TopSizerLineWidth: Integer;
    function VerticalSizerLineLength: Integer;
    // for Horizontal
    function RightSizerRectWidth: Integer;
    function RightSizerLineWidth: Integer;
    function LeftSizerRectLeft: Integer;
    function LeftSizerLineWidth: Integer;
    function HorizontalSizerLineLength: Integer;

    procedure AdjustFormHandler;

    function GetMenuHeight: Integer;
  protected
    FNodes: TObjectList;
  protected
    procedure TryBoundDesignedForm; virtual;
    procedure BeginFormSizeUpdate(Sender: TObject); virtual;
    procedure EndFormSizeUpdate(Sender: TObject); virtual;
  protected { IResizeFrame }
    procedure HideSizeRects;
    procedure ShowSizeRects;
    procedure PositionNodes; overload;
    function DesignedWidthToScroll: Integer;
    function DesignedHeightToScroll: Integer;
    procedure ClientChangeBounds; overload;

    function GetFrame: TCustomFrame;
    function GetVerticalScrollPos: Integer;
    procedure SetVerticalScrollPos(AValue: Integer);
    function GetHorizontalScrollPos: Integer;
    procedure SetHorizontalScrollPos(AValue: Integer);

    function GetBackgroundPanel: TPanel;
    function GetBackgroundMargin(const AIndex: Integer): Integer;

    function GetNewSize: TPoint;
    function GetFormHandler: TPanel;
    function GetNodePositioning: Boolean;
    function GetDesignedForm: IDesignedForm;
    procedure SetDesignedForm(const AValue: IDesignedForm);

    function GetSizerRectSize: Integer;
    function GetSizerLineWidth: Integer;
  public { IResizeFrame }
    procedure DesignerSetFocus;
  public
    constructor Create(TheOwner: TComponent); override;
    destructor Destroy; override;

    property DesignedForm: IDesignedForm read GetDesignedForm write SetDesignedForm;

    procedure PositionNodes(AroundControl: TWinControl); overload;
    property NodePositioning: Boolean read GetNodePositioning;
    procedure ClientChangeBounds(Sender: TObject); overload;

    property RightMargin: Integer read GetRightMargin;
    property BottomMargin: Integer read GetBottomMargin;
    property OnNodePositioning: TPositioningEvent read FOnNodePositioning write FOnNodePositioning;

    property BgLeftMargin: Integer index 0 read GetBackgroundMargin;
    property BgTopMargin: Integer index 1 read GetBackgroundMargin;
    property BgRightMargin: Integer index 2 read GetBackgroundMargin;
    property BgBottomMargin: Integer index 3 read GetBackgroundMargin;
    property SizerRectSize: Integer read FSizerRectSize;
    property SizerLineWidth: Integer read FSizerLineWidth;

    procedure HideSizeControls;
    procedure ShowSizeControls;

    procedure OnModified;

    property VerticalScrollPos: Integer read GetVerticalScrollPos write SetVerticalScrollPos;
    property HorizontalScrollPos: Integer read GetHorizontalScrollPos write SetHorizontalScrollPos;
  end;

implementation

{$R *.lfm}

{ Node grip indices are as follows:

       1
0 +----+----+ 2
  |         |
7 +         + 3
  |         |
6 +----+----+ 4
       5

Only grips 3, 4, and 5 are sizeable }

procedure TBasicResizeFrame.pFakeMenuPaint(Sender: TObject);
var
  MenuRect: Types.TRect;
  Menu: TMainMenu;
  X, Y, I: Integer;
  LCanvas: TCanvas;
begin
  //fake paint menu

  MenuRect := pFakeMenu.ClientRect;
  LCanvas := pFakeMenu.Canvas;
  LCanvas.Brush.Color := clMenuBar;
  LCanvas.FillRect(MenuRect);

  // pFakeMenu is visible only when HasMainMenu is true
  // but FDesignedForm can be nil if the designer is painted before it has been assigned
  if not HasMainMenu then
    Exit;

  Menu := FDesignedForm.Form.Menu;
  LCanvas.Font.Color := clMenuText;

  X := 5;
  Y := (MenuRect.Top+MenuRect.Bottom-LCanvas.TextHeight('Hg')) div 2;
  for I := 0 to Menu.Items.Count-1 do
    if Menu.Items[I].Visible then
    begin
      LCanvas.TextOut(X, Y, Menu.Items[I].Caption);
      Inc(X, LCanvas.TextWidth(Menu.Items[I].Caption) + 10);
    end;
end;

procedure TBasicResizeFrame.pBGPaint(Sender: TObject);
begin
  pBG.SendToBack;
end;

procedure TBasicResizeFrame.sbVerticalScroll(Sender: TObject;
  ScrollCode: TScrollCode; var ScrollPos: Integer);
begin
  if ScrollCode <> scEndScroll then
    HideSizeRects
  else
    ShowSizeRects;

  FVerticalScrollPos := ScrollPos;

  PositionNodes(Self);

  if Assigned(FOnVerticalScroll)
    // for refresh from this class, pass sender as nil.
    // In other case program will go into infinity loop
    and (Sender <> nil) then
    FOnVerticalScroll(Sender, ScrollCode, ScrollPos);
end;

procedure TBasicResizeFrame.sbHorizontalScroll(Sender: TObject;
  ScrollCode: TScrollCode; var ScrollPos: Integer);
begin
  if ScrollCode <> scEndScroll then
    HideSizeRects
  else
    ShowSizeRects;

  FHorizontalScrollPos := ScrollPos;

  PositionNodes(Self);

  if Assigned(FOnHorizontalScroll)
    // for refresh from this class, pass sender as nil.
    // In other case program will go into infinity loop
    and (Sender <> nil) then
    FOnHorizontalScroll(Sender, ScrollCode, ScrollPos);
end;

{ TResizerFrame }

// Tiles the source image over the given target canvas
procedure TileImage(const ASource: TImage; ATarget: TCanvas; AX, AY,
  AWidth, AHeight: Integer);
var
  LX, LY, LDeltaX, LDeltaY: Integer;
begin
  LDeltaX := ASource.Width;
  LDeltaY := ASource.Height;
  LY := 0;
  while LY < AHeight do
  begin
    LX := 0;
    while LX < AWidth do
    begin
      ATarget.Draw(AX + LX, AY + LY, ASource.Picture.graphic);
      Inc(LX, LDeltaX);
    end;
    Inc(LY, LDeltaY);
  end;
end;

procedure TBasicResizeFrame.PanelPaint(Sender: TObject);
var
  LWidth, LHeight: Integer;
  LOldColor: TColor;
  LCanvas: TCanvas;
begin
  if FNodePositioning then
    Exit;
  if (Sender = pR) or (Sender = pL) then
  begin
    LWidth := SizerLineWidth;
    LHeight := Height;
  end else
  begin
    LWidth := Width;
    LHeight := SizerLineWidth;
  end;
  LCanvas := (Sender as TPanel).Canvas;
  if FFakeFocusControl.Focused then
  begin
    LOldColor := LCanvas.Brush.Color;
    LCanvas.Brush.Color := $FFEEDD;
    LCanvas.FillRect(0, 0, LWidth, LHeight);
    LCanvas.Brush.Color := LOldColor;
  end;
  TileImage(iResizerLineImg, LCanvas, 0, 0, LWidth, LHeight);
end;

procedure TBasicResizeFrame.ClientChangeBounds(Sender: TObject);
begin
  if (DesignedForm = nil) or FNodePositioning then
    Exit;

  FLastClientWidth  := pClient.Width;
  FLastClientHeight := pClient.Height;

(*
  DesignedForm.BeginUpdate;

  DesignedForm.RealLeft := 0;
  DesignedForm.RealTop := 0;
  DesignedForm.RealWidth  := pClient.Width;
  DesignedForm.RealHeight := pClient.Height;
  DesignedForm.EndUpdate;
*)
end;

procedure TBasicResizeFrame.BGChangeBounds(Sender: TObject);
begin
  PositionNodes(Self);
end;

procedure TBasicResizeFrame.HideSizeRects;
var
  p: TObject;
  wc: TWinControl absolute p;
begin
  for p in FNodes do
    if not (wc is TPanel) then
      wc.Visible := False;
end;

procedure TBasicResizeFrame.HideSizeControls;
begin
  pL.Repaint;
  pT.Repaint;
  pR.Repaint;
  pB.Repaint;

  HideSizeRects;
  pBG.SendToBack;
end;

procedure TBasicResizeFrame.ShowSizeRects;
var
  p: TObject;
  wc: TWinControl absolute p;
begin
  for p in FNodes do
    wc.Visible := True;
end;

procedure TBasicResizeFrame.PositionNodes;
begin
  PositionNodes(Self);
end;

procedure TBasicResizeFrame.ShowSizeControls;
begin
  pL.Repaint;
  pT.Repaint;
  pR.Repaint;
  pB.Repaint;

  ShowSizeRects;
  //pBG.Visible := True;
end;

procedure TBasicResizeFrame.CreateNodes;
var
  Node: Integer;
  Panel: TPanel;
begin
  for Node := 0 to 7 do
  begin
    Panel := TPanel.Create(self);
    with Panel do
    begin
      BevelOuter := bvNone;
      Color := clBlack;

      Name := 'Node' + IntToStr(Node);
      Caption:='';
      Width := SIZER_RECT_SIZE;  // scaled dynamically by LCL
      Height := SIZER_RECT_SIZE; // scaled dynamically by LCL
      Parent := Self;
      Visible := True;
      FNodes.Add(Panel);

      case Node of
        // on mac there is no cursor for crNWSE ( https://bugs.freepascal.org/view.php?id=32194#c101876 )
        {0,}4: Cursor := {$IFDEF MACOS}crSizeAll{$ELSE}crSizeNWSE{$ENDIF};
        {1,}5: Cursor := crSizeNS;
        //{2,}6: Cursor := $IFDEF MACOS}crSizeAll{$ELSE}crSizeNESW{$ENDIF};
        3{,7}: Cursor := crSizeWE;
      end;
      if Node in [3,4,5] then
      begin
        OnMouseDown := NodeMouseDown;
        OnMouseMove := NodeMouseMove;
        OnMouseUp := NodeMouseUp;
      end;

      with TShape.Create(Panel) do
      begin
        Parent := Panel;
        Align:= alClient;
        Enabled := False;

        if Node in [3,4,5] then
          Brush.Color:=clBtnFace
        else
          Brush.Color:=clGray;
      end;
    end;
  end;
  // extra resizers
  pB.OnMouseDown := NodeMouseDown;
  pB.OnMouseMove := NodeMouseMove;
  pB.OnMouseUp := NodeMouseUp;

  pR.OnMouseDown := NodeMouseDown;
  pR.OnMouseMove := NodeMouseMove;
  pR.OnMouseUp := NodeMouseUp;

  FNodes.Add(pL);
  FNodes.Add(pT);
  FNodes.Add(pR);
  FNodes.Add(pB);
end;

procedure TBasicResizeFrame.NodeMouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
var
  LCtrlPoint: TPoint;
begin
  { TShape in TBasicResizeFrame.CreateNodes is disabled, anyway in future can be usefull
  if Sender is TGraphicControl then
    Sender := TGraphicControl(Sender).Parent;}

  if (Enabled) and (Sender is TWinControl) then
  begin
    FNodePositioning:=True;
    BeginFormSizeUpdate(Sender);

    // when we start resizing the rules do not apply to us :)
    FMaxWidth := Constraints.MaxWidth;
    FMaxHeight := Constraints.MaxHeight;
    Constraints.MaxWidth := 0;
    Constraints.MaxHeight := 0;
    with pClient do
    begin
      Align := alClient;
      if pBG.Left + BgLeftMargin <= 0 then
        BorderSpacing.Left := Max(-pBG.Left - (FHorizontalScrollPos - SizerRectSize), 0)
      else
        BorderSpacing.Left := Max(pBG.Left + BgLeftMargin, 0);

      if pBG.Top + BgTopMargin <= 0 then
        BorderSpacing.Top := Max(-pBG.Top - (FVerticalScrollPos - SizerRectSize), 0)
      else
        BorderSpacing.Top := Max(pBG.Top + BgTopMargin, 0);

      BorderSpacing.Right := Max(Self.Width - (pR.Left - BgRightMargin), 0);
      BorderSpacing.Bottom := Max(Self.Height - (pB.Top - BgBottomMargin), 0);
    end;


    {$IF Defined(LCLWin32) or Defined(LCLWin64)}
    SetCapture(TWinControl(Sender).Handle);
    {$ENDIF}
    GetCursorPos(FOldPos);
    // perform first "click delta" to reduce leap
    // + calculate delta created by scrollbars and theirs position...
    FillChar(FDelta, SizeOf(FDelta), #0);
    LCtrlPoint := (Sender as TWinControl).ScreenToClient(Mouse.CursorPos);
    if Sender = pR then
    begin
      FDelta.X := -(LCtrlPoint.x - RightSizerLineWidth) + RightMargin + Left;
      FPositioningKind := [pkRight];
    end
    else if Sender = pB then
    begin
      FDelta.Y := -(LCtrlPoint.y - BottomSizerLineWidth) + BottomMargin + Top;
      FPositioningKind := [pkBottom];
    end
    else
      case FNodes.IndexOf(Sender) of
        3: // middle right
          begin
            FDelta.X := -(LCtrlPoint.x - RightSizerRectWidth) + RightMargin + Left;
            FPositioningKind := [pkRight];
          end;
        4: // right bottom
          begin
            FDelta.X := -(LCtrlPoint.x - RightSizerRectWidth) + RightMargin + Left;
            FDelta.Y := -(LCtrlPoint.y - BottomSizerRectHeight) + BottomMargin + Top;
            FPositioningKind := [pkRight, pkBottom];
          end;
        5: // middle bottom
          begin
            FDelta.Y := -(LCtrlPoint.y - BottomSizerRectHeight) + BottomMargin + Top;
            FPositioningKind := [pkBottom];
          end;
      end;
  end;
end;

procedure TBasicResizeFrame.NodeMouseMove(Sender: TObject; Shift: TShiftState; X,
  Y: Integer);
var
  newPos: TPoint;
  frmPoint : TPoint;
  OldRect: TRect;
  AdjL,AdjR,AdjT,AdjB: Boolean;
begin
  { TShape in TBasicResizeFrame.CreateNodes is disabled, anyway in future can be usefull
  // handle TPanel for resizing rectangles
  if Sender is TGraphicControl then
    Sender := TGraphicControl(Sender).Parent;}

  if FNodePositioning then
  begin
    with TWinControl(Sender) do
    begin
      newPos := Point(0, 0);
      GetCursorPos(newPos);

      if (newPos.x = FOldPos.x) and (newPos.y = FOldPos.y) then
        Exit;

      HideSizeControls;
      UpdateWindow(pBG.Handle);
      UpdateWindow(Self.Handle);
      UpdateWindow(Self.Parent.Handle);
      with Self do
      begin //resize
        frmPoint := Self.ScreenToClient(Mouse.CursorPos);
        frmPoint.x:= frmPoint.x + FDelta.x;
        frmPoint.y:= frmPoint.y + FDelta.y;

        OldRect := Self.BoundsRect;
        AdjL := False;
        AdjR := False;
        AdjT := False;
        AdjB := False;
        case FNodes.IndexOf(TWinControl(Sender)) of
          0: begin
               //AdjL := True;
               //AdjT := True;
             end;
          1: begin
               //AdjT := True;
             end;
          2: begin
               //AdjR := True;
               //AdjT := True;
             end;
          3, 10: begin
               AdjR := True;
             end;
          4: begin
               AdjR := True;
               AdjB := True;
             end;
          5, 11: begin
               AdjB := True;
             end;
          6: begin
               //AdjL := True;
               //AdjB := True;
             end;
          7: begin
               //AdjL := True;
             end;
        end;

        if AdjL then
          OldRect.Left := frmPoint.X;
        if AdjR then
          OldRect.Right := frmPoint.X;
        if AdjT then
          OldRect.Top := frmPoint.Y;
        if AdjB then
          OldRect.Bottom := frmPoint.Y;

        SetBounds(OldRect.Left,OldRect.Top,OldRect.Right - OldRect.Left,OldRect.Bottom - OldRect.Top);
      end;
      //move node
      Left := Left - FOldPos.X + newPos.X;
      Top := Top - FOldPos.Y + newPos.Y;
      FOldPos := newPos;
    end;
    PositionNodes(Self);
    if Assigned(OnNodePositioning) then
      OnNodePositioning(Self, FPositioningKind, pcPositioning);

    // the same operation as belowe exist in ClientChangeBounds but it is
    // disabled for FNodePositioning = true
    // we need to refresh this values after OnNodePositioning
    FLastClientWidth := pClient.Width;
    FLastClientHeight:= pClient.Height;
  end;
end;

procedure TBasicResizeFrame.NodeMouseUp(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
begin
  { TShape in TBasicResizeFrame.CreateNodes is disabled, anyway in future can be usefull
  if Sender is TGraphicControl then
    Sender := TGraphicControl(Sender).Parent;}

  if FNodePositioning then
  begin
    Screen.Cursor := crDefault;
    {$IF Defined(LCLWin32) or Defined(LCLWin64)}
    ReleaseCapture;
    {$ENDIF}

    Constraints.MaxWidth := FMaxWidth;
    Constraints.MaxHeight := FMaxHeight;
    FNodePositioning := False;
    ShowSizeControls;
    if Assigned(OnNodePositioning) then
      OnNodePositioning(Sender, FPositioningKind, pcPositioningEnd);
    FPositioningKind := [];
    FNodePositioning := False;

    pClient.Align := alNone;
    BorderSpacing.Left := 0;
    BorderSpacing.Top := 0;
    BorderSpacing.Right := 0;
    BorderSpacing.Bottom := 0;
    PositionNodes(Self);

    EndFormSizeUpdate(Sender);

    // after resizing, TFrame is frozen in Windows OS
    // this is trick to workaraund IDE bug. Also for proper size for normal form
    TryBoundDesignedForm;
    // for small resizes, designed form is moved on the top and on the bottom
    // is showed white block - to stop this we need to move pClient to right position
    PositionNodes;
    ShowSizeControls;
  end;
end;

procedure TBasicResizeFrame.OnModified;
begin
  FDesignerModified := True;
end;

function TBasicResizeFrame.GetRightMargin: Integer;
begin
  if not FNodePositioning then
    FLastRightMarign := Width - (pR.Left + pR.Width);
  Result := FLastRightMarign;
end;

function TBasicResizeFrame.HasMainMenu: Boolean;
var
  I: Integer;
begin
  Result := False;
  if  (FDesignedForm<>nil) and (FDesignedForm.Form.Menu<>nil)
  and not (csDestroying in FDesignedForm.Form.Menu.ComponentState)
  and (FDesignedForm.Form.Menu.Items.Count>0)
  then
    for I := 0 to FDesignedForm.Form.Menu.Items.Count-1 do
      if FDesignedForm.Form.Menu.Items[I].Visible then
        Exit(True);
end;

function TBasicResizeFrame.GetBottomMargin: Integer;
begin
  if not FNodePositioning then
    FLastBottomMarign := Height - (pB.Top + pB.Height);
  Result := FLastBottomMarign;
end;

{-----------------------------------------------------------------------------------------------------------------------
  for Vertical scroll
{----------------------------------------------------------------------------------------------------------------------}

function TBasicResizeFrame.BottomSizerRectHeight: Integer;
begin
  Result := SizerRectSize;
end;

function TBasicResizeFrame.BottomSizerLineWidth: Integer;
begin
  Result := SizerLineWidth;
end;

function TBasicResizeFrame.TopSizerRectTop: Integer;
begin
  Result := -FVerticalScrollPos;
end;

function TBasicResizeFrame.TopSizerLineWidth: Integer;
begin
  Result := SizerLineWidth;
end;

function TBasicResizeFrame.VerticalSizerLineLength: Integer;
begin
  Result := Height - BottomMargin;
end;

{-----------------------------------------------------------------------------------------------------------------------
  for Horizontal scroll
{----------------------------------------------------------------------------------------------------------------------}

function TBasicResizeFrame.RightSizerRectWidth: Integer;
begin
  Result := SizerRectSize;
end;

function TBasicResizeFrame.RightSizerLineWidth: Integer;
begin
  Result := SizerLineWidth;
end;

function TBasicResizeFrame.LeftSizerRectLeft: Integer;
begin
  Result := -FHorizontalScrollPos;
end;

function TBasicResizeFrame.LeftSizerLineWidth: Integer;
begin
  Result := SizerLineWidth;
end;

function TBasicResizeFrame.HorizontalSizerLineLength: Integer;
begin
  Result := Width - RightMargin;
end;

procedure TBasicResizeFrame.AdjustFormHandler;
begin
  pFormHandler.Left:=(-FDesignedForm.Form.Left)-(FDesignedForm.PositionDelta.x+ifthen(FHorizontalScrollPos-SizerLineWidth>0,FHorizontalScrollPos-SizerLineWidth,0));
  pFormHandler.Top:=(-FDesignedForm.Form.Top)-(FDesignedForm.PositionDelta.y+ifthen(FVerticalScrollPos-SizerLineWidth>0,FVerticalScrollPos-SizerLineWidth,0));
  pFormHandler.Width:=(FDesignedForm.Form.Width+abs(FDesignedForm.Form.Left)+FDesignedForm.PositionDelta.x);;
  pFormHandler.Height:=(FDesignedForm.Form.Height+abs(FDesignedForm.Form.Top)+FDesignedForm.PositionDelta.y);
end;

function TBasicResizeFrame.GetBackgroundMargin(const AIndex: Integer): Integer;
begin
  if FBackground = nil then
    Result := 0
  else
    Result := FBackground.GetMargin(AIndex);

  if (AIndex = 1) and HasMainMenu then
    Result := Result + GetMenuHeight;
end;

function TBasicResizeFrame.GetNewSize: TPoint;
begin
  Result := TPoint.Create(FLastClientWidth,FLastClientHeight);
end;

function TBasicResizeFrame.GetFormHandler: TPanel;
begin
  Result := pFormHandler;
end;

function TBasicResizeFrame.GetNodePositioning: Boolean;
begin
  Result := FNodePositioning;
end;

function TBasicResizeFrame.GetDesignedForm: IDesignedForm;
begin
  Result := FDesignedForm;
end;

procedure TBasicResizeFrame.SetDesignedForm(const AValue: IDesignedForm);
begin
  FDesignedForm := AValue;
  if FDesignedForm = nil then
  begin
    if Assigned(FBackground) then
      FBackground.ResizeFrame := nil;
    FBackground := nil;
  end
  else
    if Supports(FDesignedForm, IDesignedFormBackground, FBackground) then
    begin
      FBackground.Parent := pBG;
      FBackground.ResizeFrame := Self;
    end;
  // special for QT (at start "design form" has wrong position)
  TryBoundDesignedForm;
end;

function TBasicResizeFrame.GetMenuHeight: Integer;
begin
  // some WS (Gtk2) return too big SM_CYMENU, just set it according to font height
  // no problem, it is used only for the fake main menu

  {$IFDEF LCLWin32}
  Result := lclintf.GetSystemMetrics(SM_CYMENU);
  {$ELSE}
  if pBG.HandleAllocated then
    Result := pBG.Canvas.TextHeight('Hg') * 4 div 3
  else
    Result := 20;
  {$ENDIF}
end;

procedure TBasicResizeFrame.TryBoundDesignedForm;
begin
  if DesignedForm = nil then
    Exit;

  HideSizeControls;
  ShowSizeControls;

  // for GTK2 resizing form (pClient is hidden under pBG)
  {$IF DEFINED(LCLGtk2) OR DEFINED(LCLQt) OR DEFINED(LCLQt5)}
  pFormHandler.SendToBack; // <--- this is a must.
  {$ENDIF}
  pFormHandler.BringToFront;

  pFakeMenu.Visible := HasMainMenu;
  if pFakeMenu.Visible then
  begin
    pFakeMenu.Height := GetMenuHeight;
    pFakeMenu.BorderSpacing.Left := BgLeftMargin;
    pFakeMenu.BorderSpacing.Top := BgTopMargin - pFakeMenu.Height;
    pFakeMenu.BorderSpacing.Right := BgRightMargin;
    pFakeMenu.BringToFront;
  end;
end;

procedure TBasicResizeFrame.BeginFormSizeUpdate(Sender: TObject);
begin
  FLastDesignedWidthToScroll:=DesignedWidthToScroll;
  FLastDesignedHeightToScroll:=DesignedHeightToScroll;
  pBG.OnPaint := nil;
  pBG.SendToBack;
  FDesignedForm.BeginUpdate;
end;

procedure TBasicResizeFrame.EndFormSizeUpdate(Sender: TObject);
begin
  FDesignedForm.EndUpdate;
  pBG.OnPaint := pBGPaint;
end;

function TBasicResizeFrame.GetFrame: TCustomFrame;
begin
  Result := Self;
end;

function TBasicResizeFrame.GetVerticalScrollPos: Integer;
begin
  Result := FVerticalScrollPos;
end;

procedure TBasicResizeFrame.SetVerticalScrollPos(AValue: Integer);
begin
  FVerticalScrollPos := AValue;
end;

function TBasicResizeFrame.GetHorizontalScrollPos: Integer;
begin
  Result := FHorizontalScrollPos;
end;

procedure TBasicResizeFrame.SetHorizontalScrollPos(AValue: Integer);
begin
  FHorizontalScrollPos := AValue;
end;

function TBasicResizeFrame.GetSizerRectSize: Integer;
begin
  Result := SizerRectSize;
end;

function TBasicResizeFrame.GetSizerLineWidth: Integer;
begin
  Result := SizerLineWidth;
end;

function TBasicResizeFrame.GetBackgroundPanel: TPanel;
begin
  Result := pBG;
end;

function TBasicResizeFrame.DesignedWidthToScroll: Integer;
begin
  if DesignedForm = nil then
    Exit(0);
  if FNodePositioning then
    Result := FLastDesignedWidthToScroll
  else
    Result := abs(DesignedForm.Width - FLastClientWidth);
  //Result := DesignedForm.Width - DesignedForm.RealWidth;
end;

procedure TBasicResizeFrame.DesignerSetFocus;
begin
  if FFakeFocusControl.CanSetFocus then
    FFakeFocusControl.SetFocus;
end;

function TBasicResizeFrame.DesignedHeightToScroll: Integer;
begin
  if DesignedForm = nil then
    Exit(0);

  if FNodePositioning then
    Result := FLastDesignedHeightToScroll
  else
    Result := abs(DesignedForm.Height - FLastClientHeight);
  //Result := DesignedForm.Height - DesignedForm.RealHeight;
end;

procedure TBasicResizeFrame.ClientChangeBounds;
begin
  ClientChangeBounds(nil);
end;

{}

constructor TBasicResizeFrame.Create(TheOwner: TComponent);
begin
  inherited Create(TheOwner);

// Michl: Don't change DesignTimePPI of BasicResizeFrame (sparta_basicresizeframe.lfm).
//        There always has to be the default (none entry = 96 PPI) value!
  FSizerRectSize := ScaleX(SIZER_RECT_SIZE, 96);
  FSizerLineWidth := ScaleX(SIZER_LINE_WIDTH, 96);

  FFakeFocusControl := TEdit.Create(Self);
  FFakeFocusControl.Parent := Self;
  FFakeFocusControl.Top := -100;
  FFakeFocusControl.OnKeyDown := FakeKeyDown;
  FFakeFocusControl.OnKeyUp := FakeKeyUp;
  FFakeFocusControl.OnUTF8KeyPress := FakeUTF8KeyPress;
  FFakeFocusControl.OnEnter := FakeExitEnter;
  FFakeFocusControl.OnExit := FakeExitEnter;

  FNodes := TObjectList.Create(False);
  CreateNodes;

  pL.OnPaint := PanelPaint;
  pT.OnPaint := PanelPaint;
  pR.OnPaint := PanelPaint;
  pB.OnPaint := PanelPaint;

  pClient.OnChangeBounds := ClientChangeBounds;
  pBG.OnChangeBounds     := BGChangeBounds;
  PositionNodes(Self);

  Application.AddOnIdleHandler(AppOnIdle);
end;

procedure TBasicResizeFrame.AppOnIdle(Sender: TObject; var Done: Boolean);
var
  aHasMainMenu: Boolean;
begin
  if FDesignerModified then
  begin
    aHasMainMenu := HasMainMenu;
    if aHasMainMenu <> FOldHasMainMenu then
    begin
      FOldHasMainMenu := aHasMainMenu;
      TryBoundDesignedForm;
      if Assigned(OnNodePositioning) then
        OnNodePositioning(Self, [pkBottom], pcPositioningEnd);
      Application.NotifyUserInputHandler(Self, 0); // force repaint invisible components
    end else
    if pFakeMenu.Visible then
      pFakeMenu.Invalidate; // always repaint menu on modification

    FDesignerModified := False;
  end;
end;

destructor TBasicResizeFrame.Destroy;
begin
  Pointer(FDesignedForm) := nil;
  Pointer(FBackground) := nil;
  Application.RemoveOnIdleHandler(AppOnIdle);
  FNodes.Free;
  inherited Destroy;
end;

procedure TBasicResizeFrame.FakeExitEnter(Sender: TObject);
begin
  pL.Repaint;
  pT.Repaint;
  pR.Repaint;
  pB.Repaint;
end;

procedure TBasicResizeFrame.FakeKeyDown(Sender: TObject; var Key: Word;
  Shift: TShiftState);
var
  LWndProc: TWndMethod;
  LMsg: TLMKeyUp;
begin
  LWndProc := FDesignedForm.Form.WindowProc;
  FillChar(LMsg{%H-}, SizeOf(LMsg), 0);
  LMsg.msg := CN_KEYDOWN;
  LMsg.CharCode := Key;
  LWndProc(TLMessage(LMsg));
  Key := LMsg.CharCode;
end;

procedure TBasicResizeFrame.FakeKeyUp(Sender: TObject; var Key: Word;
  Shift: TShiftState);
var
  LWndProc: TWndMethod;
  LMsg: TLMKeyUp;
begin
  LWndProc := FDesignedForm.Form.WindowProc;
  FillChar(LMsg{%H-}, SizeOf(LMsg), 0);
  LMsg.msg := CN_KEYUP;
  LMsg.CharCode := Key;
  LWndProc(TLMessage(LMsg));
  Key := LMsg.CharCode;
end;

procedure TBasicResizeFrame.FakeUTF8KeyPress(Sender: TObject; var UTF8Key: TUTF8Char
  );
begin
  FDesignedForm.Form.IntfUTF8KeyPress(UTF8Key, 1, False);
end;

procedure TBasicResizeFrame.PositionNodes(AroundControl: TWinControl);
var
  Node,T,L,CT,CL,FR,FB,FT,FL: Integer;
  TopLeft: TPoint;
begin
  if FDesignedForm = nil then
    Exit;

  // positions of bars
  if not FNodePositioning then
  begin
    pL.Left := -FHorizontalScrollPos;
    pR.Left := FDesignedForm.Width - FHorizontalScrollPos + pL.Width + BgRightMargin + BgLeftMargin;
    pT.Top := -FVerticalScrollPos;
    pB.Top := FDesignedForm.Height - FVerticalScrollPos + pT.Height + BgBottomMargin + BgTopMargin;

    // width and height
    pL.Top:=0;
    pL.Height := FDesignedForm.Height + 2*SizerRectSize + BgTopMargin + BgBottomMargin;
    pR.Top:=0;
    pR.Height := FDesignedForm.Height + 2*SizerRectSize + BgTopMargin + BgBottomMargin;
    pT.Left:=0;
    pT.Width := FDesignedForm.Width + 2*SizerRectSize + BgLeftMargin + BgRightMargin;
    pB.Left:=0;
    pB.Width := FDesignedForm.Width + 2*SizerRectSize + BgLeftMargin + BgRightMargin;

    // client
    if pBG.Left + BgLeftMargin <= 0 then
      pClient.Left := -(pBG.Left) - (FHorizontalScrollPos - SizerRectSize)
    else
      pClient.Left := pBG.Left + BgLeftMargin;
    if pBG.Top + BgTopMargin <= 0 then
      pClient.Top := -(pBG.Top) - (FVerticalScrollPos - SizerRectSize)
    else
      pClient.Top := pBG.Top + BgTopMargin;

    pClient.Height := Height - pClient.Top - Max(Height - (pB.Top - BgBottomMargin), 0);
    pClient.Width := Width - pClient.Left - Max(Width - (pR.Left - BgRightMargin), 0);
  end;

  AdjustFormHandler;

  for Node := 0 to 7 do
  begin
    with AroundControl do
    begin
      FR := Width - RightSizerRectWidth - RightMargin;
      FB := Height - BottomSizerRectHeight - BottomMargin;

      FT := TopSizerRectTop;
      FL := LeftSizerRectLeft;

      CL := (FR - FL) div 2 + FL;
      CT := (FB - FT) div 2 + FT;

      case Node of
        0: begin
             T := FT;
             L := FL;
           end;
        1: begin
             T := FT;
             L := CL;
           end;
        2: begin
             T := FT;
             L := FR;
           end;
        3: begin
             T := CT;
             L := FR;
           end;
        4: begin
             T := FB;
             L := FR;
           end;
        5: begin
             T := FB;
             L := CL;
           end;
        6: begin
             T := FB;
             L := FL;
           end;
        7: begin
             T := CT;
             L := FL;
           end;
        else
          T := 0;
          L := 0;
      end;

      TopLeft := (Classes.Point(L,T));
    end;
    with TPanel(FNodes[Node]) do
    begin
      Top := TopLeft.Y;
      Left := TopLeft.X;
      Repaint;
    end;
  end;
end;

end.

