DEADSOFTWARE

changed license to GPLv3 only; sorry, no trust to FSF anymore
[d2df-sdl.git] / src / flexui / fui_gfx_gl.pas
index b0ccd2285e7af84a8ffa1f8e8444a1ebfca45378..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,19 @@ 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);
+    procedure glSetScaleTrans (ascale, ax, ay: Single);
+
   public
     property active: Boolean read mActive;
     property color: TGxRGBA read mColor write setColor;
@@ -125,15 +139,20 @@ type
 
 // set active context; `ctx` can be `nil`
 procedure gxSetContext (ctx: TGxContext; ascale: Single=1.0);
+procedure gxSetContextNoMatrix (ctx: TGxContext);
 
 
 // setup 2D OpenGL mode; will be called automatically in `glInit()`
 procedure oglSetup2D (winWidth, winHeight: Integer; upsideDown: Boolean=false);
+procedure oglSetup2DState (); // don't modify viewports and matrices
 
 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
@@ -142,6 +161,37 @@ var
 
 implementation
 
+uses
+  fui_wadread,
+  utils;
+
+
+// ////////////////////////////////////////////////////////////////////////// //
+// returns `false` if the color is transparent
+// returns `false` if the color is transparent
+function setupGLColor (constref clr: TGxRGBA): Boolean;
+begin
+  if (clr.a < 255) then
+  begin
+    glEnable(GL_BLEND);
+    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+  end
+  else
+  begin
+    glDisable(GL_BLEND);
+  end;
+  glColor4ub(clr.r, clr.g, clr.b, clr.a);
+  result := (clr.a <> 0);
+end;
+
+function isScaled (): Boolean;
+var
+  mt: packed array [0..15] of GLfloat;
+begin
+  glGetFloatv(GL_MODELVIEW_MATRIX, @mt[0]);
+  result := (mt[0] <> 1.0) or (mt[1*4+1] <> 1.0);
+end;
+
 
 // ////////////////////////////////////////////////////////////////////////// //
 //TODO: OpenGL framebuffers and shaders state
@@ -209,7 +259,9 @@ var
 
 // ////////////////////////////////////////////////////////////////////////// //
 // set active context; `ctx` can be `nil`
-procedure gxSetContext (ctx: TGxContext; ascale: Single=1.0);
+procedure gxSetContextInternal (ctx: TGxContext; ascale: Single; domatrix: Boolean);
+var
+  mt: packed array [0..15] of GLfloat;
 begin
   if (savedGLState.saved) then savedGLState.restore();
 
@@ -224,15 +276,30 @@ begin
   begin
     ctx.mActive := true;
     savedGLState.save();
-    oglSetup2D(fuiScrWdt, fuiScrHgt);
-    glScalef(ascale, ascale, 1.0);
-    ctx.mScaled := (ascale <> 1.0);
-    ctx.mScale := ascale;
+    if (domatrix) then
+    begin
+      oglSetup2D(fuiScrWdt, fuiScrHgt);
+      glScalef(ascale, ascale, 1.0);
+      ctx.mScaled := (ascale <> 1.0);
+      ctx.mScale := ascale;
+    end
+    else
+    begin
+      // assume uniform scale
+      glGetFloatv(GL_MODELVIEW_MATRIX, @mt[0]);
+      ctx.mScaled := (mt[0] <> 1.0) or (mt[1*4+1] <> 1.0);
+      ctx.mScale := mt[0];
+      oglSetup2DState();
+    end;
     ctx.onActivate();
   end;
 end;
 
 
+procedure gxSetContext (ctx: TGxContext; ascale: Single=1.0); begin gxSetContextInternal(ctx, ascale, true); end;
+procedure gxSetContextNoMatrix (ctx: TGxContext); begin gxSetContextInternal(ctx, 1, false); end;
+
+
 // ////////////////////////////////////////////////////////////////////////// //
 type
   TScissorSave = record
@@ -285,8 +352,6 @@ end;
 
 
 // ////////////////////////////////////////////////////////////////////////// //
-{$INCLUDE fui_gfx_gl_fonts.inc}
-
 type
   TGxBmpFont = class(TGxFont)
   private
@@ -295,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;
@@ -311,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;
@@ -350,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;
 
 
@@ -393,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;
@@ -472,6 +683,7 @@ begin
 end;
 
 
+{
 procedure deleteFonts ();
 var
   f: Integer;
@@ -479,16 +691,55 @@ begin
   for f := 0 to High(fontList) do freeAndNil(fontList[f]);
   fontList := nil;
 end;
+}
 
 
-procedure createFonts ();
+procedure fuiGfxLoadFont (const fontname: AnsiString; const fontFile: AnsiString; proportional: Boolean=false);
+var
+  st: TStream;
 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 '''+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 fuiGfxLoadFont (const fontname: AnsiString; st: TStream; proportional: Boolean=false);
+var
+  fnt: TGxBmpFont = nil;
+  f: Integer;
+begin
+  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;
 
 
@@ -509,10 +760,8 @@ end;
 
 
 // ////////////////////////////////////////////////////////////////////////// //
-procedure oglSetup2D (winWidth, winHeight: Integer; upsideDown: Boolean=false);
+procedure oglSetup2DState ();
 begin
-  glViewport(0, 0, winWidth, winHeight);
-
   glDisable(GL_BLEND);
   glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
   glDisable(GL_LINE_SMOOTH);
@@ -527,6 +776,17 @@ begin
   glDisable(GL_CULL_FACE);
   glDisable(GL_ALPHA_TEST);
 
+  glClearColor(0, 0, 0, 0);
+  glColor4f(1, 1, 1, 1);
+end;
+
+
+procedure oglSetup2D (winWidth, winHeight: Integer; upsideDown: Boolean=false);
+begin
+  glViewport(0, 0, winWidth, winHeight);
+
+  oglSetup2DState();
+
   glMatrixMode(GL_TEXTURE);
   glLoadIdentity();
 
@@ -546,9 +806,6 @@ begin
 
   glMatrixMode(GL_MODELVIEW);
   glLoadIdentity();
-
-  glClearColor(0, 0, 0, 0);
-  glColor4f(1, 1, 1, 1);
 end;
 
 
@@ -558,33 +815,6 @@ end;
 procedure oglDrawCursor (); begin oglDrawCursorAt(fuiMouseX, fuiMouseY); end;
 
 
-// ////////////////////////////////////////////////////////////////////////// //
-// returns `false` if the color is transparent
-// returns `false` if the color is transparent
-function setupGLColor (constref clr: TGxRGBA): Boolean;
-begin
-  if (clr.a < 255) then
-  begin
-    glEnable(GL_BLEND);
-    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
-  end
-  else
-  begin
-    glDisable(GL_BLEND);
-  end;
-  glColor4ub(clr.r, clr.g, clr.b, clr.a);
-  result := (clr.a <> 0);
-end;
-
-function mScaled (): Boolean;
-var
-  mt: packed array [0..15] of Double;
-begin
-  glGetDoublev(GL_MODELVIEW_MATRIX, @mt[0]);
-  result := (mt[0] <> 1.0) or (mt[1*4+1] <> 1.0);
-end;
-
-
 // ////////////////////////////////////////////////////////////////////////// //
 constructor TGxContext.Create ();
 begin
@@ -768,7 +998,7 @@ begin
       glVertex2f(x+len+0.375, y+0.375);
     glEnd();
   end
-  else
+  else if (mScale > 1.0) then
   begin
     glBegin(GL_QUADS);
       glVertex2i(x, y);
@@ -776,6 +1006,13 @@ begin
       glVertex2i(x+len, y+1);
       glVertex2i(x, y+1);
     glEnd();
+  end
+  else
+  begin
+    glPointSize(1);
+    glBegin(GL_POINTS);
+      while (len > 0) do begin glVertex2i(x, y); Inc(x); Dec(len); end;
+    glEnd();
   end;
 end;
 
@@ -792,7 +1029,7 @@ begin
       glVertex2f(x+0.375, y+len+0.375);
     glEnd();
   end
-  else
+  else if (mScale > 1.0) then
   begin
     glBegin(GL_QUADS);
       glVertex2i(x, y);
@@ -800,33 +1037,18 @@ begin
       glVertex2i(x+1, y+len);
       glVertex2i(x+1, y);
     glEnd();
-  end;
-end;
-
-
-procedure TGxContext.rect (x, y, w, h: Integer);
-  procedure hlinex (x, y, len: Integer);
+  end
+  else
   begin
-    if (len < 1) then exit;
-    glBegin(GL_QUADS);
-      glVertex2i(x, y);
-      glVertex2i(x+len, y);
-      glVertex2i(x+len, y+1);
-      glVertex2i(x, y+1);
+    glPointSize(1);
+    glBegin(GL_POINTS);
+      while (len > 0) do begin glVertex2i(x, y); Inc(y); Dec(len); end;
     glEnd();
   end;
+end;
 
-  procedure vlinex (x, y, len: Integer);
-  begin
-    if (len < 1) then exit;
-    glBegin(GL_QUADS);
-      glVertex2i(x, y);
-      glVertex2i(x, y+len);
-      glVertex2i(x+1, y+len);
-      glVertex2i(x+1, y);
-    glEnd();
-  end;
 
+procedure TGxContext.rect (x, y, w, h: Integer);
 begin
   if (not mActive) or (mClipRect.w < 1) or (mClipRect.h < 1) or (mColor.a = 0) then exit;
   if (w < 0) or (h < 0) then exit;
@@ -851,10 +1073,10 @@ begin
     end
     else
     begin
-      hlinex(x, y, w);
-      hlinex(x, y+h-1, w);
-      vlinex(x, y+1, h-2);
-      vlinex(x+w-1, y+1, h-2);
+      hline(x, y, w);
+      hline(x, y+h-1, w);
+      vline(x, y+1, h-2);
+      vline(x+w-1, y+1, h-2);
     end;
   end;
 end;
@@ -917,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
@@ -928,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);
@@ -973,26 +1238,143 @@ 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;
 
 
+procedure TGxContext.glSetScale (ascale: Single);
+begin
+  if (ascale < 0.01) then ascale := 0.01;
+  glLoadIdentity();
+  glScalef(ascale, ascale, 1.0);
+  mScale := ascale;
+  mScaled := (ascale <> 1.0);
+end;
+
+procedure TGxContext.glSetTrans (ax, ay: Single);
+begin
+  glLoadIdentity();
+  glScalef(mScale, mScale, 1.0);
+  glTranslatef(ax, ay, 0);
+end;
+
+
+procedure TGxContext.glSetScaleTrans (ascale, ax, ay: Single);
+begin
+  glSetScale(ascale);
+  glTranslatef(ax, ay, 0);
+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);
@@ -1028,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;
@@ -1057,7 +1439,7 @@ end;
 // ////////////////////////////////////////////////////////////////////////// //
 initialization
   savedGLState := TSavedGLState.Create(false);
-  createFonts();
+  //createFonts();
   //winFocusCB := onWinFocus;
   //winBlurCB := onWinBlur;
   //prerenderFrameCB := onPreRender;