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 = 6; frm_offset_capt = 17;
  frm_offsetx = 8; frm_offsety = -6;
  frm_scrollcolor = $99FF99;
  frm_scrollbtn_width = 8;
  frm_scrollbar_width = 16;

  frm_scleft_left = 64; frm_scleft_top = 96;
    frm_btleft_left = 96; frm_btleft_top = 96;
  frm_sctop_left = 32; rrm_sctop_top = 96;
    frm_bttop_left = 32; frm_bttop_top = 128;
  // BUTTON
  btn_left = 0; btn_top = 0; btn_width = 16; btn_height = 8;
  // PROGRESSBAR
  pbar_offsetx = 3; pbar_offsety = 0;
  pbar_left = 176; pbar_top = 48; pbar_width = 4; pbar_height = 8;
  pbdata_left = 184; pbdata_top = 48; pbdata_width = 1; pbdata_height = 8;
  // TRACKBAR
  trck_left = 160; trck_top = 32; trck_width = 16; trck_height = 16;
  // 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 = 5; pcb_height = 20; pcb_heightd2 = 11; pcb_rect = 4;
  pc_left = 0; pc_top = 128; pc_width = 16; pc_height = 16;   
  pcs_left = 160; pcs_top = 0; pcs_width = 8; pcs_height = 8;
  img_width = 16; img_height = 16;
  // MAINMENU
  mm_defheight = 16; mm_margin = 4; mm_voffset = 2;  
  mm_left = 128; mm_top = 64; mm_width = 8; mm_height = 8;
  mm_itm_left = 128; mm_itm_top = 80; mm_itm_width = 8; mm_itm_height = 8;
  mm_frm_left = 144; mm_frm_top = 64; mm_frm_width = 8; mm_frm_height = 8;
  // EDITMODE
  em_left = 108; em_top = 92; em_width = 2; em_height = 2;

type

  TCaption = String;
  THandleEvent = (heNone, heMoveObject, heResizeObject, heInputText,
                  heComboField, heComboMove, hePopup, heTrackBar);
  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
    public
      Normal : zglTFontObject;
      Active: zglTFontObject;
      function GetForState(state: Boolean): zglTFontObject;
      constructor Create(pNormal, pActive: zglTFontObject); overload;
      constructor Create(pData: zglPFont; pSize: Single; pColor: integer;
        pAlpha: Byte; pAlign: Integer = TEXT_CENTER); overload;
      destructor Destroy; override;
  end;

  zglTGui = class;
  zglTFrame = class;
  zglTGUIObject = class;
  zglTMainMenuItem = class;
  zglTPopupMenu = class;
  zglTMenuItem = class;

  zglTEvent = procedure (Sender: zglTGUIObject);
  zglTMMEvent = procedure (SelectedElement: zglTMainMenuItem);
  zglTMenuEvent = procedure (Sender: zglTGUIObject;
    SelectedElement: zglTMenuItem; X, Y: integer);
  zglTGuiEvent = procedure (Sender: zglTGui);
  zglTMouseEvent = procedure (Sender: zglTGUIObject; X, Y: integer);

  // объект, который может быть невидимым
  zglTBaseGUIObject = class(TObject)
    private
      Gui: zglTGui;
      fFont: zglTFontContainer;
      function getFont: zglTFontContainer;
      procedure HandleDraw(evnt: THandleEvent); virtual;
      function HandleUpdate(dt: Double; evnt: THandleEvent): boolean; virtual;
      procedure HandleLeave(evnt: THandleEvent); virtual;
    public
      constructor Create(pGui: zglTGui);        
      property Font: zglTFontContainer read getFont write fFont;
  end;

  // базовый объект
  zglTGUIObject = class(zglTBaseGUIObject)
    private
      fJustPressed, fNeedScroll, fPressed,
        fHover, fVisible, fCatchMouseClick, fEditMode,
        fEvenNotMove, fContainer: Boolean;
      fParent: zglTFrame;
      fCaption, fName: TCaption;
      fData: pointer;
      fMouse_X, fMouse_Y, fOffsetX, fOffsetY,
        fTag, fCorner: integer;
      fDtX, fDtY: single;
      fRootPos: zglTPoint2D;
      fRect: zglTRect;
      fPopupMenu: zglTPopupMenu;
      fOnResize, fOnMove: zglTEvent;
      fOnClick, fOnMouseEnter, fOnMouseLeave,
        fOnMouseMove: zglTMouseEvent;

      function updateRect(oX: single = 0; oY: single = 0):zglTRect;
      function isHover(upr: zglTRect; oX: single = 0; oY: single = 0): boolean;
      function updateOffset(r:zglTRect;
        offsetX: single = 0; offsetY: single = 0):zglTRect;
      function getRootFont (pActive: Boolean): zglTFontObject;
      function HandleUpdate(dt: Double; evnt: THandleEvent): boolean; override;

      procedure setCaption(capt: TCaption); virtual;
      procedure setName(capt: TCaption);
      procedure DrawEdit(oX, oY: single); virtual;
    public
      (* Прорисовать объект
        clntRect - область прорисовки, если компонент не попадет в нее то он
          будет отсечен.
        oX, oY - относительные координаты
      *)
      procedure Draw(clntRect: zglTRect; oX, oY: single); virtual; abstract;
      (* Обновить объект
        dt - коэффициент запаздывания
        clntRect - область обновления, если компонент не попадет в нее то он
          будет отсечен.
        updateChind - обновлять ли дочерние элементы
        oX, oY - относительные координаты
      *)
      procedure Update(dt: Double; clntRect: zglTRect; updateChind: boolean;
        oX, oY: single); virtual;
      (* Конструктор
        pGui - объект гуя
        pX, pY, pW, pH - координаты и размеры
        pVisible - видимость
      *)
      constructor Create(pGui: zglTGui; pX, pY, pW, pH: Single;
        pVisible: Boolean = true);

      // Нажата ли кнопка мыши на этом объекте только-что
      property JustPressed: boolean read fJustPressed;
      // Является ли объект контейнером для других объектов
      property Container: boolean read fContainer;
      // Находтся ли мышь над эти объектом
      property Hover: Boolean read fHover write fHover;
      // Видимый ли объект
      property Visible: Boolean read fVisible write fVisible;
      // Заголовок (то бишь Текст, Надпись etc)
      property Caption: TCaption read fCaption write setCaption;
      // Имя (для системных нужд)
      property Name: TCaption read fName write setName;
      // Событие при клике
      property OnClick: zglTMouseEvent read fOnClick write fOnClick;
      // Событие при перемещении
      property OnMove: zglTEvent read fOnMove write fOnMove;
      // Событие при имзменении размера
      property OnResize: zglTEvent read fOnResize write fOnResize;
      // Событие попадания мыши на объект
      property OnMouseEnter: zglTMouseEvent
        read fOnMouseEnter write fOnMouseEnter;
      // Событие покидания мыши объекта
      property OnMouseLeave: zglTMouseEvent
        read fOnMouseLeave write fOnMouseLeave;
      // Событие передвижения мыши по объекту
      property OnMouseMove: zglTMouseEvent
        read fOnMouseMove write fOnMouseMove;
      // Родительский объект (если есть, иначе = nil)
      property Parent: zglTFrame read fParent write fParent;
      // Абсолютные координаты объекта
      //  (ведь обычные - относительно родительского)
      property RootPos: zglTPoint2d read fRootPos;
      // Ловить ли клик мыши. То есть если по объекту кликнуть средующий на ним
      //   объект (или его родительский) не сможет обработать клик
      property CatchMouseClick: boolean
        read fCatchMouseClick write fCatchMouseClick;
      // Выпадающее меню для объекта. Если задать и нажать по объекту ПКМ то
      //   выпадет соотв. меню
      property PopupMenu: zglTPopupMenu read fPopupMenu write fPopupMenu;
      // Режим редактирования. Если включить то объект можно будет двигать
      property EditMode: boolean read fEditMode write fEditMode;
      // Координаты и размеры объекта
      property Rect: zglTRect read fRect write fRect;
      // Любое нужное для вас значение
      property Tag: integer read fTag write fTag;
      // Любое нужное для вас значение
      property Data: pointer read fData write fData;
      // Передвинуть объект
      procedure Move (pX, pY: Single);
      // Изменить размеры (вызовет OnResize)
      procedure Resize (pW, pH: Single); virtual;
      // Показать объект
      procedure Show; virtual;
      // Спрятать объект
      procedure Hide; virtual;
  end;

  // Класс-список. Его используют: zglTFrame и его производные и zglTGUI
  zglTGUIObjectsList = class
    private
      fGui: zglTGUI;
      fMaster: zglTFrame;
      fObjects: TList;
      function getObject(Index: Integer):zglTGUIObject;
      function getObjectsCount:integer;
      constructor Create(pGui: zglTGUI; pMaster: zglTFrame);
    public
      // Элементы списка
      property Items[Index: integer]:zglTGUIObject read getObject;
      // Количество элементов
      property Count:integer read getObjectsCount;
      // Добавить элемент в список
      function Add(obj: zglTGUIObject): zglTGUIObject;
      // Удалить элемент из списка
      procedure Remove(obj: zglTGUIObject);
      // Удалить все эелементы из списка
      procedure RemoveAll;
      // Передвинуть заданный элемент в конец списока (будет вверху)
      procedure MoveToTop(obj: zglTGUIObject);
      // Передвинуть заданный элемент в начало списка (будет внизу)
      procedure MoveToBottom(obj: zglTGUIObject);
      // Удалить класс
      destructor Destroy; override;
  end;

  // Метка
  zglTLabel = class(zglTGUIObject)
    public
      constructor Create(pGui: zglTGui; pX, pY, pW, pH: Single;
        pCaption: TCaption; pVisible: Boolean = true);
      procedure Draw(clntRect: zglTRect; oX, oY: single); override;
      procedure Update(dt: Double; clntRect: zglTRect; updateChind: boolean;
        oX, oY: single); override;
  end;
  // Кнопка
  zglTButton = class(zglTGUIObject)
    public
      procedure Draw(clntRect: zglTRect; oX, oY: single); override;
      procedure Update(dt: Double; clntRect: zglTRect; updateChind: boolean;
        oX, oY: single); override;
      constructor Create(pGui: zglTGui; pX, pY, pW, pH: Single;
        pCaption: TCaption; pVisible: Boolean = true);
  end;
  // Прогрессбар
  zglTProgressBar = class(zglTGUIObject)
    private
      fProgress, fLastProgress, fMax, fMin: integer;
      fOnChange: zglTEvent;
      procedure setProgress(progress: Integer);
    public
      // Текущий прогресс, = Min ... Max
      property Progress: Integer read fProgress write setProgress;
      // Максимально возможный прогресс. По-умолчанию: 100
      property Max: Integer read fMax write fMax;
      // Минимально возможный прогресс. По-уполчанию: 0
      property Min: Integer read fMin write fMin;
      // Событие по изменению прогресса
      property OnChange: zglTEvent read fOnChange write fOnChange;
      procedure Draw(clntRect: zglTRect; oX, oY: single); override;
      procedure Update(dt: Double; clntRect: zglTRect; updateChind: boolean;
        oX, oY: single); override;
      constructor Create(pGui: zglTGui; pX, pY, pW, pH: Single;
        pVisible: Boolean = true);
  end;
  // Тракбар
  zglTTrackBar = class(zglTProgressBar)
    private
      procedure HandleDraw(evnt: THandleEvent); override;
      function HandleUpdate(dt: Double; evnt: THandleEvent): boolean; override;
      procedure HandleLeave(evnt: THandleEvent); override;
    public
      procedure Draw(clntRect: zglTRect; oX, oY: single); override;
      procedure Update(dt: Double; clntRect: zglTRect; updateChind: boolean;
        oX, oY: single); override;
      constructor Create(pGui: zglTGui; pX, pY, pW, pH: Single;
        pVisible: Boolean = true);
  end;
  // Кнопка - картинка
  zglTImageButton = class(zglTGUIObject)
    private
      fTexture: zglPTexture;
      fTexWidth, fTexHeight, fTextureOffset: Word;
    public
      // Текстура кнопки-картинки. Представляеть из себя трехкадровый спрайт
      //   размером texHeight x texHeight, где
      //   1 кадр - обычное состояние
      //   2 кадр - наведенная мышка
      //   3 кадр - нажатая на объекте кнопка мыши
      property Texture: zglPTexture read fTexture write fTexture;
      procedure Draw(clntRect: zglTRect; oX, oY: single); override;
      procedure Update(dt: Double; clntRect: zglTRect; updateChind: boolean;
        oX, oY: single); override;
      constructor Create(pGui: zglTGui; pX, pY, pW, pH: Single; texWidth,
        texHeight: word; pTexture: zglPTexture; pTextureOffset: Word = 0;
        pVisible: Boolean = true);
  end;
  // поле ввода
  zglTEdit = class(zglTGUIObject)
    private
      fAlign, fMaxLength: integer;
      fUpd: single;
      fPasswordChar: Char;
      fLastVal: TCaption;
      fOnChange: zglTEvent;
      procedure setCaption(capt: TCaption); override;
      procedure HandleDraw(evnt: THandleEvent); override;
      function HandleUpdate(dt: Double; evnt: THandleEvent): boolean; override;
      procedure HandleLeave(evnt: THandleEvent); override;
    public
      procedure Draw(clntRect: zglTRect; oX, oY: single); override;
      procedure Update(dt: Double; clntRect: zglTRect; updateChind: boolean;
        oX, oY: single); override;
      // Событие изменения текста
      property OnChange: zglTEvent read fOnChange write fOnChange;
      // Максимальное количество текста
      property MaxLength: integer read fMaxLength write fMaxLength;
      // Символ ввода пароля. Если <> #0 то будет отображаться вместо текста
      property PasswordChar: char read fPasswordChar write fPasswordChar;
      // Уклон текста: TEXT_LEFT, TEXT_CENTER, TEXT_RIGHT
      property Align: integer read fAlign write fAlign;
      constructor Create(pGui: zglTGui; pX, pY, pW, pH: Single;
        pCaption: TCaption; pAlign: Integer = TEXT_LEFT; pVisible: Boolean = true);
      // Сфокусироваться на этом элементе
      procedure Focus;
  end;
  // комбо-бокс: вырадающий список
  zglTComboBox = class(zglTGUIObject)
    private
      fSelected, fComboMax: Word;
      fOffset, fdOffset, fsOffset: Integer;
      fItems: TStringList;
      procedure HandleDraw(evnt: THandleEvent); override;
      function HandleUpdate(dt: Double; evnt: THandleEvent): boolean; override;
      procedure HandleLeave(evnt: THandleEvent); override;
    public
      // Элементы списка - обычные строки
      property Items: TStringList read fItems write fItems;
      // Выбранный элемент
      property Selected: Word read fSelected write fSelected;
      // Максимальное кол-во элементов, которое можнт быть отображено
      //  (иначе появится полоса прокрутки)
      property ComboMax: Word read fComboMax write fComboMax;
      procedure Draw(clntRect: zglTRect; oX, oY: single); override;
      procedure Update(dt: Double; clntRect: zglTRect; updateChind: boolean;
        oX, oY: single); override;
      constructor Create(pGui: zglTGui; pX, pY, pW: Single; pH: Single
        = cmb_itm_height*2 + 1; pComboMax: Word = 8; pVisible: Boolean = true);
      destructor Destroy; override;
  end;
  // Чекбокс
  zglTCheckBox = class(zglTGUIObject)
    private
      fChecked: Boolean;
    public
      // Отмечен ли чекбокс
      property Checked: Boolean read fChecked write fChecked;
      procedure Draw(clntRect: zglTRect; oX, oY: single); override;
      procedure Update(dt: Double; clntRect: zglTRect; updateChind: boolean;
        oX, oY: single); override;
      constructor Create(pGui: zglTGui; pX, pY, pW, pH: Single;
        pCaption: TCaption; pVisible: Boolean = true);
  end;
  // Радио-кнопка. Чтобы радио-кнопка работала, ее родителем должен быть
  //   zglTRadioBox
  zglTRadioButton = class(zglTGUIObject)
    public
      constructor Create(pGui: zglTGui; pX, pY, pW, pH: Single;
        pCaption: TCaption; pVisible: Boolean = true);
      procedure Draw(clntRect: zglTRect; oX, oY: single); override;
      procedure Update(dt: Double; clntRect: zglTRect; updateChind: boolean;
        oX, oY: single); override;
  end;
  // Пункт главного меню
  zglTMainMenuItem = class
    private
      Popup: zglTPopupMenu;
      Text: TCaption;
      X, Width: integer;
    public
      CallBack: zglTMMEvent;
      constructor Create(Text_: TCaption; Width_: integer;
        Popup_: zglTPopupMenu; CallBack_: zglTMMEvent = nil);
  end;
  // Главное меню
  zglTMainMenu = class (zglTGUIObject)
    private
      Items: TList;
      ContexItem: zglTMainMenuItem;
      procedure CalcOffsets;
    public
      procedure Draw(clntRect: zglTRect; oX, oY: single); override;
      procedure Update(dt: Double; clntRect: zglTRect; updateChind: boolean;
        oX, oY: single); override;
      // добавить элемент в меню
      procedure AddItem(Item: zglTMainMenuItem);
      // удалить элемент из меню
      procedure RemoveItem(Item: zglTMainMenuItem);
      destructor Destroy; override;
      constructor Create(pGui: zglTGui; pX, pY, pW: Single; pH: Single
        = mm_defheight; pVisible: Boolean = true); virtual;
  end;
  // Пункт выпадающего меню
  zglTMenuItem = class
    private
      fText: TCaption;
      fData: Pointer;
      fCallBack: zglTMenuEvent;
    public
      property Text: TCaption read fText write fText;
      property Data: Pointer read fData write fData;
      property CallBack: zglTMenuEvent read fCallBack write fCallBack;
      constructor Create(Text_: TCaption; CallBack_: zglTMenuEvent);
  end;
  // Выпадающее меню
  zglTPopupMenu = class (zglTBaseGUIObject)
    private
      fItems: TList;
      X, Y, Width, MinWidth: integer;
      Caller: zglTGUIObject;
      fOnPopup: zglTMouseEvent;
      procedure HandleDraw(evnt: THandleEvent); override;   
      function HandleUpdate(dt: Double; evnt: THandleEvent): boolean; override;
      procedure HandleLeave(evnt: THandleEvent); override;
      function getItem(index: Integer): zglTMenuItem;
      function getItemsCount: integer;
    public
      // элементы списка
      property Items[Index: integer]: zglTMenuItem read getItem;
      // количество элементов списка
      property ItemsCount: Integer read getItemsCount;
      // событие по выпаданию списка
      property OnPopup: zglTMouseEvent read fOnPopup write fOnPopup;
      // заставить список выпасть
      procedure Popup(Sender: zglTGUIObject; X_, Y_: Integer);
      // добавить элемент в список
      function AddItem(Item: zglTMenuItem): zglTPopupMenu;
      // удалить элемент из списка
      procedure RemoveItem(Item: zglTMenuItem);
      destructor Destroy; override;
      constructor Create(pGui: zglTGui; MinWidth_: integer); virtual;
  end;
  // фрейм (контейнер других объектов, все, что может кого-то содержать - дочь этого)
  zglTFrame = class(zglTGUIObject)
    private
      fItems: zglTGUIObjectsList;
      fCanMove: Boolean;
      fScrollBars, fActiveScrollBars: byte;
      fClientRect, fBorder: zglTRect;
      fScroll: zglTPoint2D;
      procedure UpdateClientRect;
      procedure HandleDraw(evnt: THandleEvent); override;
      function HandleUpdate(dt: Double; evnt: THandleEvent): boolean; override;
      procedure HandleLeave(evnt: THandleEvent); override;
      procedure onAdd(Sender: zglTGUIObject); virtual;
      procedure onRemove(Sender: zglTGUIObject); virtual;
    public
      // Дочерние элементы
      property Items:zglTGUIObjectsList read fItems;
      // Внутренние размеры
      property ClientRect: zglTRect read fClientRect write fClientRect;
      // Позиции прокрутки
      property Scroll: zglTPoint2D read fScroll write fScroll;
      // Может ли быть перемещен мышкой
      property CanMove: Boolean read fCanMove write fCanMove;
      // Показывать ли полосы прокрутки, если нужно:
      //   SCROLL_NONE = не нужно
      //   SCROLL_LEFT = только горизонтальную
      //   SCROLL_TOP =  только вертикальную
      //   SCROLL_BOTH = обе
      property ScrollBars: Byte read fScrollBars write fScrollBars;
      // Передвинуть на середину экрана
      procedure MoveToCenter;
      // Растянуть на весь экран
      procedure FullScreen;
      // Передвинуть прокрутку
      procedure ScrollTo (pX, pY: Single);
      // Создать таким образом, чтобы он был на весь экран
      constructor CreateFullScreen(pGui: zglTGui; pVisible: Boolean = true);
      procedure Resize (pW, pH: Single); override;
      procedure Draw(clntRect: zglTRect; oX, oY: single); override;
      procedure Update(dt: Double; clntRect: zglTRect; updateChind: boolean;
        oX, oY: single); override;
      constructor Create(pGui: zglTGui; pX, pY, pW, pH: Single;
        pVisible: Boolean = true);
      destructor Destroy; override;
  end;
  zglTPageControl = class;
  // Вкладки. Чтобы вкладки работали, родителем должен быть zglTPageControl.
  zglTPageControlSheet = class (zglTFrame)
    private
      fImage: zglPTexture;
      X, Width: integer;
      procedure setCaption(capt: TCaption); override;
      procedure setImage(tex: zglPTexture);
    public
      // Картинка к вкладке. Если <> Nil то мимо тексту будет нарисована
      //   и картинка
      property Image: zglPTexture read fImage write setImage;
      constructor Create(pGui: zglTGui; pCaption: TCaption;
        pVisible: Boolean = true; pImage: zglPTexture = nil);
      destructor Destroy; override;

      procedure Update(dt: Double; clntRect: zglTRect; updateChind: boolean;
        oX, oY: single); override;
      procedure Draw(clntRect: zglTRect; oX, oY: single); override;
      procedure Show; override;
      procedure Hide; override;
  end;
  // Контейнер вкладок
  zglTPageControl = class (zglTFrame)
    private
      fSelected: Word;
      function getSheet(Index: Integer):zglTPageControlSheet;
      procedure setSelected(Index: Word);
      procedure RelocateButtons;            
      procedure onAdd(Sender: zglTGUIObject); override;
      procedure onRemove(Sender: zglTGUIObject); override;
    public
      // Список вкладок (Обертка над Items, чтобы удалить, нужно удалять из
      //   Items)
      property Sheets[Index: integer]:zglTPageControlSheet read getSheet;
      // Номер выбранной вкладки
      property Selected:Word read fSelected write setSelected;
      procedure Resize (pW, pH: Single); override;
      procedure Draw(clntRect: zglTRect; oX, oY: single); override;
      procedure Update(dt: Double; clntRect: zglTRect; updateChind: boolean;
        oX, oY: single); override;
      constructor Create(pGui: zglTGui; pX, pY, pW, pH: Single;
        pVisible: Boolean = true);
      destructor Destroy; override;  
  end;
  // Контейнер радио-кнопок
  zglTRadioBox = class(zglTFrame)
    private
      fSelected: zglTRadioButton;
    public
      procedure Draw(clntRect: zglTRect; oX, oY: single); override;
      procedure Update(dt: Double; clntRect: zglTRect; updateChind: boolean;
        oX, oY: single); override;
      // Отмеченная радио-кнопка
      property Selected: zglTRadioButton read fSelected write fSelected;
      // Отметить какую-либо радио-кнопку
      procedure Select(pI: zglTGUIObject);
      constructor Create(pGui: zglTGui; pX, pY, pW, pH: Single;
        pVisible: Boolean = true);
  end;
  // ФОРМА
  zglTForm = class(zglTFrame)
    private
      fMainMenu: zglTMainMenu;
    public
      // Показать так, чтобы другие элементы стали неактивными
      procedure ShowModal;
      procedure Hide; override;
      procedure Draw(clntRect: zglTRect; oX, oY: single); override;
      procedure Update(dt: Double; clientRect: zglTRect; updateChind: boolean;
        oX, oY: single); override;
      constructor Create(pGui: zglTGui; pX, pY, pW, pH: Single;
        pCaption: TCaption; pVisible: Boolean = false);
      destructor Destroy; override;
  end;
  // обработчик всплывающих деталей
  zglTHandler = class
    private
      gui: zglTGui;
      hObject: zglTBaseGUIObject;
      hHandle: THandleEvent;
      canHover: boolean;
      procedure HandleEvent(obj: zglTBaseGUIObject; evnt: THandleEvent);
      procedure Update(dt: Double);
      procedure Draw;
      procedure Clear;
      constructor Create;
  end;
  // главный класс, рисует-обновляет все
  zglTGui = class
    private
      Handler: zglTHandler;
      fModal: zglTForm;
      fItems: zglTGUIObjectsList;
      fFont: zglTFontContainer;
      fVisible: Boolean;
      fTex: zglPTexture;
      grid: zglTGrid2D;
      fCounter: integer;
      fRect: zglTRect;
      fOnResize: zglTGuiEvent;
      fHover: zglTGUIObject;
      function getRect(X, Y, W, H: single):zglTRect;
      function getCounter: integer;
      procedure DrawTex(X, Y, dX, dY, dW, dH: Single);
      procedure DrawField(X, Y, W, H, dX, dY, dW, dH: Single);
      property Counter: integer read getCounter;
    public
      // Объект, над которым мышка в данный момент (иначе = nil)
      property Hover: zglTGUIObject read fHover;
      // Модальная форма (та, которая показывается поверх всех, если есть)
      property Modal: zglTForm read fModal;
      // Дочерние эелементы (обычно - формы)
      property Items:zglTGUIObjectsList read fItems;
      // Шрифт по-умолчанию
      property Font: zglTFontContainer read fFont write fFont;
      // Видимый ли гуй
      property Visible: Boolean read fVisible write fVisible;
      // Событие возникает после изменения размера
      property OnResize: zglTGuiEvent read fOnResize write fOnResize;
      procedure Draw;
      // Спрятать модальную форму (если есть)
      procedure ClearModal;
      procedure Update(dt: Double);
      constructor Create(dir: TCaption; screenWidth, screenHeight: integer;
        defaultFont: zglTFontContainer);
      procedure Resize(screenWidth, screenHeight: integer);
      destructor Destroy; override;
  end;

var
  Gui: zglTGui;
                          
implementation

function GetClipboardText(Wnd: HWND; var Str: TCaption): 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;

function Min(f, d: Single): Single;
begin
  if f > d then Result := d
    else Result := f;
end;

function Max(f, d: Single): Single;
begin
  if f < d then Result := d
    else Result := f;
end;

function scissor_Rect(rct, clntRect: zglTRect): zglTRect;
begin
  Result.X := Max(rct.X, clntRect.X);
  Result.Y := Max(rct.Y, clntRect.Y);
  Result.W := Min(rct.X + rct.W, clntRect.X + clntRect.W);
  Result.W := Result.W - Result.X;
  Result.H := Min(rct.Y + rct.H, clntRect.Y + clntRect.H);
  Result.H := Result.H - Result.Y;
  scissor_Begin(Round(Result.X), Round(Result.Y),
    Round(Result.W), Round(Result.H));
end;

// zglTBaseGUIObject

constructor zglTBaseGUIObject.Create;
begin
  Gui := pGui;   
end;

function zglTBaseGUIObject.getFont: zglTFontContainer;
begin
  if Assigned(fFont) then
    result := fFont
  else
    result := Gui.fFont;
end;

function zglTBaseGUIObject.HandleUpdate;
begin      
  Result := true;
end;

procedure zglTBaseGUIObject.HandleLeave;
begin      
  //
end;

procedure zglTBaseGUIObject.HandleDraw;
begin      
  //
end;

// zglTGUIObject

function zglTGUIObject.HandleUpdate;
var r: zglTRect;
begin
  result := inherited HandleUpdate(dt, evnt);
  case evnt of
    heMoveObject: begin
      r := updateRect;
      Move(mouse_X - fOffsetX, mouse_Y - fOffsetY);
      Result := not isHover(r);
      if not mouse_Down(M_BLEFT) then begin
        Gui.Handler.Clear;
        fPressed := False;
        Result := true;
      end;
    end;
    heResizeObject: begin
      Result := false;
      r := updateRect(fDtX, fDtY);
      if fCorner and 1 > 0 then begin
        Resize(fOffsetX - mouse_X, fRect.H);
        Move(fOffsetX - r.W + Rect.X - r.X, fRect.Y);
      end else begin
        Resize(mouse_X - r.X, fRect.H);
      end;
      if fCorner and 2 > 0 then begin
        Resize(fRect.W, fOffsetY - mouse_Y);
        Move(fRect.X, fOffsetY - r.H + Rect.Y - r.Y);
      end else begin     
        Resize(fRect.W, mouse_Y - r.Y);
      end;                   
      if fRect.W < 8 then fRect.W := 8;
      if fRect.H < 8 then fRect.H := 8; 

      if not mouse_Down(M_BLEFT) then begin
        Gui.Handler.Clear;          
        fPressed := False;
        Result := true;
      end;
    end;
  end; 
end;

function zglTGUIObject.updateRect;
begin
  Result := fRect;
  Result.X := fRect.X + oX;
  Result.Y := fRect.Y + oY;
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;
var ns: Boolean;
begin
  fRect.X := pX; fRect.Y := pY;
  if Assigned(fOnMove) then begin
    fOnMove(Self);
  end;
end;

procedure zglTGUIObject.setCaption(capt: TCaption);
begin
  fCaption := capt;
end;

procedure zglTGUIObject.setName(capt: TCaption);
var i: integer;
    AnsiCapt: AnsiString;
begin
  AnsiCapt := AnsiString(Capt);
  if capt = '' then
    exit;
  for i := 1 to Length(AnsiCapt) do
    if (not (AnsiCapt[i] in ['a'..'z', 'A'..'Z', '_', '0'..'9'])) or
      ((AnsiCapt[i] in ['0'..'9']) and (i = 1)) then
        Exit;
  fName := Capt;
end;

procedure zglTGUIObject.Resize;
var ns: Boolean;
begin
  fRect.W := pW; fRect.H := pH;  
  if Assigned(fOnResize) then
    fOnResize(self);

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;
  Result := obj.Font.GetForState(pActive);
end;

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

procedure zglTGUIObject.DrawEdit;
var r: zglTRect;
begin             
  if not Visible then Exit;
  if fEditMode  then begin
    r := updateRect(oX, oY);
    if fHover then begin
      text_DrawEx(Gui.Font.Normal.Data,
        r.X, r.Y, 1, 0, Name, 255, $FFFFFF,
        TEXT_VALIGN_BOTTOM or TEXT_HALIGN_LEFT);
    end;
    pr2d_Rect(r.X, r.Y, r.W, r.H, $FFFFFF, 32);
    Gui.DrawField(r.X, r.Y, em_width * 2, em_height * 2,
      em_left, em_top, em_width, em_height);  
    Gui.DrawField(r.X + r.W - em_width * 2, r.Y, em_width * 2, em_height * 2,
      em_left, em_top, em_width, em_height);
    Gui.DrawField(r.X, r.Y + r.H - em_height * 2, em_width * 2, em_height * 2,
      em_left, em_top, em_width, em_height);  
    Gui.DrawField(r.X + r.W - em_width * 2, r.Y + r.H - em_height * 2,
      em_width * 2, em_height * 2,
      em_left, em_top, em_width, em_height);
  end;
end;

procedure zglTGUIObject.Update;
var nowIsHover, b: Boolean;       
    r: zglTRect;
    pX, pY: Integer;
begin
  if not Visible then Exit;
  nowIsHover := false;
  if (Gui.Handler.canHover) then begin
    if isHover(clntRect, oX, oY) and (Gui.fHover = nil) then begin
      nowIsHover := true;
      Gui.fHover := self;
    end;
  end;
  fJustPressed := false;
  fRootPos.X := oX;
  fRootPos.Y := oY;
  r := updateRect(oX, oY);
  if nowIsHover then begin
    if mouse_Click(M_BLEFT) then begin
      if fEditMode then begin
        if not fEvenNotMove then begin
          fDtX := oX; fDtY := oY;
          pX := mouse_X - round(r.X);
          pY := mouse_Y - round(r.Y);
          fCorner := Byte((pX <= em_width * 2) and (pY <= em_height * 2)) * 4 +
                Byte((pX >= r.W - em_width * 2) and (pY <= em_height * 2)) * 3 +
                Byte((pX >= r.W - em_width * 2) and (pY >= r.H - em_height * 2)) * 1 +
                Byte((pX <= em_width * 2) and (pY >= r.H - em_height * 2)) * 2;
          if fCorner > 0 then begin
            Dec(fCorner);

            if fCorner and 1 > 0 then
              fOffsetX := round(r.X + r.W)
            else
              fOffsetX := round(fRect.X);

            if fCorner and 2 > 0 then
              fOffsetY := round(r.Y + r.H)
            else
              fOffsetY := round(fRect.H);
            Gui.Handler.HandleEvent(Self, heResizeObject);
          end else begin
            fOffsetX := mouse_X - round(Rect.X);
            fOffsetY := mouse_Y - round(Rect.Y);
            Gui.Handler.HandleEvent(Self, heMoveObject);
          end;
          mouse_ClearState;
          fPressed := true;
          fJustPressed := false;
        end;
      end else begin
        fPressed := true;
        fJustPressed := true;
        gui.Handler.Clear;
        if fCatchMouseClick then
          mouse_ClearState;        
      end;
    end;
    if mouse_Click(M_BRIGHT) then begin
      if Assigned(PopupMenu) then begin
        PopupMenu.Popup(self, mouse_X, mouse_Y);
        if CatchMouseClick then
          mouse_ClearState;
      end;
    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
        OnClick(self, Round(mouse_X - fRootPos.X), Round(mouse_Y - fRootPos.Y));
      end;
    end;
    fPressed := false;
  end;
end;

constructor zglTGUIObject.Create;
begin
  inherited Create(pGui); 
  fRect.X := pX; fRect.Y := pY; fRect.W := pW; fRect.H := pH;
  fPressed := False; fHover := false; Visible := pVisible; fTag := 0;
  fCatchMouseClick := true;
  fEditMode := false; fEvenNotMove := False;
  fData := nil; fNeedScroll := false;
  fName := 'Noname' + Copy(ClassName, 5, Length(ClassName)) +
    u_IntToStr(Gui.Counter); fContainer := 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(oX, oY);
  scissor_Rect(rct, clntRect);
  fnt := getRootFont(fHover);
  text_DrawInRectEx(fnt.Data, rct, fnt.Size, 0, Caption,
    fnt.Alpha, fnt.Color, fnt.Align);
  scissor_End;
  inherited DrawEdit(oX, oY);
end;

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

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

// zglTEdit

procedure zglTEdit.Draw;
var fnt: zglTFontObject;
    rct: zglTRect;
    i, w, off, f: single;
    s: TCaption;
begin
  if not Visible then Exit;
  rct := updateRect(oX, oY);
  scissor_Rect(rct, clntRect);
  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);
  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;

  inherited DrawEdit(oX, oY);
end;

procedure zglTEdit.Update;
begin
  if not Visible then Exit;
  inherited;
  if fJustPressed then begin
    Focus;
  end;
end;

procedure zglTEdit.setCaption;
begin
  inherited;
  if (fLastVal <> capt) and Assigned(fOnChange) then
    fOnChange(Self);
  fLastVal := capt;
end;

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

procedure zglTEdit.Focus;
begin
  key_BeginReadText(fCaption, fMaxLength);
  Gui.Handler.HandleEvent(Self, heInputText);  
end;

procedure zglTEdit.HandleDraw;
begin
  //
end;

procedure zglTEdit.HandleLeave;
begin
  case evnt of
    heInputText: begin
      fCaption := key_GetText;
      key_EndReadText;
    end
  end;
end;

function zglTEdit.HandleUpdate;
var s: TCaption;
begin
  inherited HandleUpdate(dt, evnt);
  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);
        if (fCaption <> fLastVal) and Assigned(fOnChange) then begin
          fOnChange(Self);
          fLastVal := fCaption;   
        end;
      end;
      fCaption := key_GetText;
      if (fCaption <> fLastVal) and Assigned(fOnChange) then begin
        fOnChange(Self);
        fLastVal := fCaption;
      end;
      fUpd := fUpd + dt;
      if fUpd > 500 then fUpd := 0;
    end;
  end;
  Result := true;
end;

// zglTComboBox

procedure zglTComboBox.Draw;
var fnt: zglTFontObject;
    rct: zglTRect;
begin           
  if not Visible then Exit;         
  rct := updateRect(oX, oY);
  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);
  inherited DrawEdit(oX, oY);
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)
    else
      Gui.Handler.Clear;
  end;
end;

constructor zglTComboBox.Create;
begin
  inherited Create(pGui, pX, pY, pW, pH, pVisible);
  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
  case evnt of
    heComboField, heComboMove: begin
      rct := updateRect(fRootPos.X, fRootPos.Y);
      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;
  end;
end;

procedure zglTComboBox.HandleLeave;
begin
  //
end;

function zglTComboBox.HandleUpdate;
var rct, r: zglTRect;
    ic: integer;
    pY: single;
    mU, mD: Boolean;
begin
  Result := inherited HandleUpdate(dt, evnt);
  rct := updateRect(fRootPos.X, fRootPos.Y);
  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
      Result := false;
      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);
        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 else
      Result := true;
  end;
  rct.H := ic * cmb_itm_height * 2;
  if col2d_PointInRect(mouse_X, mouse_Y, rct) then begin
    Result := False;
    if mouse_Click(M_BLEFT) then begin
      Result := True;
      Selected := trunc((mouse_Y - rct.Y) / (2*cmb_itm_height)) + fOffset;
      if CatchMouseClick then
        mouse_ClearState;
       Gui.Handler.Clear;
    end;
  end;
  case evnt of
    heComboMove: begin
      Result := false;
      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);
      end;
    end;
  end;
end;

// zglTImageButton

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

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

constructor zglTImageButton.Create;
begin
  inherited Create(pGui, pX, pY, pW, pH, pVisible);
  fTexture := pTexture;
  fTexWidth := texWidth;
  fTexHeight := texHeight;
  fTextureOffset := pTextureOffset;
  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(oX, oY);
  scissor_Rect(rct, clntRect);
  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);
  scissor_End;
  inherited DrawEdit(oX, oY);
end;

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

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

// zglTProgressBar

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

procedure zglTProgressBar.Draw;
var rct: zglTRect;
begin
  if not Visible then Exit;
  rct := updateRect(oX, oY);
  Gui.DrawField(rct.X, rct.Y, rct.W, rct.H,
    pbar_left, pbar_top, pbar_width, pbar_height);
  rct.X := rct.X + pbar_offsetx;
  rct.Y := rct.Y + pbar_offsety;
  rct.H := rct.H - pbar_offsety * 2;
  rct.W := ((fProgress - fMin) / (fMax - fMin)) * (rct.W - pbar_offsetx * 2);
  Gui.DrawField(rct.X, rct.Y, rct.W, rct.H,
    pbdata_left, pbdata_top, pbdata_width, pbdata_height);
  inherited DrawEdit(oX, oY);
end;

constructor zglTProgressBar.Create;
begin
  inherited Create(pGui, pX, pY, pW, pH, pVisible);
  fMax := 100;
  fMin := 0;
  fProgress := 0;
  fLastProgress := 0;
end;

procedure zglTProgressBar.setProgress;
begin
  fProgress := progress;
  if fProgress < fMin then
    fProgress := fMin;
  if fProgress > fMax then
    fProgress := fMax;
  if fProgress <> fLastProgress then begin
    if Assigned(fOnChange) then
      fOnChange(Self);
    fLastProgress := fProgress;
  end;
end;

// zglTTrackBar

constructor zglTTrackBar.Create;
begin
  inherited Create(pGui, pX, pY, pW, pH, pVisible);
  fMax := 100;
  fMin := 0;
  fProgress := 0;
end;

procedure zglTTrackBar.Update;
var rct, rd: zglTRect;
begin
  inherited;
  if not Visible then Exit;
  rct := updateRect(oX, oY);
  rd.X := Round(rct.X + ((fProgress - fMin) / (fMax - fMin)) *
    (rct.W - trck_width));
  rd.Y := round(rct.Y + rct.H / 2 - trck_height / 2);
  rd.W := trck_width;
  rd.H := trck_height;
  if fJustPressed then begin
    if isHover(rd, oX, oY) then
      Gui.Handler.HandleEvent(Self, heTrackBar)
    else
      Progress := Min +
        Round(((Mouse_X - Rect.X - RootPos.X - trck_width / 2)
          / (Rect.W - trck_width)) * (Max - Min));

    if CatchMouseClick then
      mouse_ClearState;
  end;
end;

procedure zglTTrackBar.Draw;
var rct: zglTRect;
begin
  if not Visible then Exit;
  rct := updateRect(oX, oY);
  Gui.DrawField(rct.X, rct.Y, rct.W, rct.H,
    pbar_left, pbar_top, pbar_width, pbar_height);

  rct.X := Round(rct.X + ((fProgress - fMin) / (fMax - fMin)) *
    (rct.W - trck_width));
  rct.Y := round(rct.Y + rct.H / 2 - trck_height / 2);
  rct.W := trck_width;
  rct.H := trck_height;


  Gui.DrawTex(rct.X, rct.Y,
    trck_left, trck_top + Byte(isHover(rct, oX, oY)) * trck_height,
    trck_width, trck_height);
  inherited DrawEdit(oX, oY);
end;

procedure zglTTrackBar.HandleDraw;
begin
  //
end;

procedure zglTTrackBar.HandleLeave;
begin
  //
end;

function zglTTrackBar.HandleUpdate;
var s: TCaption;
begin
  inherited HandleUpdate(dt, evnt);
  case evnt of
    heTrackBar: begin
      Progress := Min +
        Round(((Mouse_X - Rect.X - RootPos.X - trck_width / 2)
          / (Rect.W - trck_width)) * (Max - Min));
      if not mouse_Down(M_BLEFT) then
        Gui.Handler.Clear;
    end;
  end;
  Result := False;
end;

// zglTCheckBox

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

procedure zglTCheckBox.Draw;
var
    rct: zglTRect;
    fnt: zglTFontObject;
    i: integer;
begin
  if not Visible then Exit;
  rct := updateRect(oX, oY);   
  scissor_Rect(rct, clntRect);
  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);
  scissor_End;
  inherited DrawEdit(oX, oY);      
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, pVisible);
  Caption := pCaption;
end;

procedure zglTRadioButton.Draw;
var
    rct: zglTRect;
    fnt: zglTFontObject;
    i: integer;
begin
  if not Visible then Exit;
  rct := updateRect(oX, oY);      
  scissor_Rect(rct, clntRect);
  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);
  scissor_End;
  inherited DrawEdit(oX, oY);
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;
  inherited DrawEdit(oX, oY);
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, pVisible);
  with fBorder do begin
    X := 0; Y := 0; W := 0; H := 0;
  end;
  UpdateClientRect;
  with fScroll do begin
    X := 0; Y := 0;
  end;
  fItems := zglTGUIObjectsList.Create(pGui, self);
  canMove := false;
  fScrollBars := SCROLL_BOTH;
  fActiveScrollBars := SCROLL_NONE;
  fContainer := true;
end;

constructor zglTFrame.CreateFullScreen;
begin
  inherited Create(pGui, 0, 0, pGui.fRect.W, pGui.fRect.H, pVisible);
  with fBorder do begin
    X := 0; Y := 0; W := 0; H := 0;
  end;
  UpdateClientRect;
  with fScroll do begin
    X := 0; Y := 0;
  end;
  fItems := zglTGUIObjectsList.Create(pGui, self);
  canMove := false;
  fScrollBars := SCROLL_BOTH;
  fActiveScrollBars := SCROLL_NONE;
  fContainer := true;
end;

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

procedure zglTFrame.onAdd;
begin
  //
end;

procedure zglTFrame.onRemove;
begin
  //
end;

procedure zglTFrame.HandleDraw;
begin
  //
end;

procedure zglTFrame.HandleLeave;
begin
  //
end;

function zglTFrame.HandleUpdate;
begin        
  Result := inherited HandleUpdate(dt, evnt);
end;

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

procedure zglTFrame.Resize;
begin
  inherited;
  UpdateClientRect;
end;

procedure zglTForm.ShowModal;
begin
  Gui.fModal := self;
  Show;
end;

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

procedure zglTFrame.Draw;
var i: integer;
    r, rct, fUpdateRect: zglTRect;
begin          
  if not Visible then Exit;
  r := updateRect(oX + fClientRect.X, oY + fClientRect.Y);
  fUpdateRect := Gui.GetRect(r.X, r.Y,
                     ClientRect.W, ClientRect.H);
  rct := scissor_Rect(fUpdateRect, clntRect);
  for i := 0 to fItems.Count - 1 do
    fItems.Items[i].Draw(rct, r.X + fScroll.X,
                         r.Y + fScroll.Y);
  scissor_End;
end;

procedure zglTFrame.Update;
var i: integer;
    r, fUpdateRect: zglTRect;
begin
  if not Visible then Exit;
  r := updateRect(oX + ClientRect.X, oY + ClientRect.Y);
  fUpdateRect := Gui.GetRect(r.X, r.Y,
                     ClientRect.W, ClientRect.H);
  if updateChind then                                  
    for i := fItems.Count - 1 downto 0 do begin
      fItems.Items[i].Update(dt, fUpdateRect, true,
        r.X + fScroll.X, r.Y + fScroll.Y);
      //if fItems.Items[i].fHover and fItems.Items[i].CatchMouseClick then
      //  lockHover := True;
    end;
  inherited;
  if Assigned(Gui) then
    if fJustPressed then begin
      if CanMove then begin
        fOffsetX := mouse_X - round(Rect.X);
        fOffsetY := mouse_Y - round(Rect.Y);
        Gui.Handler.HandleEvent(Self, heMoveObject);
      end;
      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;

// zglTMainMenuItem

constructor zglTMainMenuItem.Create;
begin
  Text := Text_;
  CallBack := CallBack_;
  Width := Width_;
  Popup := Popup_;
end;

// zglTMainMenu

constructor zglTMainMenu.Create;
begin
  inherited Create(pGui, pX, pY, pW, pH, pVisible);
  Visible := true;
  Items := TList.Create;
  ContexItem := nil;
end;

procedure zglTMainMenu.CalcOffsets;
var i, px: integer;
    item: zglTMainMenuItem;
begin
  px := 0;    
  for i := 0 to Items.Count -1 do begin
    item := Items.Items[i];
    item.X := px; 
    Inc(px, item.Width);
  end;
end;

procedure zglTMainMenu.AddItem;
begin
  Items.Add(Item);
  CalcOffsets;
end;

procedure zglTMainMenu.RemoveItem;
begin
  Items.Remove(Item);
  CalcOffsets;
  Item.Free;
end;

procedure zglTMainMenu.Draw;
var rct, r: zglTRect;
    i: integer;
    item: zglTMainMenuItem;
    fnt: zglTFontObject;
    b: Boolean;
begin
  rct := updateRect(oX, oY);
  gui.DrawField(rct.X, rct.Y, rct.W, rct.H,
    mm_left, mm_top, mm_width, mm_height);
  r.Y := rct.Y; r.H := rct.H;
  for i := 0 to Items.Count -1 do begin
    item := Items.Items[i];
    r.W := item.Width;
    r.X := rct.X + item.X;
    b := isHover(r) or ((Gui.Handler.hObject = self) and (ContexItem = item));
    fnt := getRootFont(b);
    Gui.DrawField(r.X, r.Y, r.W, r.H,
      mm_itm_left, mm_itm_top + Byte(b)
        * mm_itm_height * 2, mm_itm_width, mm_itm_height);
    text_DrawEx(fnt.Data,
      rct.X + item.X + item.Width / 2,
      rct.Y + rct.H / 2,
      fnt.Size, 0,
      item.Text, fnt.Alpha, fnt.Color, TEXT_HALIGN_CENTER or TEXT_VALIGN_CENTER);
  end;
  inherited DrawEdit(oX, oY);
end;

procedure zglTMainMenu.Update;
var rct, r: zglTRect;
    i: integer;
    item: zglTMainMenuItem;
begin
  if not Visible then Exit;
  inherited;
  rct := updateRect(oX, oY);
  r.Y := rct.Y; r.H := rct.H;
  for i := 0 to Items.Count -1 do begin
    item := Items.Items[i];
    r.W := item.Width;
    r.X := rct.X + Item.X;
    if isHover(r) then begin
      if fPressed or ((Gui.Handler.hHandle = hePopup)
        and (Gui.Handler.hObject <> item.Popup)) then begin
        if Assigned(item.Callback) then begin
          item.Callback(item);   
        end;
        item.Popup.Popup(Self,
          round(r.X) + 1, round(r.Y + r.H) + 1);
      end;
    end;
  end;
end;

destructor zglTMainMenu.Destroy;
begin
  inherited;
end;

//zglTPopupMenuItem

constructor zglTMenuItem.Create;
begin
  Text := Text_;
  CallBack := CallBack_;
end;


//zglTPopupMenu

procedure zglTPopupMenu.Popup;
var i, j: integer;
    pmi: zglTMenuItem;
    fnt: zglTFontObject;
begin
  if Width = 0 then begin
    Width := MinWidth;
    fnt := Font.GetForState(false);
    for i := 0 to fItems.Count -1 do begin
      pmi := fItems.Items[i];
      j := round(text_GetWidth(fnt.Data, pmi.Text) * fnt.Size) + mm_margin * 2;
      if Width < j then
        Width := j;
    end;
  end;
  Caller := Sender;
  if X_ + Width > Gui.fRect.W then
    X_ := X_ - Width;
  if Y_ + fItems.Count * mm_height * 2 > Gui.fRect.H then
    Y_ := Y_ - fItems.Count * mm_height * 2;
  X := X_; Y := Y_;
  if Assigned(OnPopup) then
    OnPopup(Sender, X, Y);                   
  Gui.Handler.HandleEvent(self, hePopup);
end;

function zglTPopupMenu.getItem;
begin
  Result := fItems.Items[index];
end;

function zglTPopupMenu.getItemsCount;
begin
  Result := fItems.Count;
end;

procedure zglTPopupMenu.HandleDraw(evnt: THandleEvent);
var r: zglTRect;
    i: integer;
    item: zglTMenuItem;
    fnt: zglTFontObject;
    b: Boolean;
begin
  case evnt of
    hePopup: begin
      if fItems.Count = 0 then exit;
      Gui.DrawField(X, Y, Width, fItems.Count * mm_height * 2,
        mm_frm_left, mm_frm_top, mm_frm_width, mm_frm_height);
      r.X := X; r.H := mm_height * 2; r.W := Width;
      for i := 0 to fItems.Count -1 do begin
        item := fItems.Items[i];
        r.Y := Y + i * mm_height * 2;
        if (item.Text = '-') then begin
          pr2d_Line(r.X + 2, r.Y + mm_height,
                    r.X + r.W - 2, r.Y + mm_height, $FFFFFF, 32);
        end else begin
          b := col2d_PointInRect(Mouse_X, Mouse_Y, r);
          fnt := Font.GetForState(b);
          Gui.DrawField(r.X, r.Y, r.W, r.H,
            mm_frm_left, mm_itm_top + Byte(b) * mm_frm_height * 2,
            mm_frm_width, mm_frm_height);
          text_DrawEx(fnt.Data,
            r.X + mm_margin,
            r.Y + r.H / 2,
            fnt.Size, 0, item.Text, fnt.Alpha, fnt.Color,
            TEXT_HALIGN_LEFT or TEXT_VALIGN_CENTER);
        end;
      end;
    end;
  end; 
end;

function zglTPopupMenu.HandleUpdate;
var r, rct: zglTRect;
    i: integer;
    item: zglTMenuItem;
begin
  rct := Gui.getRect(X, Y, Width, fItems.Count * mm_height * 2);
  Result := not col2d_PointInRect(Mouse_X, Mouse_Y, rct);
  r.X := X; r.H := mm_height * 2; r.W := Width;
  for i := fItems.Count -1 downto 0 do begin
    item := fItems.Items[i];
    r.Y := Y + i * mm_height * 2;
    if col2d_PointInRect(Mouse_X, Mouse_Y, r) then begin
      if mouse_Click(M_BLEFT) then begin
        if Assigned(Item.Callback) then begin
          r := Caller.updateRect;
          Item.CallBack(Caller, item, X - round(r.X), Y - round(r.Y));
        end;           
        self.Caller := nil;
        Result := true;
        if Gui.Handler.hHandle = hePopup then
          Gui.Handler.Clear;
        mouse_ClearState;  
      end;
    end;
  end;

end;

procedure zglTPopupMenu.HandleLeave(evnt: THandleEvent);
begin
  //
end;

constructor zglTPopupMenu.Create;
begin
  inherited Create(pGui);
  fItems := TList.Create;
  MinWidth := MinWidth_;
end;

function zglTPopupMenu.AddItem;
begin
  fItems.Add(Item);
  result := self;
end;

procedure zglTPopupMenu.RemoveItem;
begin
  fItems.Remove(Item);
  Item.Free;
end;

destructor zglTPopupMenu.Destroy;
begin
  inherited;
end;

// zglTPageControlSheet

constructor zglTPageControlSheet.Create;
begin
  inherited Create(pGui, 0, 0, 0, 0);
  fCaption := pCaption;
  fImage := pImage;
  Visible := true;
  fEditMode := false;
  CatchMouseClick := true;
  fEvenNotMove := true;
  UpdateClientRect;
end;

procedure zglTPageControlSheet.Show;
begin
  inherited;
  zglTPageControl(Parent).RelocateButtons;
end;

procedure zglTPageControlSheet.Hide;
begin
  inherited;
  zglTPageControl(Parent).RelocateButtons;
end;

procedure zglTPageControlSheet.Draw;
begin
  inherited;
  DrawEdit(oX, oY);
end;

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

procedure zglTPageControlSheet.setImage;
begin
  fImage := tex;
  zglTPageControl(fParent).RelocateButtons;
end;

procedure zglTPageControlSheet.setCaption(capt: TCaption);
begin
  inherited;
  zglTPageControl(fParent).RelocateButtons;
end;

destructor zglTPageControlSheet.Destroy;
begin
  inherited;
end;

// zglTPageControl

constructor zglTPageControl.Create;
begin
  inherited;
  CatchMouseClick := true;
  with fBorder do begin
    X := pcb_rect; Y := pcb_height; W := pcb_rect; H := pcb_rect;
  end;
  UpdateClientRect;
end;

procedure zglTPageControl.Draw;
var rct, r, prct: zglTRect;
    i: integer;
    pcs: zglTPageControlSheet;
    fnt: zglTFontObject;
    b: Boolean;
begin
  if not Visible then Exit;
  rct := updateRect(oX, oY);
  gui.DrawField(rct.X, rct.Y + pcb_height, rct.W, rct.H - pcb_height,
    pc_left, pc_top, pc_width, pc_height);
  r := Gui.getRect(0, rct.Y, 0, pcb_height);
  for i := 0 to fItems.Count -1 do begin   
    pcs := zglTPageControlSheet(fItems.Items[i]);
    if pcs.Visible then begin
      r.X := rct.X + pcs.X;
      r.W := pcs.Width;
      b := (fSelected = i);
      gui.DrawField(rct.X + pcs.X, rct.Y, pcs.Width, pcb_height,
        pcs_left, pcs_top + Byte(b) * pcs_height * 2 , pcs_width, pcs_height);
      fnt := getRootFont(isHover(r, oX, oY));
      if pcs.fImage <> nil then begin
        ssprite2d_Draw(pcs.fImage,
          rct.X + pcs.X + pcb_drawoffset, rct.Y + pcb_heightd2 - img_height / 2,
          img_width, img_height, 0);
      end;
      text_DrawEx(fnt.Data,
        rct.X + pcs.X + pcb_drawoffset + Byte(pcs.fImage <> nil) * img_width,
        rct.Y + pcb_heightd2,
        fnt.Size, 0, pcs.fCaption, fnt.Alpha, fnt.Color,
        TEXT_HALIGN_LEFT or TEXT_VALIGN_CENTER);
    end;
  end;
  prct := scissor_Rect(rct, clntRect);
  if fItems.Count > 0 then begin
    zglTPageControlSheet(fItems.Items[fSelected]).Draw(prct,
      rct.X + fScroll.X, rct.Y + fScroll.Y);
  end;
  scissor_End;
  inherited DrawEdit(oX, oY);
end;

procedure zglTPageControl.Update;
var rct, r: zglTRect;
    i: integer;
    pcs: zglTPageControlSheet;
begin
  if not Visible then Exit;
  rct := updateRect(oX, oY);
  r := Gui.getRect(0, rct.Y, 0, pcb_height);
  for i := 0 to fItems.Count -1 do begin
    pcs := zglTPageControlSheet(fItems.Items[i]);
    if pcs.Visible then begin
      r.X := rct.X + pcs.X;
      r.W := pcs.Width;
      if isHover(r, oX, oY) and fJustPressed then begin
        fSelected := i;
        mouse_ClearState;
      end;
    end;
  end;          
  if fItems.Count > 0 then begin
    pcs := zglTPageControlSheet(fItems.Items[fSelected]);
    pcs.Update(dt, clntRect, true, rct.X + fScroll.X, rct.Y + fScroll.Y);
    //if pcs.fHover and pcs.CatchMouseClick then
    //  lockHover := true;
  end;
  inherited Update(dt, clntRect, false, oX, oY);  
end;

procedure zglTPageControl.onAdd;
begin
  inherited;
  zglTPageControlSheet(Sender).Move(ClientRect.X, ClientRect.Y);
  zglTPageControlSheet(Sender).Resize(ClientRect.W, ClientRect.H);
  fSelected := fItems.Count -1;
  RelocateButtons;
end;

procedure zglTPageControl.Resize;
var i: Integer;
begin
  for i := 0 to fItems.Count - 1 do begin
    zglTPageControlSheet(fItems.Items[i]).Move(ClientRect.X, ClientRect.Y);
    zglTPageControlSheet(fItems.Items[i]).Resize(ClientRect.W, ClientRect.H);
  end;
  inherited;
end;

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

procedure zglTPageControl.setSelected;
begin
  fSelected := Index;
  if fSelected > fItems.Count - 1 then
    fSelected := fItems.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 fItems.Count - 1 do begin
    sht := zglTPageControlSheet(fItems.Items[i]);
    if sht.Visible then begin
      sht.X := px;
      l := Round( text_GetWidth(fnt.Data, sht.fCaption) * fnt.Size )
        + pcb_drawoffset * 2;
      if sht.fImage <> nil then
        l := l + img_width;
      sht.Width := l;
      Inc(px, l);
    end;
  end;
end;


procedure zglTPageControl.onRemove;
begin
  if fSelected = fItems.Count -1 then
    fSelected := fItems.Count -2;
  RelocateButtons;
end;

destructor zglTPageControl.Destroy;
begin
  inherited;
end;

// zglTForm

constructor zglTForm.Create;
begin
  inherited Create(pGui, pX, pY, pW, pH, pVisible);
  Caption := pCaption;
  canMove := false;
  fMainMenu := nil;
  with fBorder do begin
    X := frm_offset; Y := frm_offset_capt; W := frm_offset; H := frm_offset;
  end;
  UpdateClientRect;
  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;

destructor zglTForm.Destroy;
begin
  inherited;
end;

procedure zglTForm.Draw;
var
    r: zglTRect;
    fnt: zglTFontObject;
begin
  if not Visible then Exit;
  r := updateRect(oX, oY);
  scissor_Rect(r, clntRect);
  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);
  scissor_End;
  inherited DrawEdit(oX, oY);
end;

procedure zglTForm.Hide;
begin
  inherited;
  if Gui.Modal = self then
    Gui.ClearModal;
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;

destructor zglTFontContainer.Destroy;
begin
  if Normal = Active then
    Normal.Free
  else begin
    Normal.Free;
    Active.Free;
  end;
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;

function zglTFontContainer.GetForState;
begin
  if state then
    Result := Active
  else
    Result := 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 := false;
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 
    canHover := hObject.HandleUpdate(dt, hHandle);
  end;
end;

// GUI     

constructor zglTGui.Create;
begin
  Handler := zglTHandler.Create;
  Handler.gui := self;
  fItems := zglTGUIObjectsList.Create(Self, nil);
  fVisible := True; fHover := nil;
  Font := defaultFont;
  fRect := getRect(0, 0, screenWidth, screenHeight);
  fTex := 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);
  fCounter := 1;
end;

function zglTGui.getCounter: integer;
begin
  Result := fCounter;
  inc(fCounter);
end;

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

destructor zglTGui.Destroy;
begin
  fItems.Free;
  Handler.Free;
  if fFont <> nil then
    fFont.Free;
  inherited;
end;

procedure zglTGui.Draw;
var i: integer;
begin
  if not Visible then Exit;
  if Assigned(Modal) then begin
    for i := 0 to fItems.Count - 1 do
      if fItems.Items[i] <> Modal then
        if fItems.Items[i].Visible then
          zglTGUIObject(fItems.Items[i]).Draw(fRect, 0, 0);
    if Modal.Visible then begin
      pr2d_Rect(0, 0, fRect.W, fRect.H, $0, 128, PR2D_FILL);
      Modal.Draw(fRect, 0, 0);
    end;
  end else
    for i := 0 to fItems.Count - 1 do
      if fItems.Items[i].Visible then
        fItems.Items[i].Draw(fRect, 0, 0);
  Handler.Draw;
end;

procedure zglTGui.Update;
var i: integer;
begin
  if not Visible then Exit;
  fHover := nil;
  Handler.Update(dt);
  if Assigned(Modal) then begin
    if Modal.Visible then begin
      Modal.Update(dt, fRect, true, 0, 0);
    end else begin
      ClearModal;
    end;
  end else begin
    for i := fItems.Count - 1 downto 0 do
      if fItems.Items[i].Visible then
        fItems.Items[i].Update(dt, fRect, true, 0, 0);
  end;

  if mouse_Click(M_BLEFT) then
    Handler.Clear;
end;

procedure zglTGui.ClearModal;
begin
  fModal := nil;
end;

procedure zglTGui.DrawTex;
var rct: zglTRect;
begin
  rct.X := dX; rct.Y := dY; rct.W := dW; rct.H := dH;
  csprite2d_Draw(fTex, X, Y, dW, dH, 0, rct);
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(fTex, X, Y, dW, dH, 0, rct);
  rct.X := dX + dW;
  csprite2d_Draw(fTex, X + WdW, Y, dW, dH, 0, rct);
  rct.Y := dY + dH;
  csprite2d_Draw(fTex, X + WdW, Y + HdH, dW, dH, 0, rct);
  rct.X := dX;
  csprite2d_Draw(fTex, X, Y + HdH, dW, dH, 0, rct);
  rct.X := dX + dW; rct.Y := dY; rct.W := 0; rct.H := dH;
  csprite2d_Draw(fTex, X + dW, Y, W - dW*2, dH, 0, rct);
  rct.Y := dY + dH;
  csprite2d_Draw(fTex, X + dW, Y + HdH, W - dW*2, dH, 0, rct);
  rct.H := 0;
  csprite2d_Draw(fTex, X + dW, Y + dH, W - dW*2, H - dH*2, 0, rct);
  rct.W := dW; rct.X := dX;
  csprite2d_Draw(fTex, X, Y + dH, dW, H - dH*2, 0, rct);
  rct.X := dX + dW;
  csprite2d_Draw(fTex, X + WdW, Y + dH, dW, H - dH*2, 0, rct);
end;

constructor zglTGUIObjectsList.Create;
begin
  fObjects := TList.Create;
  fGui := pGui;
  fMaster := pMaster;
end;

destructor zglTGUIObjectsList.Destroy;
var i: integer;
    item: zglTGUIObject;
begin
  for i := 0 to fObjects.Count -1 do begin
    item := fObjects.Items[i];
    Item.Free;
  end;
  fObjects.Free;
  inherited;
end;

procedure zglTGUIObjectsList.RemoveAll;
var i: integer;
    item: zglTGUIObject;
begin
  for i := 0 to fObjects.Count -1 do begin
    item := fObjects.Items[i];
    Item.Free;
  end;
  fObjects.Clear;
end;

procedure zglTGUIObjectsList.Remove;
begin         
  if Assigned(fMaster) then
    fMaster.onRemove(obj);
  fObjects.Remove(obj);
  obj.Free;
end;

function zglTGUIObjectsList.Add;
begin
  fObjects.Add(obj);
  Obj.Parent := fMaster;
  Obj.Gui := fGui;
  Result := obj;
  if Assigned(fMaster) then
    fMaster.onAdd(obj);
end;

function zglTGUIObjectsList.getObjectsCount;
begin
  Result := fObjects.Count;
end;

function zglTGUIObjectsList.getObject(Index: Integer):zglTGUIObject;
begin
  Result := fObjects.Items[Index];
end;

procedure zglTGUIObjectsList.MoveToTop(obj: zglTGUIObject);
begin
  fObjects.Move(fObjects.IndexOf(obj), fObjects.Count - 1);
end;

procedure zglTGUIObjectsList.MoveToBottom(obj: zglTGUIObject);
begin
  fObjects.Move(fObjects.IndexOf(obj), 0);
end;

end.
