unit MainMenu;

interface

uses
  Windows, Graphics, Classes, QuadEngine, mSettings;

const
  SELECTION_COLOR = $88dd0000;

type
  // forward declaration
  TCustomMenuItem = class;
  TSubMenuItem = class;
  TTopMenuItem = class;
  TSubMenuBooleanItem = class;
  TSubMenuIntegerItem = class;
  TSubMenuSelectionItem = class;

  TMenu = class;

  TOnChangeItem = procedure(AIsNext: Boolean) of object;
  TOnEnterItem = procedure of object;

  TCustomMenuItem = class
  private
    FFontSize: Single;
    FFontColor: Cardinal;
    FFontColorHover: Cardinal;
    FQuadRender: IQuadRender;
    FCaption: String;
    FHeight: Single;
    FEnabled: Boolean;
    FMenu: TMenu;
    FOnChange: TOnChangeItem;
    FOnEnter: TOnEnterItem;    
    FParent: TCustomMenuItem;
    FWidth: Single;
    procedure SetCaption(const Value: String);
  public
    Selected: Boolean;
    constructor Create(AQuadRender: IQuadRender; AMenu: TMenu); virtual;

    procedure DoOnChange(AIsNext: Boolean); virtual;
    procedure Draw(AXpos, AYpos: Double); virtual; abstract;

    property Caption: String read FCaption write SetCaption;
    property Height: Single read FHeight;
    property Enabled: Boolean read FEnabled write FEnabled;
    property OnChange: TOnChangeItem read FOnChange write FOnChange;
    property OnEnter: TOnEnterItem read FOnEnter write FOnEnter;
    property Parent: TCustomMenuItem read FParent;
    property Width: Single read FWidth;
  end;

  TTopMenuItem = class(TCustomMenuItem)
  public
    function AddSubMenuItem: TSubMenuItem;
    function AddSubMenuBooleanItem: TSubMenuBooleanItem;
    function AddSubMenuIntegerItem: TSubMenuIntegerItem;
    function AddSubMenuSelectionItem: TSubMenuSelectionItem;
    procedure Draw(AXpos, AYpos: Double); override;
  end;

  TSubMenuItem = class(TCustomMenuItem)
  private

  public
    procedure Draw(AXpos, AYpos: Double); override;
    constructor Create(AQuadRender: IQuadRender; AMenu: TMenu); override;
  end;

  TSubMenuBooleanItem = class(TSubMenuItem)
  private
    FValue: Boolean;
    FLinkedValue: PBoolean;
    function GetValue: Boolean;
    procedure SetValue(const Value: Boolean);
  public
    procedure DoOnChange(AIsNext: Boolean); override;
    procedure Draw(AXpos, AYpos: Double); override;
    procedure LinkWithVar(aVar: PBoolean);

    property Value: Boolean read GetValue write SetValue;
  end;

  TSubMenuIntegerItem = class(TSubMenuItem)
  private
    FMaxValue: Integer;
    FMinValue: Integer;
    FValue: Integer;
    FLinkedValue: PInteger;
    function GetValue: Integer;
    procedure SetValue(const AValue: Integer);
  public
    procedure DoOnChange(AIsNext: Boolean); override;
    procedure Draw(AXpos, AYpos: Double); override;
    procedure LinkWithVar(aVar: PInteger);

    property MaxValue: Integer read FMaxValue write FMaxValue;
    property MinValue: Integer read FMinValue write FMinValue;
    property Value: Integer read GetValue write SetValue;
  end;

  TSubMenuSelectionItem = class(TSubMenuItem)
  private
    FItems: TStringList;
    FItemIndex: Integer;
    FLinkedValue: PInteger;
    function GetItemIndex: Integer;
    procedure SetItemIndex(const Value: Integer);
  public
    destructor Destroy; override;

    procedure AfterConstruction; override;
    procedure DoOnChange(AIsNext: Boolean); override;
    procedure Draw(AXpos, AYpos: Double); override;
    procedure LinkWithVar(aVar: PInteger);

    property ItemIndex: Integer read GetItemIndex write SetItemIndex;
    property Items: TStringList read FItems write FItems;
  end;

  TMenu = class
  private
    FQuadRender: IQuadRender;
    FList: TList;
    FSelectedItem: TCustomMenuItem;
    FSelectedSubItem: TCustomMenuItem;
    FTopMenuPosition: Integer;
    FTime: Double;
    X, Y: Integer;
    FOnExit: TOnEnterItem;
    FExitProc: Boolean;
    function GetItem(Index: Integer): TCustomMenuItem;
    function GetItemsCount: Integer;
    procedure FindNextItem;
    procedure FindNextSubItem;
    procedure FindPrevItem;
    procedure FindPrevSubItem;
    procedure SetOnExit(OnExit: TOnEnterItem);
    procedure SetSelectedItem(AItem: TCustomMenuItem);
  public
    constructor Create(AQuadRender: IQuadRender; MenuFont: IQuadFont);
    destructor Destroy; override;

    function AddItem: TTopMenuItem;
    procedure Draw(ADelta: Double);
    procedure KeyPress(Key: Word);
    procedure ItemMouseHover(mx, my: Integer);
    procedure ItemMouseClick(mx, my: Integer);

    property Items[Index: Integer]: TCustomMenuItem read GetItem; default;
    property ItemsCount: Integer read GetItemsCount;
    property Left: Integer write X;
    property Top: Integer write Y;
    property SelectedItem: TCustomMenuItem read FSelectedItem write SetSelectedItem;
    property OnExit: TOnEnterItem write SetOnExit;
  end;

implementation

uses SysUtils, Math;

var
  Font: IQuadFont;

function Animate(A, B, Time: Double): Double;
begin
  Result := (B - A) * (Time * Time * (3 - 2 * Time)) + A;
end;

{ TCustomListItem }

constructor TCustomMenuItem.Create(AQuadRender: IQuadRender; AMenu: TMenu);
begin
  FFontSize := 1.0;
  FFontColor := $FFFFFFFF;
  FQuadRender := AQuadRender;
  FMenu := AMenu;
  FEnabled := True;

  AfterConstruction; 
end;

{ TMenu }

function TMenu.AddItem: TTopMenuItem;
begin
  Result := TTopMenuItem.Create(FQuadRender, Self);
  FList.Add(Result);

  if FSelectedItem = nil then
    FSelectedItem := Result;
end;

constructor TMenu.Create(AQuadRender: IQuadRender; MenuFont: IQuadFont);
begin
  Font:= MenuFont;
  FQuadRender := AQuadRender;
  FSelectedItem := nil;
  FList := TList.Create;
  FTopMenuPosition := 0;
end;

destructor TMenu.Destroy;
var
  i: Integer;
begin
  if ItemsCount > 0 then
    for i := ItemsCount - 1 downto 0 do
      Items[i].Free;

  FList.Free;

  inherited;
end;

procedure TMenu.Draw(ADelta: Double);
var
  i : integer;
  TopItemPos, SubItemPos: Single;
  SelColor: Cardinal;
begin
  FTime := FTime + ADelta;
  if FTime > 1.5 then
    FTime := FTime - 1.5;

  TopItemPos := Y;//Settings.WindowHeight div 2 - ItemsCount * (Items[0].Height + 10) + 120;

  FQuadRender.SetBlendMode(qbmSrcAlpha);

  if ItemsCount > 0 then
    for i := 0 to ItemsCount - 1 do
    begin
      if (Items[i] is TTopMenuItem) then
      if (Items[i].Enabled) then
      begin
        if FSelectedItem = Items[i] then
        begin
          FQuadRender.Rectangle(X, TopItemPos, X + 270, TopItemPos + Items[i].Height, SELECTION_COLOR);
        end;                                                  

        Items[i].Draw(X + 10, TopItemPos);
        TopItemPos := TopItemPos + Items[i].Height + 10;
      end;
    end;
end;

procedure TMenu.SetSelectedItem(AItem: TCustomMenuItem);
var
  i, j: Integer;
  TopItemPos: Single;
  Count: Byte;
begin
  Count:= 0;
  TopItemPos := Y;
  if ItemsCount > 0 then
    for i := 0 to ItemsCount - 1 do
      Items[i].Selected := False;

  FSelectedSubItem := AItem;
  AItem.Selected := True;
end;

procedure TMenu.ItemMouseHover(mx, my: Integer);
var
  i, j: Integer;
  TopItemPos: Single;
  Count: Byte;
begin
  Count:= 0;
  TopItemPos := Y;
  if ItemsCount > 0 then
    for i := 0 to ItemsCount - 1 do
      if (Items[i].Enabled) then
        begin
          if  (mx > X) and
              (my > TopItemPos + (Items[i].Height + 10) * Count) and
              (mx < X + 270) and
              (my < TopItemPos + (Items[i].Height + 10) * (Count + 1)) then
          begin
            FSelectedItem := Items[i];
            Exit;
          end;
          Inc(Count);
        end;

  FSelectedItem := nil;
end;

procedure TMenu.ItemMouseClick(mx, my: Integer);
var
  i: Integer;
  TopItemPos: Single;
  Count: Byte;
begin  
  if  (mx < X) or (mx > X + 270) or (my < 100) then
    Exit;

  Count := 0;
  TopItemPos := Y;
  if ItemsCount > 0 then
    for i := 0 to ItemsCount - 1 do
      if assigned(Items[i]) and (Items[i].Enabled) then
      begin
        if (mx >= X) and
              (my >= TopItemPos + Items[i].Height * Count) and
              (mx <= X + 270) and
              (my <= TopItemPos + Items[i].Height * (Count + 1)) then
            FSelectedItem.OnEnter;
          Inc(Count);
        end;
end;

procedure TMenu.FindNextItem;
var
  i, j: Integer;
begin
  if FSelectedSubItem <> nil then
    Exit;

  j := ItemsCount;
  for i := 0 to ItemsCount - 1 do
  begin
    if FSelectedItem = Items[i] then
      j := i;

    if (Items[i] is TTopMenuItem) and (j < i) and (Items[i].Enabled) then
    begin
      FSelectedItem := Items[i];
      Exit;
    end;
  end;
end;

procedure TMenu.FindNextSubItem;
var
  i, j: Integer;
begin
  FTime := 0.75;

  if FSelectedSubItem = nil then
    j := 0
  else
    j := ItemsCount;

  for i := 0 to ItemsCount - 1 do
  begin
    if FSelectedSubItem = Items[i] then
      j := i;

    if Items[i].Enabled and (Items[i] is TSubMenuItem) and (j < i) and (Items[i].Parent = FSelectedItem) then
    begin
      FSelectedSubItem := Items[i];
      Exit;
    end;
  end;

  FSelectedSubItem := nil;
end;

procedure TMenu.FindPrevItem;
var
  i, j: Integer;
begin
  if FSelectedSubItem <> nil then
    Exit;
    
  j := 0;
  for i := ItemsCount - 1 downto 0 do
  begin
    if FSelectedItem = Items[i] then
      j := i;

    if (Items[i] is TTopMenuItem) and (j > i) and (Items[i].Enabled) then
    begin
      FSelectedItem := Items[i];
      Exit;
    end;
  end;
end;

procedure TMenu.FindPrevSubItem;
var
  i, j: Integer;
begin
  FTime := 0.75;

  if FSelectedSubItem = nil then
    j := ItemsCount
  else
    j := 0;

  for i := ItemsCount - 1 downto 0 do
  begin
    if FSelectedSubItem = Items[i] then
      j := i;

    if Items[i].Enabled and (Items[i] is TSubMenuItem) and (j > i) and (Items[i].Parent = FSelectedItem) then
    begin
      FSelectedSubItem := Items[i];
      Exit;
    end;
  end;
  FSelectedSubItem := nil;
end;

function TMenu.GetItem(Index: Integer): TCustomMenuItem;
begin
  Result := TCustomMenuItem(FList[Index]);
end;

function TMenu.GetItemsCount: Integer;
begin
  Result := FList.Count;
end;           

procedure TMenu.SetOnExit(OnExit: TOnEnterItem);
begin
  FOnExit:= OnExit;
  FExitProc:= true;
end;

procedure TMenu.KeyPress(Key: Word);
begin
  case Key of
    VK_DOWN: FindNextItem;
    VK_UP  : FindPrevItem;
  end;
  case Key of
    VK_ESCAPE, VK_LEFT:
      begin
        if FExitProc then
          FOnExit;
      end;
    VK_RETURN, VK_RIGHT:
      if (FSelectedSubItem <> nil) then
      begin
        if Assigned(FSelectedSubItem.OnEnter) then
          FSelectedSubItem.OnEnter;
      end
      else
        if Assigned(FSelectedItem.OnEnter) then
          FSelectedItem.OnEnter;
  end;
end;

{ TTopMenuItem }

function TTopMenuItem.AddSubMenuBooleanItem: TSubMenuBooleanItem;
begin
  Result := TSubMenuBooleanItem.Create(FQuadRender, FMenu);
  Result.FParent := Self;
  FMenu.FList.Add(Result);
end;

function TTopMenuItem.AddSubMenuIntegerItem: TSubMenuIntegerItem;
begin
  Result := TSubMenuIntegerItem.Create(FQuadRender, FMenu);
  Result.FParent := Self;
  FMenu.FList.Add(Result);
end;

function TTopMenuItem.AddSubMenuItem: TSubMenuItem;
begin
  Result := TSubMenuItem.Create(FQuadRender, FMenu);
  Result.FParent := Self;
  FMenu.FList.Add(Result);
end;

function TTopMenuItem.AddSubMenuSelectionItem: TSubMenuSelectionItem;
begin
  Result := TSubMenuSelectionItem.Create(FQuadRender, FMenu);
  Result.FParent := Self;
  FMenu.FList.Add(Result);
end;

procedure TTopMenuItem.Draw(AXpos, AYpos: Double);
begin
  inherited;
    Font.TextOut(AXpos+2, AYPos+2, FFontSize, PAnsiChar(AnsiString(FCaption)), $FF000000);

  if Selected then
    Font.TextOut(AXpos, AYPos, FFontSize, PAnsiChar(AnsiString(FCaption)), SELECTION_COLOR)
  else
    Font.TextOut(AXpos, AYPos, FFontSize, PAnsiChar(AnsiString(FCaption)), FFontColor);
end;

procedure TCustomMenuItem.DoOnChange(AIsNext: Boolean);
begin
  if Assigned(FOnChange) then
    FOnChange(AIsNext);
end;

procedure TCustomMenuItem.SetCaption(const Value: String);
begin
  FCaption := Value;
  FWidth:= Font.TextWidth(PAnsiChar(AnsiString(FCaption)), FFontSize);
  FHeight:= Font.TextHeight(PAnsiChar(AnsiString(FCaption)), FFontSize);

  if Value = '-' then
  begin
    FHeight := FHeight / 4;
    FCaption := '';
    FEnabled := False;
  end;
end;

{ TSubMenuItem }

constructor TSubMenuItem.Create(AQuadRender: IQuadRender; AMenu: TMenu);
begin
  inherited;
  FFontSize := 0.5;
  FFontColor := $FF88FFFF;
end;

procedure TSubMenuItem.Draw(AXpos, AYpos: Double);
begin
  inherited;

  Font.TextOut(AXpos, AYPos, FFontSize, PAnsiChar(AnsiString(FCaption)), FFontColor);
end;

{ TSubMenuBooleanItem }

procedure TSubMenuBooleanItem.DoOnChange(AIsNext: Boolean);
begin
  Value := not Value;
  inherited;
end;

procedure TSubMenuBooleanItem.Draw(AXpos, AYpos: Double);
begin
  inherited;
  if Enabled then
    if Value then
      Font.TextOutAligned(AXpos + 400, AYPos, FFontSize, '<On>', FFontColor, qfaCenter)
    else
      Font.TextOutAligned(AXpos + 400, AYPos, FFontSize, '<Off>', FFontColor, qfaCenter);
end;

function TSubMenuBooleanItem.GetValue: Boolean;
begin
  if FLinkedValue <> nil then
    Result := FLinkedValue^
  else
    Result := FValue;
end;

procedure TSubMenuBooleanItem.LinkWithVar(aVar: PBoolean);
begin
  FLinkedValue := aVar;
end;

procedure TSubMenuBooleanItem.SetValue(const Value: Boolean);
begin
  if FLinkedValue <> nil then
    FLinkedValue^ := Value
  else
    FValue := Value;
end;

{ TSubMenuIntegerItem }

procedure TSubMenuIntegerItem.DoOnChange(AIsNext: Boolean);
begin
  if AIsNext then
  begin
    if Value < FMaxValue then
      Value := Value + 1;
  end
  else
    if Value > FMinValue then
      Value := Value - 1;

  inherited;      
end;

procedure TSubMenuIntegerItem.Draw(AXpos, AYpos: Double);
begin
  inherited;
  if Enabled then
    Font.TextOutAligned(AXpos+ 400, AYPos, FFontSize, PAnsiChar(AnsiString('<'+IntToStr(Value)+'>')), FFontColor, qfaCenter);
end;

function TSubMenuIntegerItem.GetValue: Integer;
begin
  if FLinkedValue <> nil then
    Result := FLinkedValue^
  else
    Result := FValue;
end;

procedure TSubMenuIntegerItem.LinkWithVar(aVar: PInteger);
begin
  FLinkedValue := aVar;
end;

procedure TSubMenuIntegerItem.SetValue(const AValue: Integer);
begin
  if FLinkedValue <> nil then
    FLinkedValue^ := AValue
  else
    FValue := AValue;
end;

{ TSubMenuSelectionItem }

procedure TSubMenuSelectionItem.AfterConstruction;
begin
  inherited;
  FItems := TStringList.Create;
end;

destructor TSubMenuSelectionItem.Destroy;
begin
  FItems.Free;
  inherited;
end;

procedure TSubMenuSelectionItem.DoOnChange(AIsNext: Boolean);
begin
  if Items.Count = 0 then
  begin
    ItemIndex := -1;
    Exit;
  end;

  if AIsNext then
  begin
    if (ItemIndex < FItems.Count - 1) then
      ItemIndex := ItemIndex + 1;
  end
  else
    if ItemIndex > 0 then
      ItemIndex := ItemIndex - 1;

  inherited;
end;

procedure TSubMenuSelectionItem.Draw(AXpos, AYpos: Double);
begin
  inherited;
  if Enabled then
    if ItemIndex <> -1 then
      Font.TextOutAligned(AXpos + 400, AYPos, FFontSize, PAnsiChar(AnsiString('<'+FItems[ItemIndex]+'>')), FFontColor, qfaCenter);
end;

function TSubMenuSelectionItem.GetItemIndex: Integer;
begin
  if FLinkedValue <> nil then
    Result := FLinkedValue^
  else
    Result := FItemIndex;
end;

procedure TSubMenuSelectionItem.LinkWithVar(aVar: PInteger);
begin
  FLinkedValue := aVar;
end;

procedure TSubMenuSelectionItem.SetItemIndex(const Value: Integer);
begin
  if FLinkedValue <> nil then
    FLinkedValue^ := Value
  else
    FItemIndex := Value;
end;

end.

//t*t*(3-2*t);

