DEADSOFTWARE

changed license to GPLv3 only; sorry, no trust to FSF anymore
[d2df-sdl.git] / src / flexui / fui_gfx_gl.pas
index f70e78967a753b4255e82716058fc49e04123a5d..e543fc0e7023a9700a08ddf705f83f439aa8958c 100644 (file)
@@ -3,8 +3,7 @@
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
+ * the Free Software Foundation, version 3 of the License ONLY.
  *
  * This program is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  * along with this program. If not, see <http://www.gnu.org/licenses/>.
  *)
 {$INCLUDE ../shared/a_modes.inc}
+{$DEFINE FUI_TEXT_ICONS}
 unit fui_gfx_gl;
 
 interface
 
 uses
+  {$INCLUDE ../nogl/noGLuses.inc}
   SysUtils, Classes,
-  GL, GLExt, SDL2,
+  SDL2,
   sdlcarcass,
   fui_common, fui_events;
 
@@ -114,6 +115,14 @@ type
 
     function combineClip (constref aclip: TGxRect): TGxRect; // returns previous clip
 
+    // vertical scrollbar
+    procedure drawVSBar (x, y, wdt, hgt: Integer; cur, min, max: Integer; constref clrfull, clrempty: TGxRGBA);
+    // horizontal scrollbar
+    procedure drawHSBar (x, y, wdt, hgt: Integer; cur, min, max: Integer; constref clrfull, clrempty: TGxRGBA);
+
+    class function sbarFilled (wh: Integer; cur, min, max: Integer): Integer;
+    class function sbarPos (cxy: Integer; xy, wh: Integer; min, max: Integer): Integer;
+
   public //HACK!
     procedure glSetScale (ascale: Single);
     procedure glSetTrans (ax, ay: Single);
@@ -141,6 +150,9 @@ procedure oglDrawCursor ();
 procedure oglDrawCursorAt (msX, msY: Integer);
 
 
+procedure fuiGfxLoadFont (const fontname: AnsiString; const fontFile: AnsiString; proportional: Boolean=false);
+procedure fuiGfxLoadFont (const fontname: AnsiString; st: TStream; proportional: Boolean=false);
+
 
 // ////////////////////////////////////////////////////////////////////////// //
 var
@@ -149,6 +161,10 @@ var
 
 implementation
 
+uses
+  fui_wadread,
+  utils;
+
 
 // ////////////////////////////////////////////////////////////////////////// //
 // returns `false` if the color is transparent
@@ -170,9 +186,9 @@ end;
 
 function isScaled (): Boolean;
 var
-  mt: packed array [0..15] of Double;
+  mt: packed array [0..15] of GLfloat;
 begin
-  glGetDoublev(GL_MODELVIEW_MATRIX, @mt[0]);
+  glGetFloatv(GL_MODELVIEW_MATRIX, @mt[0]);
   result := (mt[0] <> 1.0) or (mt[1*4+1] <> 1.0);
 end;
 
@@ -245,7 +261,7 @@ var
 // set active context; `ctx` can be `nil`
 procedure gxSetContextInternal (ctx: TGxContext; ascale: Single; domatrix: Boolean);
 var
-  mt: packed array [0..15] of Double;
+  mt: packed array [0..15] of GLfloat;
 begin
   if (savedGLState.saved) then savedGLState.restore();
 
@@ -270,7 +286,7 @@ begin
     else
     begin
       // assume uniform scale
-      glGetDoublev(GL_MODELVIEW_MATRIX, @mt[0]);
+      glGetFloatv(GL_MODELVIEW_MATRIX, @mt[0]);
       ctx.mScaled := (mt[0] <> 1.0) or (mt[1*4+1] <> 1.0);
       ctx.mScale := mt[0];
       oglSetup2DState();
@@ -336,8 +352,6 @@ end;
 
 
 // ////////////////////////////////////////////////////////////////////////// //
-{$INCLUDE fui_gfx_gl_fonts.inc}
-
 type
   TGxBmpFont = class(TGxFont)
   private
@@ -346,15 +360,20 @@ type
     mFontBmp: PByte;
     mFontWdt: PByte;
     mFreeFontWdt: Boolean;
+    mFreeFontBmp: Boolean;
 
   protected
     procedure oglCreateTexture ();
     procedure oglDestroyTexture ();
 
+    procedure initDrawText ();
+    procedure doneDrawText ();
+    function drawCharInterim (x, y: Integer; const ch: AnsiChar): Integer; // return width (not including last empty pixel)
+    function drawCharInternal (x, y: Integer; const ch: AnsiChar): Integer; // return width (not including last empty pixel)
     function drawTextInternal (x, y: Integer; const s: AnsiString): Integer; // return width (not including last empty pixel)
 
   public
-    constructor Create (const aname: AnsiString; awdt, ahgt: Integer; const afont: PByte; const awdtable: PByte=nil);
+    constructor Create (const aname: AnsiString; st: TStream; proportional: Boolean);
     destructor Destroy (); override;
 
     function charWidth (const ch: AnsiChar): Integer; override;
@@ -362,37 +381,77 @@ type
   end;
 
 
-constructor TGxBmpFont.Create (const aname: AnsiString; awdt, ahgt: Integer; const afont: PByte; const awdtable: PByte=nil);
+constructor TGxBmpFont.Create (const aname: AnsiString; st: TStream; proportional: Boolean);
 var
-  c: Integer;
+  sign: packed array [0..7] of AnsiChar;
+  enc: packed array [0..16] of AnsiChar;
+  b: Byte;
+  wdt, hgt, elen: Integer;
+  ch, dy: Integer;
+  fntbwdt: Integer;
+  wrd: Word;
 begin
-  if (afont = nil) then raise Exception.Create('internal error in font creation');
-  if (ahgt < 1) then raise Exception.Create('internal error in font creation');
-  if (awdt > 0) then
+  mFreeFontBmp := true;
+  mFreeFontWdt := true;
+  mName := aname;
+  mTexId := 0;
+  // signature
+  st.ReadBuffer(sign[0], 8);
+  if (sign <> 'FUIFONT0') then raise Exception.Create('FlexUI: invalid font file signature');
+  // encoding length and width
+  st.ReadBuffer(b, 1);
+  wdt := (b and $0f)+1; // 16 is not supported
+  if (wdt = 16) then raise Exception.Create('FlexUI: 16-wdt fonts aren''t supported yet');
+  elen := ((b shr 4) and $0f);
+  if (elen = 0) then raise Exception.CreateFmt('FlexUI: invalid font encoding length: %d', [elen]);
+  // height
+  st.ReadBuffer(b, 1);
+  hgt := b;
+  if (hgt < 2) then raise Exception.CreateFmt('FlexUI: invalid font height: %d', [hgt]);
+  // encoding
+  st.ReadBuffer(enc[0], elen);
+  // check for 'cp1251' here (it can also be 'koi8')
+  if (wdt <= 8) then fntbwdt := 1 else fntbwdt := 2;
+  // shift and width table (hi nibble: left shift for proportional print; lo nibble: shifted character width for proportional print)
+  GetMem(mFontWdt, 256);
+  st.ReadBuffer(mFontWdt^, 256);
+  // font bitmap
+  GetMem(mFontBmp, (hgt*fntbwdt)*256);
+  st.ReadBuffer(mFontBmp^, (hgt*fntbwdt)*256);
+  mWidth := wdt;
+  mHeight := hgt;
+  mBaseLine := hgt-1; //FIXME
+  if (proportional) then
   begin
-    //if (awdtable <> nil) then raise Exception.Create('internal error in font creation');
-    mFreeFontWdt := true;
-    // create width table
-    GetMem(mFontWdt, 256);
-    for c := 0 to 255 do mFontWdt[c] := awdt-1;
+    // shift font
+    for ch := 0 to 255 do
+    begin
+      for dy := 0 to hgt-1 do
+      begin
+        if (fntbwdt = 1) then
+        begin
+          mFontBmp[ch*hgt+dy] := mFontBmp[ch*hgt+dy] shl (mFontWdt[ch] shr 4);
+        end
+        else
+        begin
+          wrd := mFontBmp[ch*(hgt*2)+(dy*2)]+256*mFontBmp[ch*(hgt*2)+(dy*2)+1];
+          wrd := wrd shl (mFontWdt[ch] shr 4);
+          mFontBmp[ch*(hgt*2)+(dy*2)+0] := (wrd and $ff);
+          mFontBmp[ch*(hgt*2)+(dy*2)+1] := ((wrd shr 16) and $ff);
+        end;
+      end;
+    end;
   end
   else
   begin
-    if (awdtable = nil) then raise Exception.Create('internal error in font creation');
-    awdt := 0;
-    mFontWdt := awdtable;
+    FillChar(mFontWdt^, 256, wdt);
   end;
-  mName := aname;
-  mWidth := awdt;
-  mHeight := ahgt;
-  mBaseLine := ahgt-1; //FIXME
-  mFontBmp := afont;
-  mTexId := 0;
 end;
 
 
 destructor TGxBmpFont.Destroy ();
 begin
+  if (mFreeFontBmp) and (mFontBmp <> nil) then FreeMem(mFontBmp);
   if (mFreeFontWdt) and (mFontWdt <> nil) then FreeMem(mFontWdt);
   mName := '';
   mWidth := 0;
@@ -401,14 +460,91 @@ begin
   mFontBmp := nil;
   mFontWdt := nil;
   mFreeFontWdt := false;
+  mFreeFontBmp := false;
   mTexId := 0;
   inherited;
 end;
 
 
 procedure TGxBmpFont.oglCreateTexture ();
+const
+  TxWidth = 16*16;
+  TxHeight = 16*16;
+var
+  tex, tpp: PByte;
+  b: Byte;
+  cc: Integer;
+  x, y, dx, dy: Integer;
 begin
-  mTexId := createFontTexture(mFontBmp, mFontWdt, (mWidth <= 0));
+  GetMem(tex, TxWidth*TxHeight*4);
+  FillChar(tex^, TxWidth*TxHeight*4, 0);
+
+  for cc := 0 to 255 do
+  begin
+    x := (cc mod 16)*16;
+    y := (cc div 16)*16;
+    for dy := 0 to mHeight-1 do
+    begin
+      if (mWidth <= 8) then b := mFontBmp[cc*mHeight+dy] else b := mFontBmp[cc*(mHeight*2)+(dy*2)+1];
+      //if prop then b := b shl (fontwdt[cc] shr 4);
+      tpp := tex+((y+dy)*(TxWidth*4))+x*4;
+      for dx := 0 to 7 do
+      begin
+        if ((b and $80) <> 0) then
+        begin
+          tpp^ := 255; Inc(tpp);
+          tpp^ := 255; Inc(tpp);
+          tpp^ := 255; Inc(tpp);
+          tpp^ := 255; Inc(tpp);
+        end
+        else
+        begin
+          tpp^ := 0; Inc(tpp);
+          tpp^ := 0; Inc(tpp);
+          tpp^ := 0; Inc(tpp);
+          tpp^ := 0; Inc(tpp);
+        end;
+        b := (b and $7f) shl 1;
+      end;
+      if (mWidth > 8) then
+      begin
+        b := mFontBmp[cc*(mHeight*2)+(dy*2)+0];
+        for dx := 0 to 7 do
+        begin
+          if ((b and $80) <> 0) then
+          begin
+            tpp^ := 255; Inc(tpp);
+            tpp^ := 255; Inc(tpp);
+            tpp^ := 255; Inc(tpp);
+            tpp^ := 255; Inc(tpp);
+          end
+          else
+          begin
+            tpp^ := 0; Inc(tpp);
+            tpp^ := 0; Inc(tpp);
+            tpp^ := 0; Inc(tpp);
+            tpp^ := 0; Inc(tpp);
+          end;
+          b := (b and $7f) shl 1;
+        end;
+      end;
+    end;
+  end;
+
+  glGenTextures(1, @mTexId);
+  if (mTexId = 0) then raise Exception.Create('can''t create FlexUI font texture');
+
+  glBindTexture(GL_TEXTURE_2D, mTexId);
+  glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
+  glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
+  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+
+  glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, TxWidth, TxHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, tex);
+  glFinish();
+
+  glBindTexture(GL_TEXTURE_2D, 0);
+  FreeMem(tex);
 end;
 
 
@@ -444,45 +580,69 @@ begin
 end;
 
 
-// return width (not including last empty pixel)
-function TGxBmpFont.drawTextInternal (x, y: Integer; const s: AnsiString): Integer;
-var
-  ch: AnsiChar;
-  tx, ty: Integer;
+procedure TGxBmpFont.initDrawText ();
 begin
-  if (Length(s) = 0) then begin result := 0; exit; end;
-
-  result := -1;
-
   glEnable(GL_ALPHA_TEST);
   glAlphaFunc(GL_NOTEQUAL, 0.0);
   glEnable(GL_TEXTURE_2D);
   glBindTexture(GL_TEXTURE_2D, mTexId);
+end;
 
-  for ch in s do
-  begin
-    tx := (Integer(ch) mod 16)*8;
-    ty := (Integer(ch) div 16)*8;
-    glBegin(GL_QUADS);
-      glTexCoord2f((tx+0)/128.0, (ty+0)/128.0); glVertex2i(x+0, y+0); // top-left
-      glTexCoord2f((tx+8)/128.0, (ty+0)/128.0); glVertex2i(x+8, y+0); // top-right
-      glTexCoord2f((tx+8)/128.0, (ty+8)/128.0); glVertex2i(x+8, y+8); // bottom-right
-      glTexCoord2f((tx+0)/128.0, (ty+8)/128.0); glVertex2i(x+0, y+8); // bottom-left
-    glEnd();
-    x += (mFontWdt[Byte(ch)] and $0f)+1;
-    result += (mFontWdt[Byte(ch)] and $0f)+1;
-  end;
 
+procedure TGxBmpFont.doneDrawText ();
+begin
   glDisable(GL_ALPHA_TEST);
   glDisable(GL_TEXTURE_2D);
   glBindTexture(GL_TEXTURE_2D, 0);
 end;
 
 
+function TGxBmpFont.drawCharInterim (x, y: Integer; const ch: AnsiChar): Integer;
+var
+  tx, ty: Integer;
+begin
+  tx := (Integer(ch) mod 16)*16;
+  ty := (Integer(ch) div 16)*16;
+  glBegin(GL_QUADS);
+    glTexCoord2f((tx+0)/256.0, (ty+0)/256.0); glVertex2i(x+0, y+0); // top-left
+    glTexCoord2f((tx+mWidth)/256.0, (ty+0)/256.0); glVertex2i(x+mWidth, y+0); // top-right
+    glTexCoord2f((tx+mWidth)/256.0, (ty+mHeight)/256.0); glVertex2i(x+mWidth, y+mHeight); // bottom-right
+    glTexCoord2f((tx+0)/256.0, (ty+mHeight)/256.0); glVertex2i(x+0, y+mHeight); // bottom-left
+  glEnd();
+  result := (mFontWdt[Byte(ch)] and $0f);
+end;
+
+
+function TGxBmpFont.drawCharInternal (x, y: Integer; const ch: AnsiChar): Integer;
+begin
+  initDrawText();
+  result := drawCharInterim(x, y, ch);
+  doneDrawText();
+end;
+
+
+function TGxBmpFont.drawTextInternal (x, y: Integer; const s: AnsiString): Integer;
+var
+  ch: AnsiChar;
+  wdt: Integer;
+begin
+  if (Length(s) = 0) then begin result := 0; exit; end;
+  result := -1;
+  initDrawText();
+  for ch in s do
+  begin
+    wdt := drawCharInterim(x, y, ch)+1;
+    x += wdt;
+    result += wdt;
+  end;
+  doneDrawText();
+end;
+
+
 // ////////////////////////////////////////////////////////////////////////// //
 var
   fontList: array of TGxBmpFont = nil;
-  defaultFontName: AnsiString = 'dos';
+  defaultFontName: AnsiString = 'win14';
 
 
 function strEquCI (const s0, s1: AnsiString): Boolean;
@@ -523,6 +683,7 @@ begin
 end;
 
 
+{
 procedure deleteFonts ();
 var
   f: Integer;
@@ -530,16 +691,55 @@ begin
   for f := 0 to High(fontList) do freeAndNil(fontList[f]);
   fontList := nil;
 end;
+}
+
+
+procedure fuiGfxLoadFont (const fontname: AnsiString; const fontFile: AnsiString; proportional: Boolean=false);
+var
+  st: TStream;
+begin
+  if (Length(fontname) = 0) then raise Exception.Create('FlexUI: cannot load nameless font '''+fontFile+'''');
+  st := fuiOpenFile(fontFile);
+  if (st = nil) then raise Exception.Create('FlexUI: cannot load font '''+fontFile+'''');
+  try
+    fuiGfxLoadFont(fontname, st, proportional);
+  except on e: Exception do
+    begin
+      writeln('FlexUI font loadin error: ', e.message);
+      FreeAndNil(st);
+      raise Exception.Create('FlexUI: cannot load font '''+fontFile+'''');
+    end;
+  else
+    raise;
+  end;
+  FreeAndNil(st);
+end;
 
 
-procedure createFonts ();
+procedure fuiGfxLoadFont (const fontname: AnsiString; st: TStream; proportional: Boolean=false);
+var
+  fnt: TGxBmpFont = nil;
+  f: Integer;
 begin
-  deleteFonts();
-  SetLength(fontList, 4);
-  fontList[0] := TGxBmpFont.Create('dos', 8, 8, @kgiFont8[0], @kgiFont8PropWidth[0]);
-  fontList[1] := TGxBmpFont.Create('dos-prop', 0, 8, @kgiFont8[0], @kgiFont8PropWidth[0]);
-  fontList[2] := TGxBmpFont.Create('msx', 6, 8, @kgiFont6[0], @kgiFont6PropWidth[0]);
-  fontList[3] := TGxBmpFont.Create('msx-prop', 0, 8, @kgiFont6[0], @kgiFont6PropWidth[0]);
+  if (Length(fontname) = 0) then raise Exception.Create('FlexUI: cannot load nameless font');
+  fnt := TGxBmpFont.Create(fontname, st, proportional);
+  try
+    for f := 0 to High(fontList) do
+    begin
+      if (strEquCI(fontList[f].name, fontname)) then
+      begin
+        if (fontList[f].mTexId <> 0) then raise Exception.Create('FlexUI: cannot reload generated font named '''+fontname+'''');
+        FreeAndNil(fontList[f]);
+        fontList[f] := fnt;
+        exit;
+      end;
+    end;
+    SetLength(fontList, Length(fontList)+1);
+    fontList[High(fontList)] := fnt;
+  except
+    FreeAndNil(fnt);
+    raise;
+  end;
 end;
 
 
@@ -939,7 +1139,7 @@ function TGxContext.drawChar (x, y: Integer; const ch: AnsiChar): Integer; // re
 begin
   result := mFont.charWidth(ch);
   if (not mActive) or (mClipRect.w < 1) or (mClipRect.h < 1) or (mColor.a = 0) then exit;
-  TGxBmpFont(mFont).drawTextInternal(x, y, ch);
+  TGxBmpFont(mFont).drawCharInternal(x, y, ch);
 end;
 
 function TGxContext.drawText (x, y: Integer; const s: AnsiString): Integer; // returns text width
@@ -950,14 +1150,57 @@ begin
 end;
 
 
-function TGxContext.iconMarkWidth (ic: TMarkIcon): Integer; begin result := 11; end;
-function TGxContext.iconMarkHeight (ic: TMarkIcon): Integer; begin result := 8; end;
+function TGxContext.iconMarkWidth (ic: TMarkIcon): Integer;
+begin
+  {$IFDEF FUI_TEXT_ICONS}
+  case ic of
+    TMarkIcon.Checkbox: result := textWidth('[x]');
+    TMarkIcon.Radiobox: result := textWidth('(*)');
+    else result := textWidth('[x]');
+  end;
+  {$ELSE}
+  result := 11;
+  {$ENDIF}
+end;
+
+function TGxContext.iconMarkHeight (ic: TMarkIcon): Integer;
+begin
+  {$IFDEF FUI_TEXT_ICONS}
+  case ic of
+    TMarkIcon.Checkbox: result := textHeight('[x]');
+    TMarkIcon.Radiobox: result := textHeight('(*)');
+    else result := textHeight('[x]');
+  end;
+  {$ELSE}
+  result := 8;
+  {$ENDIF}
+end;
 
 procedure TGxContext.drawIconMark (ic: TMarkIcon; x, y: Integer; marked: Boolean);
 var
+  {$IFDEF FUI_TEXT_ICONS}
+  xstr: AnsiString;
+  {$ELSE}
   f: Integer;
+  {$ENDIF}
 begin
   if (not mActive) or (mClipRect.w < 1) or (mClipRect.h < 1) or (mColor.a = 0) then exit;
+  {$IFDEF FUI_TEXT_ICONS}
+  case ic of
+    TMarkIcon.Checkbox: xstr := '[x]';
+    TMarkIcon.Radiobox: xstr := '(*)';
+    else exit;
+  end;
+  if (marked) then
+  begin
+    drawText(x, y, xstr);
+  end
+  else
+  begin
+    drawChar(x, y, xstr[1]);
+    drawChar(x+textWidth(xstr)-charWidth(xstr[3]), y, xstr[3]);
+  end;
+  {$ELSE}
   if (ic = TMarkIcon.Checkbox) then
   begin
     vline(x, y, 7);
@@ -995,23 +1238,61 @@ begin
         hline(x+4, y+5, 3);
       end;
   end;
+  {$ENDIF}
 end;
 
 
-function TGxContext.iconWinWidth (ic: TWinIcon): Integer; begin result := 9; end;
-function TGxContext.iconWinHeight (ic: TWinIcon): Integer; begin result := 8; end;
+function TGxContext.iconWinWidth (ic: TWinIcon): Integer;
+begin
+  {$IFDEF FUI_TEXT_ICONS}
+  case ic of
+    TWinIcon.Close: result := nmax(textWidth('[x]'), textWidth('[#]'));
+    else result := nmax(textWidth('[x]'), textWidth('[#]'));
+  end;
+  {$ELSE}
+  result := 9;
+  {$ENDIF}
+end;
+
+function TGxContext.iconWinHeight (ic: TWinIcon): Integer;
+begin
+  {$IFDEF FUI_TEXT_ICONS}
+  case ic of
+    TWinIcon.Close: result := nmax(textHeight('[x]'), textHeight('[#]'));
+    else result := nmax(textHeight('[x]'), textHeight('[#]'));
+  end;
+  {$ELSE}
+  result := 8;
+  {$ENDIF}
+end;
 
 procedure TGxContext.drawIconWin (ic: TWinIcon; x, y: Integer; pressed: Boolean);
 var
+  {$IFDEF FUI_TEXT_ICONS}
+  xstr: AnsiString;
+  wdt: Integer;
+  {$ELSE}
   f: Integer;
+  {$ENDIF}
 begin
   if (not mActive) or (mClipRect.w < 1) or (mClipRect.h < 1) or (mColor.a = 0) then exit;
+  {$IFDEF FUI_TEXT_ICONS}
+  case ic of
+    TWinIcon.Close: if (pressed) then xstr := '[#]' else xstr := '[x]';
+    else exit;
+  end;
+  wdt := nmax(textWidth('[x]'), textWidth('[#]'));
+  drawChar(x, y, xstr[1]);
+  drawChar(x+wdt-charWidth(xstr[3]), y, xstr[3]);
+  drawChar(x+((wdt-charWidth(xstr[2])) div 2), y, xstr[2]);
+  {$ELSE}
   if pressed then rect(x, y, 9, 8);
   for f := 1 to 5 do
   begin
     vline(x+1+f, y+f, 1);
     vline(x+1+6-f, y+f, 1);
   end;
+  {$ENDIF}
 end;
 
 
@@ -1039,6 +1320,61 @@ begin
 end;
 
 
+// vertical scroll bar
+procedure TGxContext.drawVSBar (x, y, wdt, hgt: Integer; cur, min, max: Integer; constref clrfull, clrempty: TGxRGBA);
+var
+  filled: Integer;
+begin
+  if (wdt < 1) or (hgt < 1) then exit;
+  filled := sbarFilled(hgt, cur, min, max);
+  color := clrfull;
+  fillRect(x, y, wdt, filled);
+  color := clrempty;
+  fillRect(x, y+filled, wdt, hgt-filled);
+end;
+
+
+// horizontal scrollbar
+procedure TGxContext.drawHSBar (x, y, wdt, hgt: Integer; cur, min, max: Integer; constref clrfull, clrempty: TGxRGBA);
+var
+  filled: Integer;
+begin
+  if (wdt < 1) or (hgt < 1) then exit;
+  filled := sbarFilled(wdt, cur, min, max);
+  color := clrfull;
+  fillRect(x, y, filled, hgt);
+  color := clrempty;
+  fillRect(x+filled, y, wdt-filled, hgt);
+end;
+
+
+class function TGxContext.sbarFilled (wh: Integer; cur, min, max: Integer): Integer;
+begin
+       if (wh < 1) then result := 0
+  else if (min > max) then result := 0
+  else if (min = max) then result := wh
+  else
+  begin
+    if (cur < min) then cur := min else if (cur > max) then cur := max;
+    result := wh*(cur-min) div (max-min);
+  end;
+end;
+
+
+class function TGxContext.sbarPos (cxy: Integer; xy, wh: Integer; min, max: Integer): Integer;
+begin
+  if (wh < 1) then begin result := 0; exit; end;
+  if (min > max) then begin result := 0; exit; end;
+  if (min = max) then begin result := max; exit; end;
+  if (cxy < xy) then begin result := min; exit; end;
+  if (cxy >= xy+wh) then begin result := max; exit; end;
+  result := min+((max-min)*(cxy-xy) div wh);
+  assert((result >= min) and (result <= max));
+end;
+
+
+
+
 // ////////////////////////////////////////////////////////////////////////// //
 (*
 procedure oglRestoreMode (doClear: Boolean);
@@ -1074,8 +1410,8 @@ end;
 *)
 
 
-//procedure onWinFocus (); begin end;
-//procedure onWinBlur (); begin fuiResetKMState(true); end;
+//procedure onWinFocus (); begin uiFocus(); end;
+//procedure onWinBlur (); begin fuiResetKMState(true); uiBlur(); end;
 
 //procedure onPreRender (); begin oglRestoreMode(gGfxDoClear); end;
 procedure onPostRender (); begin oglDrawCursor(); end;
@@ -1103,7 +1439,7 @@ end;
 // ////////////////////////////////////////////////////////////////////////// //
 initialization
   savedGLState := TSavedGLState.Create(false);
-  createFonts();
+  //createFonts();
   //winFocusCB := onWinFocus;
   //winBlurCB := onWinBlur;
   //prerenderFrameCB := onPreRender;