unit imguimain;

{$mode objfpc}{$H+}

interface

uses
  Classes, Forms, Controls, Graphics, ExtCtrls, OpenGLContext,
  GL, LCLType, fpImage, SysUtils;

type
  TGLColor = record
    alpha: GLushort;
    blue: GLushort;
    green: GLushort;
    red: GLushort;
  end;

  { TForm1 }

  TForm1 = class(TForm)
    OpenGLControl1: TOpenGLControl;
    Timer1: TTimer;
    procedure FormCreate(Sender: TObject);
    procedure FormKeyDown(Sender: TObject; var Key: word; Shift: TShiftState);
    procedure FormKeyPress(Sender: TObject; var Key: char);
    procedure OpenGLControl1MouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: integer);
    procedure OpenGLControl1MouseMove(Sender: TObject; Shift: TShiftState; X, Y: integer);
    procedure OpenGLControl1MouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: integer);
    procedure OpenGLControl1Paint(Sender: TObject);
    procedure Timer1Timer(Sender: TObject);
  private
    { private declarations }
  public
    { public declarations }
  end;

  { UIStateType }

  UIStateType = object
    activeitem: integer;
    hotitem: integer;
    kbditem: integer;
    keychar: integer;
    keyentered: integer;
    keymod: TShiftState;
    lastwidget: integer;
    mousedown: integer;
    mousex: integer;
    mousey: integer;

    procedure Init;
    procedure SetMousePos(X, Y: integer);
  end;

var
  Form1: TForm1;
  uistate: UIStateType;

implementation

uses
  uglyfont;

var
  bgcolor: integer;
  sometext: string;
  genid: integer;

{ UIStateType }

procedure UIStateType.Init;
begin
  mousex := 0;
  mousey := 0;
  mousedown := 0;

  hotitem := 0;
  activeitem := 0;

  kbditem := 0;
  keyentered := 0;
  keymod := [];

  lastwidget := 0;
end;

procedure UIStateType.SetMousePos(X, Y: integer);
begin
  mousex := X;
  mousey := Y;
end;

{$R *.lfm}

function GLColor(color: GLuint): TGLColor;
begin
  Result.red := (color shr 16) and $0000ff;
  Result.green := (color shr 8) and $0000ff;
  Result.blue := (color shr 0) and $0000ff;
  //Result.alpha := (color shr 0) and $000000ff;
  Result.alpha := $ff;
end;

//Draw the string. Characters are fixed width, so this is also
//deadly simple.
procedure drawstring(str: string; x, y: double);
begin
  glTextOut(x, y + 14, 0, 14, 14, 1, 0, str);
end;

//Simplified interface to OpenGL's fillrect call
procedure drawrect(x, y, w, h: integer; AColor: TGLColor);
begin
  glColor3ub(AColor.red, AColor.green, AColor.blue);
  glRectf(x, y, x + w, y + h);
end;

//Check whether current mouse position is within a rectangle
function regionhit(x, y, w, h: integer): integer;
begin
  if (uistate.mousex < x) or
    (uistate.mousey < y) or
    (uistate.mousex >= x + w) or
    (uistate.mousey >= y + h) then
    Result := 0
  else
    Result := 1;
end;

//Simple button IMGUI widget
function button(id: integer; x: integer; y: integer): integer;
begin
  //Check whether the button should be hot
  if regionhit(x, y, 64, 48) = 1 then
  begin
    uistate.hotitem := id;
    if (uistate.activeitem = 0) and (uistate.mousedown = 1) then
      uistate.activeitem := id;
  end;

  //If no widget has keyboard focus, take it
  if uistate.kbditem = 0 then
    uistate.kbditem := id;

  //If we have keyboard focus, show it
  if uistate.kbditem = id then
    drawrect(x - 6, y - 6, 84, 68, GLColor($ff0000));

  drawrect(x + 8, y + 8, 64, 48, GLColor($000000));

  //Render button
  if uistate.hotitem = id then
  begin
    if uistate.activeitem = id then
      //Button is both 'hot' and 'active'
      drawrect(x + 2, y + 2, 64, 48, GLColor($ffffff))
    else
      //Button is merely 'hot'
      drawrect(x, y, 64, 48, GLColor($ffffff));
  end
  else
    //button is not hot, but it may be active
    drawrect(x, y, 64, 48, GLColor($aaaaaa));

  //If we have keyboard focus, we'll need to process the keys
  if uistate.kbditem = id then
  begin
    case uistate.keyentered of
      VK_TAB:
      begin
        //If tab is pressed, lose keyboard focus.
        //Next widget will grab the focus.
        uistate.kbditem := 0;

        //If shift was also pressed, we want to move focus
        //to the previous widget instead.
        if ssShift in uistate.keymod then
          uistate.kbditem := uistate.lastwidget;

        //Also clear the key so that next widget
        //won't process it
        uistate.keyentered := 0;
      end;
      VK_RETURN:
      begin
        //Had keyboard focus, received return,
        //so we'll act as if we were clicked.
        Result := 1;
        exit;
      end;
    end;
  end;
  uistate.lastwidget := id;

  //If button is hot and active, but mouse button is not
  //down, the user must have clicked the button.
  if (uistate.mousedown = 0) and
    (uistate.hotitem = id) and
    (uistate.activeitem = id) then
    Result := 1
  else
    //Otherwise, no clicky.
    Result := 0;
end;

//Simple scroll bar IMGUI widget
function slider(id: integer; x: integer; y: integer; max: integer; var Value: integer): integer;
var
  ypos: integer;
  mousepos: integer;
  v: integer;
begin
  //Calculate mouse cursor's relative y offset
  ypos := ((256 - 16) * Value) div max;

  //Check for hotness
  if regionhit(x + 8, y + 8, 16, 255) = 1 then
  begin
    uistate.hotitem := id;
    if (uistate.activeitem = 0) and (uistate.mousedown = 1) then
      uistate.activeitem := id;
  end;

  //If no widget has keyboard focus, take it
  if uistate.kbditem = 0 then
    uistate.kbditem := id;

  //If we have keyboard focus, show it
  if uistate.kbditem = id then
    drawrect(x - 4, y - 4, 40, 280, GLColor($ff0000));

  drawrect(x, y, 32, 256 + 16, GLColor($777777));

  //Render the scrollbar
  if (uistate.activeitem = id) or (uistate.hotitem = id) then
    drawrect(x + 8, y + 8 + ypos, 16, 16, GLColor($ffffff))
  else
    drawrect(x + 8, y + 8 + ypos, 16, 16, GLColor($aaaaaa));

  //If we have keyboard focus, we'll need to process the keys
  if uistate.kbditem = id then
  begin
    case uistate.keyentered of
      VK_TAB:
      begin
        //If tab is pressed, lose keyboard focus.
        //Next widget will grab the focus.
        uistate.kbditem := 0;

        //If shift was also pressed, we want to move focus
        //to the previous widget instead.
        if ssShift in uistate.keymod then
          uistate.kbditem := uistate.lastwidget;

        //Also clear the key so that next widget
        //won't process it
        uistate.keyentered := 0;
      end;
      VK_UP:
      begin
        //Slide slider up (if not at zero)
        if Value > 0 then
        begin
          Dec(Value);

          Result := 1;
          exit;
        end;
      end;
      VK_DOWN:
      begin
        //Slide slider down (if not at max)
        if Value < max then
        begin
          Inc(Value);

          Result := 1;
          exit;
        end;
      end;
    end;
  end;
  uistate.lastwidget := id;

  //Update widget value
  if uistate.activeitem = id then
  begin
    mousepos := uistate.mousey - (y + 8);

    if mousepos < 0 then
      mousepos := 0;

    if mousepos > 255 then
      mousepos := 255;

    v := (mousepos * max) div 255;

    if v <> Value then
    begin
      Value := v;

      Result := 1;
      exit;
    end;
  end;

  Result := 0;
end;

function GetTickCount: DWord;
begin
  Result := DWord(Trunc(Now * 24 * 60 * 60 * 1000));
end;

function textfield(id: integer; x: integer; y: integer; var buffer: string): integer;
var
  len: integer;
  changed: integer;
begin
  len := Length(buffer);
  changed := 0;

  //Check for hotness
  if regionhit(x - 4, y - 4, 30 * 14 + 8, 24 + 8) = 1 then
  begin
    uistate.hotitem := id;

    if (uistate.activeitem = 0) and (uistate.mousedown = 1) then
      uistate.activeitem := id;
  end;

  //If no widget has keyboard focus, take it
  if uistate.kbditem = 0 then
    uistate.kbditem := id;

  //If we have keyboard focus, show it
  if uistate.kbditem = id then
    drawrect(x - 6, y - 6, 30 * 14 + 12, 24 + 12, GLColor($ff0000));

  //Render the text field
  if (uistate.activeitem = id) or (uistate.hotitem = id) then
    drawrect(x - 4, y - 4, 30 * 14 + 8, 24 + 8, GLColor($aaaaaa))
  else
    drawrect(x - 4, y - 4, 30 * 14 + 8, 24 + 8, GLColor($777777));

  glColor3ub($00, $00, $00);
  drawstring(buffer, x, y);

  //Render cursor if we have keyboard focus
  if (uistate.kbditem = id) and (((GetTickCount shr 8) and 1) = 1) then
    drawstring('_', x + len * 14, y);

  //If we have keyboard focus, we'll need to process the keys
  if uistate.kbditem = id then
  begin
    case uistate.keyentered of
      VK_TAB:
      begin
        //If tab is pressed, lose keyboard focus.
        //Next widget will grab the focus.
        uistate.kbditem := 0;

        //If shift was also pressed, we want to move focus
        //to the previous widget instead.
        if ssShift in uistate.keymod then
          uistate.kbditem := uistate.lastwidget;

        uistate.keyentered := 0;
      end;
      VK_BACK:
      begin
        //Also clear the key so that next widget
        //won't process it
        if len > 0 then
        begin
          Delete(buffer, len, 1);
          Dec(len);
          changed := 1;
        end;
      end;
    end;

    if (uistate.keychar >= 32) and (uistate.keychar < 127) and (len < 30) then
    begin
      buffer := buffer + Chr(uistate.keyentered);
      Inc(len);
      changed := 1;
    end;
  end;

  //If button is hot and active, but mouse button is not
  //down, the user must have clicked the widget; give it
  //keyboard focus.
  if (uistate.mousedown = 0) and (uistate.hotitem = id) and (uistate.activeitem = id) then
    uistate.kbditem := id;

  uistate.lastwidget := id;

  Result := changed;
end;

procedure imgui_prepare;
begin
  uistate.hotitem := 0;
  genid := 0;
end;

procedure imgui_finish;
begin
  if uistate.mousedown = 0 then
    uistate.activeitem := 0
  else
    if uistate.activeitem = 0 then
      uistate.activeitem := -1;

  //If no widget grabbed tab, clear focus
  if uistate.keyentered = VK_TAB then
    uistate.kbditem := 0;

  //Clear the entered key
  uistate.keyentered := 0;
  uistate.keychar := 0;
end;

function GEN_ID: integer;
begin
  Inc(genid);
  Result := genid;
end;

//Rendering function
procedure render;
var
  slidervalue: integer;
begin
  imgui_prepare;

  button(GEN_ID, 50, 50);

  button(GEN_ID, 150, 50);

  if button(GEN_ID, 50, 150) = 1 then
    bgcolor := Round(Random * $ffffff);

  if button(GEN_ID, 150, 150) = 1 then
    halt(0);

  textfield(GEN_ID, 50, 250, sometext);

  slidervalue := bgcolor and $ff;
  if slider(GEN_ID, 500, 40, 255, slidervalue) = 1 then
    bgcolor := (bgcolor and $ffff00) or slidervalue;

  slidervalue := ((bgcolor shr 10) and $3f);
  if slider(GEN_ID, 550, 40, 63, slidervalue) = 1 then
    bgcolor := (bgcolor and $ff00ff) or (slidervalue shl 10);

  slidervalue := ((bgcolor shr 20) and $f);
  if slider(GEN_ID, 600, 40, 15, slidervalue) = 1 then
    bgcolor := (bgcolor and $00ffff) or (slidervalue shl 20);

  imgui_finish;
end;

{ TForm1 }

procedure TForm1.OpenGLControl1Paint(Sender: TObject);
begin
  //setup 2D projection
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity;
  glOrtho(0, OpenGLControl1.Width, OpenGLControl1.Height, 0, 0, 1);
  glMatrixMode(GL_MODELVIEW);

  //clear screen
  drawrect(0, 0, 640, 480, GLColor(bgcolor));

  render;

  glColor3ub($ff, $00, $00);
  glTextOut(10, 20, 0, 10, 10, 1, 0, Format('%f FPS', [1000 / OpenGLControl1.FrameDiffTimeInMSecs]));

  OpenGLControl1.SwapBuffers;
end;

procedure TForm1.Timer1Timer(Sender: TObject);
begin
  OpenGLControl1.Paint;
end;

procedure TForm1.OpenGLControl1MouseMove(Sender: TObject; Shift: TShiftState; X, Y: integer);
begin
  uistate.SetMousePos(X, Y);
end;

procedure TForm1.OpenGLControl1MouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: integer);
begin
  uistate.mousedown := 1;
end;

procedure TForm1.FormKeyDown(Sender: TObject; var Key: word; Shift: TShiftState);
begin
  uistate.keymod := Shift;
  uistate.keyentered := Key;
end;

procedure TForm1.FormKeyPress(Sender: TObject; var Key: char);
begin
  //if escape is pressed, quit the application
  if Ord(Key) = VK_ESCAPE then
    halt(0);

  uistate.keyentered := Ord(Key);

  //if key is ASCII, accept it as character input
  if (uistate.keyentered and $FF80) = 0 then
    uistate.keychar := uistate.keyentered and $7f;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  bgcolor := $77;
  sometext := 'Some text';
end;

procedure TForm1.OpenGLControl1MouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: integer);
begin
  uistate.mousedown := 0;
end;

initialization
  uistate.Init;

end.

