unit zglGui;

interface

uses zglHeader, Windows, Classes;

const
  TEXT_CENTER = TEXT_HALIGN_CENTER or TEXT_VALIGN_CENTER;
  TEXT_LEFT = TEXT_HALIGN_LEFT or TEXT_VALIGN_CENTER;
  TEXT_RIGHT = TEXT_HALIGN_RIGHT or TEXT_VALIGN_CENTER;

  // Прокрутка
  SCROLL_NONE = 0;                            
  SCROLL_LEFT = 1;
  SCROLL_TOP =  2;
  SCROLL_BOTH = 3;

  // CHECKBOX
  cx_left = 32; cx_top = 0; cx_width = 16; cx_height = 16;
  // RADIOBUTTON
  rd_left = 48; rd_top = 0; rd_width = 16; rd_height = 16;
  // FORM
  frm_left = 64; frm_top = 0; frm_width = 32; frm_height = 32;  frm_offset = 8; frm_offset_capt = 32;
  frm_offsetx = 8; frm_offsety = -6;
  // BUTTON
  btn_left = 0; btn_top = 0; btn_width = 16; btn_height = 8;
  // EDIT
  edt_left = 32; edt_top = 64; edt_width = 16; edt_height = 16; edt_offset = 3;
  edt_line_color = $FFFFFF; edt_line_alpha = 255;
  // COMBOBOX
  cmb_left = 64; cmb_top = 64; cmb_width = 16; cmb_height = 16; cmb_offsetx = 3; cmb_offsety = 2; // поле комбобокса
  cmb_btt_left = 96; cmb_btt_top = 64; cmb_btt_width = 14; cmb_btt_height = 13;                   // кнопка справа
  cmb_itm_left = 112; cmb_itm_top = 64; cmb_itm_width = 8; cmb_itm_height = 8;                    // выделенный элемент
  cmb_frm_left = 0; cmb_frm_top = 96; cmb_frm_width = 16; cmb_frm_height = 16;                    // форма вып. списка
  cmb_scr_left = 128; cmb_scr_top = 0; cmb_scr_width = 16; cmb_scr_height = 16;                   // Кнопки прокрутки
  cmb_scr2_left = 128; cmb_scr2_top = 49;
  cmb_scf_left = 128; cmb_scf_top = 17; cmb_scf_width = 16; cmb_scf_height = 32;
  // PAGECONTROL
  pcb_drawoffset = 8; pcb_height = 25;
  pc_left = 0; pc_top = 128; pc_width = 16; pc_height = 16;

type

  TCaption = String;
  THandleEvent = (heNone, heMoveFrame, heInputText, heComboField, heComboMove);
  TImageAlign = (iaLeft, iaRight);


  // шрифт
  zglTFontObject = class
    public
      Data: zglPFont;
      Size: single;
      Color: integer;
      Alpha: Byte;
      Align: integer;
      constructor Create(pData: zglPFont; pSize: Single; pColor: integer; pAlpha: Byte; pAlign: Integer = TEXT_CENTER);
  end;
  zglTFontContainer = class
    Normal : zglTFontObject;
    Active: zglTFontObject;
    constructor Create(pNormal, pActive: zglTFontObject); overload;
    constructor Create(pData: zglPFont; pSize: Single; pColor: integer; pAlpha: Byte; pAlign: Integer = TEXT_CENTER); overload;
  end;

  zglTGui = class;
  zglTFrame = class;
  zglTGUIObject = class;

  zglTEvent = procedure (Sender: zglTGUIObject);
  zglTGuiEvent = procedure (Sender: zglTGui);
  zglTClickEvent = procedure (Sender: zglTGUIObject; var handleParentClick: boolean);
  zglTMouseEvent = procedure (Sender: zglTGUIObject; X, Y: integer);
  zglTKeyEvent = procedure (Sender: zglTGUIObject; key: integer);

  // базовый объект
  zglTGUIObject = class(TObject)
    private
      Parent: zglTFrame;
      Gui: zglTGui;
      fCaption: TCaption;
      fPressed, fHover, fJustPressed, fVisible, fCatchMouseClick, lockHover: Boolean;
      fMouse_X, fMouse_Y, fOffsetX, fOffsetY, fTag: integer;
      fRect: zglTRect;
      fFont: zglTFontContainer;
      fOnClick: zglTClickEvent;
      fOnMouseEnter, fOnMouseLeave, fOnMouseMove: zglTMouseEvent;
      function updateRect(r:zglTRect):zglTRect;
      function updateOffset(r:zglTRect; offsetX: integer = 0; offsetY: integer = 0):zglTRect;
      function getRootFont (pActive: Boolean): zglTFontObject;
      procedure Draw; virtual; abstract;
      procedure Update(dt: Double; clntRect: zglTRect); virtual;
      procedure HandleDraw(evnt: THandleEvent); virtual; abstract;
      procedure HandleUpdate(dt: Double; evnt: THandleEvent); virtual; abstract;
      procedure HandleLeave(evnt: THandleEvent); virtual; abstract;
      function isHover(upr: zglTRect): boolean; overload;
      constructor Create(pGui: zglTGui; pX, pY, pW, pH: Single);
    public
      property Visible: Boolean read fVisible write fVisible;
      property Caption: TCaption read fCaption write fCaption;
      property OnClick: zglTClickEvent read fOnClick write fOnClick;
      property CatchMouseClick: boolean read fCatchMouseClick write fCatchMouseClick;
      property OnMouseEnter: zglTMouseEvent read fOnMouseEnter write fOnMouseEnter;
      property OnMouseLeave: zglTMouseEvent read fOnMouseLeave write fOnMouseLeave;
      property OnMouseMove: zglTMouseEvent read fOnMouseMove write fOnMouseMove;
      property Rect: zglTRect read fRect write fRect;
      property Font: zglTFontContainer read fFont write fFont;
      property Tag: integer read fTag write fTag;
      procedure Move (pX, pY: Single);
      procedure Resize (pW, pH: Single); virtual;
      procedure Show;
      procedure Hide;
  end;


  // метка
  zglTLabel = class(zglTGUIObject)
    public
      constructor Create(pGui: zglTGui; pX, pY, pW, pH: Single; pCaption: TCaption);
    private
      procedure Draw; override;
      procedure Update(dt: Double; clntRect: zglTRect); override;
  end;
  // кнопка
  zglTButton = class(zglTGUIObject)
    private
      procedure Draw; override;
      procedure Update(dt: Double; clntRect: zglTRect); override;
    public
      constructor Create(pGui: zglTGui; pX, pY, pW, pH: Single; pCaption: TCaption);
  end;
  // кнопка - картинка
  zglTImageButton = class(zglTGUIObject)
    private
      fTexture: zglPTexture;
      fTexWidth, fTexHeight: Word;
      procedure Draw; override;
      procedure Update(dt: Double; clntRect: zglTRect); override;
    public
      constructor Create(pGui: zglTGui; pX, pY, pW, pH: Single; texWidth, texHeight: word; pTexture: zglPTexture);
  end;
  // поле ввода
  zglTEdit = class(zglTGUIObject)
    private
      fAlign, fMaxLength: integer;
      fUpd: single;
      fPasswordChar: Char;
      procedure Draw; override;
      procedure Update(dt: Double; clntRect: zglTRect); override;
      procedure HandleDraw(evnt: THandleEvent); override;
      procedure HandleUpdate(dt: Double; evnt: THandleEvent); override;
      procedure HandleLeave(evnt: THandleEvent); override;
    public
      property MaxLength: integer read fMaxLength write fMaxLength;
      property PasswordChar: char read fPasswordChar write fPasswordChar;
      property Align: integer read fAlign write fAlign;
      constructor Create(pGui: zglTGui; pX, pY, pW, pH: Single; pCaption: TCaption; pAlign: Integer = TEXT_LEFT);
  end;
  // комбо-бокс
  zglTComboBox = class(zglTGUIObject)
    private
      fSelected, fComboMax: Word;
      fOffset, fdOffset, fsOffset: Integer;
      procedure Draw; override;
      procedure Update(dt: Double; clntRect: zglTRect); override;
      procedure HandleDraw(evnt: THandleEvent); override;
      procedure HandleUpdate(dt: Double; evnt: THandleEvent); override;
      procedure HandleLeave(evnt: THandleEvent); override;
    public
      Items: TStringList;
      property Selected: Word read fSelected write fSelected;
      property ComboMax: Word read fComboMax write fComboMax;
      constructor Create(pGui: zglTGui; pX, pY, pW: Single; pH: Single = cmb_itm_height*2 + 1; pComboMax: Word = 8);
      destructor Destroy; override;
  end;
  // Чекбокс
  zglTCheckBox = class(zglTGUIObject)
    private
      fChecked: Boolean;
      procedure Draw; override;
      procedure Update(dt: Double; clntRect: zglTRect); override;
    public
      property Checked: Boolean read fChecked write fChecked;
      constructor Create(pGui: zglTGui; pX, pY, pW, pH: Single; pCaption: TCaption);
  end;
  // Радио-кнопка
  zglTRadioButton = class(zglTGUIObject)
    public
      constructor Create(pGui: zglTGui; pX, pY, pW, pH: Single; pCaption: TCaption);  
    private
      procedure Draw; override;
      procedure Update(dt: Double; clntRect: zglTRect); override;
  end;
  // фрейм (контейнер других объектов, все, что может кого-то содержать - дочь этого)
  zglTFrame = class(zglTGUIObject)
    private
      Objects: TList;
      fCanMove: Boolean;
      fScrollBars: byte;
      fScroll, fClientRect, fBorder: zglTRect;
      fOnResize: zglTEvent;
      procedure UpdateClientRect;
      procedure Draw; override;
      procedure Update(dt: Double; clntRect: zglTRect); override;
      procedure HandleDraw(evnt: THandleEvent); override;
      procedure HandleUpdate(dt: Double; evnt: THandleEvent); override;
      procedure HandleLeave(evnt: THandleEvent); override;
    public
      property ClientRect: zglTRect read fClientRect write fClientRect;
      property Scroll: zglTRect read fScroll write fScroll;
      property CanMove: Boolean read fCanMove write fCanMove;
      property ScrollBars: Byte read fScrollBars write fScrollBars;
      property OnResize: zglTEvent read fOnResize write fOnResize;
      function Add(obj: zglTGUIObject): zglTGUIObject;
      procedure Remove(obj: zglTGUIObject);
      procedure Resize (pW, pH: Single); override;
      procedure MoveToCenter;
      procedure FullScreen;
      procedure ScrollTo (pX, pY: Single);
      constructor Create(pGui: zglTGui; pX, pY, pW, pH: Single); virtual;
      constructor CreateFullScreen(pGui: zglTGui);
      destructor Destroy; override;
  end;
  zglTPageControl = class;
  // вкладки
  zglTPageControlSheet = class (zglTFrame)
    private
      Objects: TList;
      PageControl: zglTPageControl;
      fButton: zglTButton;
      procedure Draw; override;
      procedure Update(dt: Double; clntRect: zglTRect); override;
      constructor Create(pGui: zglTGui; pc: zglTPageControl; pCaption: TCaption);
      destructor Destroy;
  end;
  zglTPageControl = class (zglTFrame)
    private
      fSelected: Word;
      fSheets: TList;
      procedure Draw; override;
      procedure Update(dt: Double; clntRect: zglTRect); override;
      function getSheet(Index: Integer):zglTPageControlSheet;
      procedure setSelected(Index: Word);
      procedure RelocateButtons;
    public
      property Sheets[Index: integer]:zglTPageControlSheet read getSheet;
      property Selected:Word read fSelected write setSelected;
      function AddSheet(pCaption: TCaption): zglTPageControlSheet;
      procedure DeleteSheet(id: Integer);
      procedure Resize (pW, pH: Single); override;
      constructor Create(pGui: zglTGui; pX, pY, pW, pH: Single); override;
      destructor Destroy;
  end;
  // контейнер с полосами прокрутки
  zglTScrollBox = class(zglTFrame)
    private
      procedure Draw; override;
      procedure Update(dt: Double; clntRect: zglTRect); override;
    public
      constructor Create(pGui: zglTGui; pX, pY, pW, pH: Single);
  end;
  // Контейнер радио-кнопок
  zglTRadioBox = class(zglTFrame)
    private
      fSelected: zglTRadioButton;
      procedure Draw; override;
      procedure Update(dt: Double; clntRect: zglTRect); override;
    public
      property Selected: zglTRadioButton read fSelected write fSelected;
      procedure Select(pI: zglTGUIObject);
      constructor Create(pGui: zglTGui; pX, pY, pW, pH: Single);
  end;
  // ФОРМА
  zglTForm = class(zglTFrame)
    public
      procedure Draw; override;
      procedure Update(dt: Double; clientRect: zglTRect); override;
      constructor Create(pGui: zglTGui; pX, pY, pW, pH: Single; pCaption: TCaption);
      destructor Destroy; override;
  end;
  // обработчик всплывающих деталей
  zglTHandler = class
    private
      gui: zglTGui;
      hObject: zglTGUIObject;
      hHandle: THandleEvent;
      canHover: boolean;
      procedure HandleEvent(obj: zglTGUIObject; evnt: THandleEvent; pCanHover: boolean);
      procedure Update(dt: Double);
      procedure Draw;
      procedure Clear;
      constructor Create;
  end;
  // главный класс, рисует-обновляет все
  zglTGui = class
    private
      Objects: TList;
      Handler: zglTHandler;
      fVisible: Boolean;
      tex: zglPTexture;
      grid: zglTGrid2D;
      fRect: zglTRect;
      fOnResize: zglTGuiEvent;

      function getPoint(X, Y: single):zglTPoint2D;
      function getRect(X, Y, W, H: single):zglTRect;
      procedure DrawTex(X, Y, dX, dY, dW, dH: Single);
      procedure DrawField(X, Y, W, H, dX, dY, dW, dH: Single);
    public
      property Visible: Boolean read fVisible write fVisible;
      property OnResize: zglTGuiEvent read fOnResize write fOnResize;
      procedure Add(obj: zglTGUIObject);
      procedure Remove(obj: zglTGUIObject);
      procedure Draw;
      procedure Update(dt: Double);
      constructor Create(dir: string; screenWidth, screenHeight: integer);
      procedure Resize(screenWidth, screenHeight: integer);
      destructor Destroy; override;
  end;


implementation

// other

procedure OnPageControlButtonClick(Sender: zglTGUIObject; var handleParentClick: boolean);
begin
  zglTPageControl(zglTButton(Sender).Parent).Selected :=
    zglTButton(Sender).Tag;
end;

function GetClipboardText(Wnd: HWND; var Str: string): Boolean;
var
  hData: HGlobal;
begin
  Result := True;
  if OpenClipboard(Wnd) then
  begin
    try
      hData := GetClipboardData(CF_TEXT);
      if hData <> 0 then
      begin
        try
          SetString(Str, PChar(GlobalLock(hData)), GlobalSize(hData));
        finally
          GlobalUnlock(hData);
        end;
      end
      else
        Result := False;
      Str := PChar(@Str[1]);
    finally
      CloseClipboard;
    end;
  end
  else
    Result := False;
end;

// zglTObject

function zglTGUIObject.updateRect;
var obj: zglTFrame;
begin
  obj := Self.Parent;
  result := r;
  while Assigned(obj) do begin
    result.X := result.X + obj.Rect.X + obj.Scroll.X + obj.ClientRect.X;
    result.Y := result.Y + obj.Rect.Y + obj.Scroll.Y + obj.ClientRect.Y;
    obj := obj.Parent;
  end;
end;

function zglTGUIObject.updateOffset;
begin
  result.W := r.W - offsetX * 2;
  result.H := r.H - offsetY * 2;
  result.X := r.X + offsetX;
  result.Y := r.Y + offsetY;
end;

procedure zglTGUIObject.Move;
begin
  fRect.X := pX; fRect.Y := pY;
end;

procedure zglTGUIObject.Resize;
begin
  fRect.W := pW; fRect.H := pH;
end;

procedure zglTGUIObject.Show;
begin
  fVisible := true;
end;

procedure zglTGUIObject.Hide;
begin
  fVisible := false;
end;

function zglTGUIObject.getRootFont;
var obj: zglTGUIObject;
begin
  obj := Self;
  while not Assigned(obj.Font) do begin
    obj := obj.Parent;
  end;
  if pActive then
    Result := obj.Font.Active
  else
    Result := obj.Font.Normal;
end;

function zglTGUIObject.isHover(upr: zglTRect): boolean;
var r: zglTRect;
begin
  r := updateRect(fRect);
  result := col2d_PointInRect(Mouse_X, Mouse_Y, upr) and
            col2d_PointInRect(Mouse_X, Mouse_Y, r);
end;

procedure zglTGUIObject.Update;
var nowIsHover, b: Boolean;
    r: zglTRect;
begin
  if not Visible then Exit;
  if (Gui.Handler.canHover) then
    nowIsHover := isHover(clntRect) xor lockHover
  else
    nowIsHover := false;
  fJustPressed := false;
  r := updateRect(fRect);
  if nowIsHover then begin
    if mouse_Click(M_BLEFT) then begin
      fPressed := true;
      fJustPressed := true;
      gui.Handler.Clear;
      if CatchMouseClick then
        mouse_ClearState;
    end;
    if ((fMouse_X <> Mouse_X) or (fMouse_Y <> Mouse_Y)) and Assigned(OnMouseMove) then begin
      OnMouseMove(Self, mouse_X - round(r.X), Mouse_Y - round(r.Y));
    end;
    fMouse_X := Mouse_X; fMouse_Y := Mouse_Y;
  end;
  if nowIsHover xor fHover then begin
    if nowIsHover then begin
      fMouse_X := -1; fMouse_Y := -1;
      if Assigned(OnMouseEnter) then
        OnMouseEnter(Self, mouse_X - round(r.X), Mouse_Y - round(r.Y));
    end else begin
      if Assigned(OnMouseLeave) then
        OnMouseLeave(Self, fmouse_X - round(r.X), fMouse_Y - round(r.Y));
    end;
    fHover := nowIsHover;
  end;
  if fPressed and (not mouse_Down(M_BLEFT)) then begin
    if nowIsHover then begin
      if Assigned(OnClick) then begin
        b := true;
        OnClick(self, b);
        if (not b) and CatchMouseClick then mouse_ClearState;
      end;
    end;
    fPressed := false;
  end;
  lockHover := false;
end;

constructor zglTGUIObject.Create;
begin
  fRect.X := pX; fRect.Y := pY; fRect.W := pW; fRect.H := pH;
  fPressed := False; fHover := false; Visible := true; fTag := 0;
  Gui := pGui; CatchMouseClick := true; lockHover := false;
end;

// zglTSomeFont

constructor zglTFontObject.Create;
begin
  Data := pData; Size := pSize;
  Color := pColor; Alpha := pAlpha;
  Align := pAlign;
end;

// zglTLabel

procedure zglTLabel.Draw;
var fnt: zglTFontObject;
    rct: zglTRect;
begin
  if not Visible then Exit;
  rct := updateRect(fRect);
  fnt := getRootFont(fHover);
  text_DrawInRectEx(fnt.Data, rct, fnt.Size, 0, Caption, fnt.Alpha, fnt.Color, fnt.Align);
end;

procedure zglTLabel.Update;
begin
  if not Visible then Exit;
  inherited;
end;

constructor zglTLabel.Create;
begin
  inherited Create(pGui, pX, pY, pW, pH);
  Caption := pCaption;
end;

// zglTEdit

procedure zglTEdit.Draw;
var fnt: zglTFontObject;
    rct: zglTRect;
    i, w, off, f: single;
    s: string;
begin

  if not Visible then Exit;
  rct := updateRect(fRect);
  s := fCaption;
  if fPasswordChar <> #0 then
   FillChar(s[1], Length(s), fPasswordChar);
  fnt := getRootFont(Gui.Handler.hObject = self);                                               
  Gui.DrawField(rct.X, rct.Y, rct.W, rct.H, edt_left, edt_top, edt_width, edt_height);
  rct := updateOffset(rct, edt_offset, edt_offset);
  scissor_Begin(round(rct.X), round(rct.Y), round(rct.W), round(rct.H));
  off := 0;
  case Align of
    TEXT_RIGHT: f := 0;
    TEXT_CENTER: f := 0.5;
    else f := 1;
  end;

  i := rct.W * (1 - f);
  w := rct.W * (1 - f) + text_GetWidth(fnt.Data, s) * fnt.Size * f;
  if w > rct.W then begin
     off := rct.W - w;
     w := rct.W;
  end;
  text_DrawEx(fnt.Data, round(rct.X+ i + off), round(rct.Y + rct.H/2), fnt.Size, 0, s, fnt.Alpha, fnt.Color, Align);
  scissor_End;
  if (Gui.Handler.hObject = self) and (fUpd > 250) then begin
    pr2d_line(rct.X + w, rct.Y, rct.X + w, rct.Y+ rct.H, edt_line_color, edt_line_alpha);
  end;
end;

procedure zglTEdit.Update;
begin
  if not Visible then Exit;
  inherited;
  if fJustPressed then begin
    key_BeginReadText(fCaption, fMaxLength);     
    Gui.Handler.HandleEvent(Self, heInputText, true); 
  end;
end;

constructor zglTEdit.Create;
begin
  inherited Create(pGui, pX, pY, pW, pH);
  Align := pAlign;
  Caption := pCaption;
  MaxLength := -1;
  fPasswordChar := #0;
end;

procedure zglTEdit.HandleDraw;
begin
  //
end;

procedure zglTEdit.HandleLeave;
begin
  fCaption := key_GetText;
  key_EndReadText;
end;
                     
procedure zglTEdit.HandleUpdate;
var s: string;
begin
  case evnt of
    heInputText: begin
      if key_Down(K_CTRL) and key_Press(K_V) then begin
        key_ClearState;
        GetClipboardText(zgl_Get(WINDOW_HANDLE), s);
        fCaption := fCaption + s;
        key_BeginReadText(fCaption);
      end;
      fCaption := key_GetText;
      fUpd := fUpd + dt;
      if fUpd > 500 then fUpd := 0;
    end;
  end;
end;

// zglTComboBox

procedure zglTComboBox.Draw;
var fnt: zglTFontObject;
    rct: zglTRect;
begin

  if not Visible then Exit;
  rct := updateRect(fRect);
  fnt := getRootFont(Gui.Handler.hObject = self);
  Gui.DrawField(rct.X, rct.Y, rct.W, rct.H, cmb_left, cmb_top, cmb_width, cmb_height);
  rct := updateOffset(rct, cmb_offsetx, cmb_offsety);
  Gui.DrawTex(rct.X + rct.W - cmb_btt_width, rct.Y,
    cmb_btt_left, cmb_btt_top + cmb_btt_height * Byte(fHover), cmb_btt_width, cmb_btt_height);  
  rct.W := rct.W - cmb_btt_width;                                
  if fPressed then begin
    rct.X := rct.X + 1; rct.Y := rct.Y + 1;
  end;
  text_DrawInRectEx(fnt.Data, rct, fnt.Size, 0, Caption, fnt.Alpha, fnt.Color, fnt.Align);
end;

procedure zglTComboBox.Update;
begin
  if not Visible then Exit;
  inherited;
  if Selected >= Items.Count then
    Selected := Items.Count - 1;
  if (Items.Count = 0) then begin
    Caption := '';
  end else begin
    Caption := Items[Selected];
  end;

  if fJustPressed then begin
    fOffset := 0;
    fdOffset := 0;
    if Gui.Handler.hObject = nil then
      Gui.Handler.HandleEvent(Self, heComboField, false)
    else
      Gui.Handler.Clear;
  end;
end;

constructor zglTComboBox.Create;
begin
  inherited Create(pGui, pX, pY, pW, pH);
  Items := TStringList.Create;
  fSelected := 0; fComboMax := pComboMax;  fOffset := 0;
end;

destructor zglTComboBox.Destroy;
begin
  Items.Free;
  inherited;
end;

procedure zglTComboBox.HandleDraw;
var rct, r: zglTRect;
    i, ic: integer;
    fnt: zglTFontObject;
    b: boolean;
    pX, pY: single;
begin

  rct := updateRect(fRect);
  rct.Y := rct.Y + rct.H;
  ic := Items.Count;
  if ic > ComboMax then begin
    ic := ComboMax;
    b := True;
  end else b := False;
  rct.H := ic * cmb_itm_height * 2;
  r := rct; r.H := cmb_itm_height * 2;

  gui.DrawField(rct.X, rct.Y, rct.W, rct.H, cmb_frm_left, cmb_frm_top, cmb_frm_width, cmb_frm_height);

  if b then begin
    pX := rct.X + rct.W - cmb_scr_width; pY := rct.Y;
    gui.DrawTex(pX, pY,
      cmb_scr_left + byte(col2d_PointInRect(mouse_X, mouse_Y, gui.getRect(pX, pY, cmb_scr_width, cmb_scr_height)) and mouse_Down(M_BLEFT))
        * cmb_scr_width,
      cmb_scr_top, cmb_scr_width, cmb_scr_height);
    pY := rct.Y + rct.H - cmb_scr_height;
    gui.DrawTex(pX, pY,
      cmb_scr2_left + byte(col2d_PointInRect(mouse_X, mouse_Y, gui.getRect(pX, pY, cmb_scr_width, cmb_scr_height)) and mouse_Down(M_BLEFT))
        * cmb_scr_width,
      cmb_scr2_top, cmb_scr_width, cmb_scr_height);
    pY := rct.Y + cmb_scr_height + fdOffset;
    gui.DrawTex(pX, pY,
      cmb_scf_left + byte(evnt = heComboMove)
        * cmb_scf_width,
      cmb_scf_top, cmb_scf_width, cmb_scf_height);
    r.W := r.W - cmb_scr_width;

  end;

  for i := 0 to ic - 1 do begin
    b := col2d_PointInRect(mouse_X, mouse_Y, r) and (evnt = heComboField);
    if b or (i + fOffset = Selected) then begin
      gui.DrawField(r.X, r.Y, r.W, r.H, cmb_itm_left, cmb_itm_top + Byte(not b)*cmb_itm_height*2, cmb_itm_width, cmb_itm_height);
    end;
    fnt := getRootFont(i + fOffset = Selected);
    text_DrawInRectEx(fnt.Data, r, fnt.Size, 0, Items[i + fOffset], fnt.Alpha, fnt.Color, fnt.Align);
    r.Y := r.Y + cmb_itm_height * 2;
  end;
end;

procedure zglTComboBox.HandleLeave;
begin
  //
end;
                     
procedure zglTComboBox.HandleUpdate;
var rct, r: zglTRect;
    ic: integer;
    pY: single;
    mU, mD: Boolean;
begin
  rct := updateRect(fRect);
  rct.Y := rct.Y + rct.H;
  ic := Items.Count;
  if ic > ComboMax then begin
    ic := ComboMax;
    rct.W := rct.W - cmb_scr_width;
    r.Y := rct.Y;
    r.X := rct.X + rct.W;
    r.W := cmb_scr_width;
    r.H := ComboMax * cmb_itm_height * 2;
    pY := rct.Y + cmb_scr_height + fdOffset;
    mU := mouse_Wheel(M_WUP);
    mD := mouse_Wheel(M_WDOWN);
    if col2d_PointInRect(mouse_X, mouse_Y, r) or mU or mD then begin
      if mouse_Click(M_BLEFT) or mU or mD then begin
        if (mouse_Y - r.Y < cmb_scr_height) or mU then begin
          Dec(fOffset);
          if fOffset < 0 then
            fOffset := 0;
        end else
        if (r.Y + r.H - mouse_Y  < cmb_scr_height) or mD then begin
          Inc(fOffset);
          if fOffset > Items.Count - ComboMax then
            fOffset := Items.Count - ComboMax;
        end else
        if (mouse_Y >= pY) and (mouse_Y <= pY + cmb_scf_height) then begin
          fsOffset := mouse_Y - Round(pY);
          gui.Handler.HandleEvent(self, heComboMove, false);
        end else begin
          fOffset := Trunc(((mouse_Y - r.Y) / (r.H - cmb_scr_height*2 - cmb_scf_height)) * (Items.Count - ComboMax ));
        end;
        fdOffset := Round((fOffset / (Items.Count - ComboMax )) * (r.H - cmb_scr_height*2 - cmb_scf_height));
        if CatchMouseClick then
          mouse_ClearState;
        exit;
      end;
    end;
  end;
  rct.H := ic * cmb_itm_height * 2;
  if mouse_Click(M_BLEFT) then begin
    if col2d_PointInRect(mouse_X, mouse_Y, rct) then begin
      Selected := trunc((mouse_Y - rct.Y) / (2*cmb_itm_height)) + fOffset;
      if CatchMouseClick then
        mouse_ClearState;
    end;
    Gui.Handler.Clear;
  end;
  case evnt of
    heComboMove: begin
      fdOffset := round(mouse_Y - rct.Y - cmb_scr_height - fsOffset);
      if fdOffset < 0 then fdOffset := 0;
      if fdOffset > rct.H - cmb_scr_height*2 - cmb_scf_height then fdOffset := round(rct.H - cmb_scr_height*2 - cmb_scf_height);
      fOffset := Trunc((fdOffset / (rct.H - cmb_scr_height*2 - cmb_scf_height)) * (Items.Count - ComboMax ));
      if not mouse_Down(M_BLEFT) then begin
        gui.Handler.HandleEvent(self, heComboField, false);
      end;
    end;
  end;
end;

// zglTImageButton

procedure zglTImageButton.Draw;
var fnt: zglTFontObject;
    rct: zglTRect;
    i: integer;
begin
  if not Visible then Exit;
  rct := updateRect(fRect);
  i := Byte(fPressed)*2 + Byte(fHover and (not fPressed));
  asprite2d_Draw(fTexture, rct.X, rct.Y, rct.W, rct.H, 0, i + 1);
end;

procedure zglTImageButton.Update;
begin
  if not Visible then Exit;
  inherited;
end;

constructor zglTImageButton.Create;
begin
  inherited Create(pGui, pX, pY, pW, pH);
  fTexture := pTexture;
  fTexWidth := texWidth;
  fTexHeight := texHeight;
  tex_SetFrameSize(fTexture, round(texWidth), round(texHeight));
end;

// zglTButton

procedure zglTButton.Draw;
var fnt: zglTFontObject;
    rct: zglTRect;
    i: integer;
begin
  if not Visible then Exit;
  rct := updateRect(fRect);
  i := Byte(fPressed)*2 + Byte(fHover and (not fPressed));
  Gui.DrawField(rct.X, rct.Y, rct.W, rct.H,
    btn_left, btn_top + i * btn_height * 2, btn_width, btn_height);
  fnt := getRootFont(fHover);
  if fPressed then begin
    rct.X := rct.X + 1; rct.Y := rct.Y + 1;
  end;
  text_DrawInRectEx(fnt.Data, rct, fnt.Size, 0, Caption, fnt.Alpha, fnt.Color, fnt.Align);
end;

procedure zglTButton.Update;
begin
  if not Visible then Exit;
  inherited;
end;

constructor zglTButton.Create;
begin
  inherited Create(pGui, pX, pY, pW, pH);
  Caption := pCaption;
end;

// zglTCheckBox

constructor zglTCheckBox.Create;
begin
  inherited Create(pGui, pX, pY, pW, pH);
  fChecked := false;
  Caption := pCaption;
end;

procedure zglTCheckBox.Draw;
var
    rct: zglTRect;
    fnt: zglTFontObject;
    i: integer;
begin
  if not Visible then Exit;
  rct := updateRect(fRect);
  i := 2 * Byte(fHover xor fPressed) + Byte(not Checked);
  fnt := getRootFont(fHover);
  Gui.DrawTex(rct.X, rct.Y, cx_left, cx_top + i* cx_height, cx_width, cx_height);
  rct.X := rct.X + cx_width; rct.W := rct.W - cx_width;
  if fPressed then begin
    rct.X := rct.X + 1; rct.Y := rct.Y + 1;
  end;
  text_DrawInRectEx(fnt.Data, rct, fnt.Size, 0, Caption, fnt.Alpha, fnt.Color, TEXT_LEFT);
end;

procedure zglTCheckBox.Update;
begin
  if not Visible then Exit;
  inherited;
  if fJustPressed then begin
    Checked := not Checked;
  end;
end;

// zglTRadioButton

constructor zglTRadioButton.Create;
begin
  inherited Create(pGui, pX, pY, pW, pH);
  Caption := pCaption;
end;

procedure zglTRadioButton.Draw;
var
    rct: zglTRect;
    fnt: zglTFontObject;
    i: integer;
begin
  if not Visible then Exit;
  rct := updateRect(fRect);
  i := 2 * Byte(fHover xor fPressed) + Byte((Parent as zglTRadioBox).Selected <> Self);
  fnt := getRootFont(fHover);
  Gui.DrawTex(rct.X, rct.Y, rd_left, rd_top + i* rd_height, rd_width, rd_height);
  rct.X := rct.X + cx_width; rct.W := rct.W - cx_width;
  if fPressed then begin
    rct.X := rct.X + 1; rct.Y := rct.Y + 1;
  end;
  text_DrawInRectEx(fnt.Data, rct, fnt.Size, 0, Caption, fnt.Alpha, fnt.Color, TEXT_LEFT);
end;

procedure zglTRadioButton.Update;
begin
  inherited;
  if fJustPressed then begin
    if Parent.ClassType = zglTRadioBox then begin
      (Parent as zglTRadioBox).Select(Self);
    end;
  end;
end;

// zglTRadioBox

constructor zglTRadioBox.Create;
begin
  inherited Create(pGui, pX, pY, pW, pH);
  fSelected := nil;
end;

procedure zglTRadioBox.Draw;
begin
  if not Visible then Exit;
  inherited;
end;

procedure zglTRadioBox.Select;
begin
  fSelected := zglTRadioButton(pI);
end;

procedure zglTRadioBox.Update;
begin
  if not Visible then Exit;
  inherited;
end;

// zglTFrame

constructor zglTFrame.Create;
begin
  inherited Create(pGui, pX, pY, pW, pH);
  ScrollTo(0, 0);
  with fBorder do begin
    X := 0; Y := 0; W := 0; H := 0;
  end;
  UpdateClientRect;
  Objects := TList.Create;
  canMove := false;
  fScrollBars := SCROLL_BOTH;
end;

constructor zglTFrame.CreateFullScreen;
begin
  inherited Create(pGui, 0, 0, pGui.fRect.W, pGui.fRect.H);
  ScrollTo(0, 0);
  with fBorder do begin
    X := 0; Y := 0; W := 0; H := 0;
  end;
  UpdateClientRect;
  Objects := TList.Create;
  canMove := false;
  fScrollBars := SCROLL_BOTH;
end;

procedure zglTFrame.FullScreen;
begin
  Resize(gui.fRect.W, gui.fRect.H);
end;

procedure zglTFrame.HandleDraw;
begin
  //
end;

procedure zglTFrame.HandleLeave;
begin
  //
end;

procedure zglTFrame.HandleUpdate;
begin
  case evnt of
    heMoveFrame: begin
      fRect.X := mouse_X - fOffsetX;
      fRect.Y := mouse_Y - fOffsetY;
      if not mouse_Down(M_BLEFT) then begin
        Gui.Handler.Clear;
      end;
    end;
  end;
end;

destructor zglTFrame.Destroy;
begin
  Objects.Free;
  inherited;
end;

function zglTFrame.Add;
begin
  Objects.Add(obj);
  obj.Parent := Self;
  result := obj;
end;

procedure zglTFrame.Remove;
begin
  Objects.Remove(obj)
end;

procedure zglTFrame.Resize;
begin
  inherited;
  UpdateClientRect;
  if Assigned(fOnResize) then
    fOnResize(self);
end;

procedure zglTFrame.MoveToCenter;
begin
  fRect.X := gui.fRect.W / 2 - Rect.W / 2;
  fRect.Y := gui.fRect.H / 2 - Rect.H / 2;
end;

procedure zglTFrame.Draw;
var i: integer;
    r: zglTRect;
begin
  if not Visible then Exit;
  r := updateRect(fRect);
  scissor_Begin(round(r.X + ClientRect.X),
                round(r.Y + ClientRect.Y),
                round(ClientRect.W), round(ClientRect.H));
  for i := 0 to Objects.Count - 1 do
      zglTGUIObject(Objects.Items[i]).Draw;
  scissor_End;
end;

procedure zglTFrame.Update;
var i: integer;
    r, fUpdateRect: zglTRect;
begin
  if not Visible then Exit;
  r := updateRect(fRect);
  fUpdateRect := Gui.GetRect(r.X + ClientRect.X, r.Y + ClientRect.Y,
                     ClientRect.W, ClientRect.H);
  for i := 0 to Objects.Count - 1 do begin
    zglTGUIObject(Objects.Items[i]).Update(dt, fUpdateRect);
    if zglTGUIObject(Objects.Items[i]).fHover and zglTGUIObject(Objects.Items[i]).CatchMouseClick then
      lockHover := True;
  end;
    inherited;
  if Assigned(Gui) then
    if fJustPressed and CanMove then begin
      fOffsetX := mouse_X - round(Rect.X);
      fOffsetY := mouse_Y - round(Rect.Y);
      Gui.Handler.HandleEvent(Self, heMoveFrame, false);
      if fCatchMouseClick then
        mouse_ClearState;
    end;

end;

procedure zglTFrame.UpdateClientRect;
begin
  fClientRect.X := fBorder.X;
  fClientRect.Y := fBorder.Y;
  fClientRect.W := fRect.W - fBorder.X - fBorder.W;
  fClientRect.H := fRect.H - fBorder.Y - fBorder.H;
end;

procedure zglTFrame.ScrollTo;
begin
  fScroll.X := pX; fScroll.Y := pY;
end;

// zglTPageControlSheet

constructor zglTPageControlSheet.Create;
begin
  PageControl := pc;
  inherited Create(pGui, 0, 0,
                         PageControl.Rect.W, PageControl.Rect.H);
  Visible := true;
  fButton := zglTButton.Create(pGui, 0, 0, 0, 0, pCaption);
  PageControl.Add(fButton);
  with fBorder do begin
    X := 0; Y := pcb_height; W := 0; H := 0;
  end;
  UpdateClientRect;
end;

procedure zglTPageControlSheet.Draw;
begin
  inherited;
end;

procedure zglTPageControlSheet.Update;
begin
  if not Visible then Exit;
  inherited;
end;

destructor zglTPageControlSheet.Destroy;
begin
  inherited;
end;

// zglTPageControl

constructor zglTPageControl.Create;
begin
  inherited;
  fSheets := TList.Create;
  CatchMouseClick := false;
  with fBorder do begin
    X := 0; Y := 0; W := 0; H := 0;
  end;
  UpdateClientRect;
end;

procedure zglTPageControl.Draw;
var rct: zglTRect;
    i, j, px, l: integer;
    sht: zglTPageControlSheet;
    fnt: zglTFontObject;
begin
  if not Visible then Exit;
  rct := updateRect(fRect);
  gui.DrawField(rct.X, rct.Y + pcb_height, rct.W, rct.H - pcb_height,
    pc_left, pc_top, pc_width, pc_height);
  if fSheets.Count > 0 then begin
    zglTPageControlSheet(fSheets.Items[fSelected]).Draw;
  end;
  inherited;
end;

procedure zglTPageControl.Update;
var rct, r: zglTRect;
    i, px, l: integer;
    sht: zglTPageControlSheet;
    fnt: zglTFontObject;
begin
  if not Visible then Exit;
  inherited; 
  if fSheets.Count > 0 then begin
    sht := zglTPageControlSheet(fSheets.Items[fSelected]);
    sht.fButton.fPressed := True;
    sht.fButton.fHover := True;
    sht.Update(dt, clntRect);
  end;
end;

function zglTPageControl.AddSheet;
var Sht: zglTPageControlSheet;
begin
  Sht := zglTPageControlSheet.Create(Gui, self, pCaption);
  Sht.Parent := self;
  fSheets.Add(Sht);

  Sht.fButton.Tag := fSheets.IndexOf(Sht);
  Sht.fButton.OnClick := @OnPageControlButtonClick;
  result := Sht;
  RelocateButtons;
end;

procedure zglTPageControl.Resize;
var i: Integer;
begin
   for i := 0 to fSheets.Count - 1 do begin
    zglTPageControlSheet(fSheets.Items[i]).Resize(pW, pH - pcb_height);
  end;
  inherited;
end;

function zglTPageControl.getSheet;
begin
  result := zglTPageControlSheet(fSheets.Items[Index]);
end;

procedure zglTPageControl.setSelected;
begin
  fSelected := Index;
  if fSelected > fSheets.Count - 1 then
    fSelected := fSheets.Count - 1;
end;

procedure zglTPageControl.RelocateButtons;
var i, px, l: integer;
    sht: zglTPageControlSheet;
    fnt: zglTFontObject;
begin

  px := 0;
  fnt := getRootFont(false);
  for i := 0 to fSheets.Count - 1 do begin
    sht := zglTPageControlSheet(fSheets.Items[i]);    
    sht.fButton.Move(px, 0);
    l := Round( text_GetWidth(fnt.Data, sht.fButton.fCaption) * fnt.Size ) + pcb_drawoffset * 2;
    sht.fButton.Resize(l, pcb_height);
    Inc(px, l);
  end;
end;


procedure zglTPageControl.DeleteSheet;
begin
  zglTPageControlSheet(fSheets.Items[id]).Free;
  fSheets.Delete(id);
end;

destructor zglTPageControl.Destroy;
var i: integer;
begin
  for i := 0 to fSheets.Count -1 do
    zglTPageControlSheet(fSheets.Items[i]).Free;
  fSheets.Free;
  inherited;
end;


// zglTScrollBox

constructor zglTScrollBox.Create;
begin
  inherited;
end;

procedure zglTScrollBox.Draw;
begin
  inherited;
end;

procedure zglTScrollBox.Update;
begin
  if not Visible then Exit;
  inherited;
end;

// zglTForm

constructor zglTForm.Create;
begin
  inherited Create(pGui, pX, pY, pW, pH);
  Objects := TList.Create;
  Caption := pCaption;
  canMove := false;
  with fBorder do begin
    X := frm_offset; Y := frm_offset_capt; W := frm_offset; H := frm_offset;
  end;
  UpdateClientRect;
end;

destructor zglTForm.Destroy;
begin
  inherited;
end;

procedure zglTForm.Draw;
var
    r: zglTRect;
    fnt: zglTFontObject;
begin
  if not Visible then Exit;
  r := updateRect(fRect);
  Gui.DrawField(r.X, r.Y, r.W, r.H, frm_left, frm_top, frm_width, frm_height);
  fnt := getRootFont(false);
  inherited;
  r.H := frm_height; r.X := r.X + frm_offsetx; r.W := r.W - frm_offsetx; r.Y := r.Y + frm_offsety; 
  text_DrawInRectEx(fnt.Data, r, fnt.Size, 0, Caption, fnt.Alpha, fnt.Color, TEXT_LEFT);
end;

procedure zglTForm.Update;
begin
  if not Visible then Exit;
  inherited;
  if fRect.X < 0 then fRect.X := 0;
  if fRect.Y < 0 then fRect.Y := 0;
  if fRect.X + fRect.W > Gui.fRect.W then fRect.X := Gui.fRect.W - fRect.W;
  if fRect.Y + fRect.H > Gui.fRect.H then fRect.Y := Gui.fRect.H - fRect.H;
end;

// FONT

constructor zglTFontContainer.Create(pNormal, pActive: zglTFontObject);
begin
  Normal := pNormal;
  Active := pActive;
end;

constructor zglTFontContainer.Create(pData: zglPFont; pSize: Single; pColor: integer; pAlpha: Byte; pAlign: Integer = TEXT_CENTER);
begin
  Normal := zglTFontObject.Create(pData, pSize, pColor, pAlpha, pAlign);
  Active := Normal;
end;

// zglTHandler

constructor zglTHandler.Create;
begin
  hHandle := heNone;
  canHover := true;
end;

procedure zglTHandler.HandleEvent;
begin
  if hObject <> nil then begin
    hObject.HandleLeave(hHandle);
  end;
  hHandle := evnt; hObject := obj;
  canHover := pCanHover;
end;

procedure zglTHandler.Clear;
begin
  if hObject <> nil then begin
    hObject.HandleLeave(hHandle);
  end;
  hObject := nil; hHandle := heNone;
  canHover := True;
end;

procedure zglTHandler.Draw;
begin
  if Assigned(hObject) then begin
    hObject.HandleDraw(hHandle);
  end;
end;


procedure zglTHandler.Update;
begin
  if Assigned(hObject) then begin 
    hObject.HandleUpdate(dt, hHandle);
  end;
end;

// GUI     

constructor zglTGui.Create;
begin
  Handler := zglTHandler.Create;
  Handler.gui := self;
  Objects := TList.Create;
  fVisible := True;
  fRect := getRect(0, 0, screenWidth, screenHeight);
  tex := tex_LoadFromFile(dir + '/Gui.tga', $FF000000,
    TEX_CLAMP or TEX_FILTER_LINEAR or TEX_CONVERT_TO_POT or TEX_CALCULATE_ALPHA);
  grid.Cols := 4; grid.Rows := 4;         
  SetLength(grid.Grid, 4, 4);
end;

procedure zglTGui.Resize;
begin
  fRect.W := screenWidth;
  fRect.H := screenHeight;
  if Assigned(fOnResize) then
    fOnResize(self);
end;

destructor zglTGui.Destroy;
begin
  Objects.Free;
  inherited;
end;

procedure zglTGui.Draw;
var i: integer;
begin
  if not Visible then Exit;
  for i := 0 to Objects.Count - 1 do
    zglTGUIObject(Objects.Items[i]).Draw;
  Handler.Draw;
end;

procedure zglTGui.Update;
var i: integer;
begin
  if not Visible then Exit;
  Handler.Update(dt);
  for i := 0 to Objects.Count - 1 do
    zglTGUIObject(Objects.Items[i]).Update(dt, fRect);
end;

procedure zglTGui.Add;
begin
  Objects.Add(obj);
  Obj.Gui := self;
end;

procedure zglTGui.Remove;
begin
  Objects.Remove(obj)
end;

procedure zglTGui.DrawTex;
var rct: zglTRect;
begin
  rct.X := dX; rct.Y := dY; rct.W := dW; rct.H := dH;
  csprite2d_Draw(tex, X, Y, dW, dH, 0, rct);
end;

function zglTGui.getPoint(X, Y: single):zglTPoint2D;
begin
  result.X := X;
  result.Y := Y;
end;

function zglTGui.getRect(X, Y, W, H: single):zglTRect;
begin
  result.X := X;
  result.Y := Y;
  result.W := W;
  result.H := H;
end;

procedure zglTGui.DrawField;
var rct: zglTRect;
    WdW, HdH: Single;
begin
  rct.X := dX; rct.Y := dY; rct.W := dW; rct.H := dH;
  WdW := W - dW;
  HdH := H - dH;


  csprite2d_Draw(tex, X, Y, dW, dH, 0, rct);
  rct.X := dX + dW;
  csprite2d_Draw(tex, X + WdW, Y, dW, dH, 0, rct);
  rct.Y := dY + dH;
  csprite2d_Draw(tex, X + WdW, Y + HdH, dW, dH, 0, rct);
  rct.X := dX;
  csprite2d_Draw(tex, X, Y + HdH, dW, dH, 0, rct);
  rct.X := dX + dW; rct.Y := dY; rct.W := 0; rct.H := dH;
  csprite2d_Draw(tex, X + dW, Y, W - dW*2, dH, 0, rct);
  rct.Y := dY + dH;
  csprite2d_Draw(tex, X + dW, Y + HdH, W - dW*2, dH, 0, rct);
  rct.H := 0;
  csprite2d_Draw(tex, X + dW, Y + dH, W - dW*2, H - dH*2, 0, rct);
  rct.W := dW; rct.X := dX;
  csprite2d_Draw(tex, X, Y + dH, dW, H - dH*2, 0, rct);
  rct.X := dX + dW;
  csprite2d_Draw(tex, X + WdW, Y + dH, dW, H - dH*2, 0, rct);
end;






end.
