DEADSOFTWARE

changed license to GPLv3 only; sorry, no trust to FSF anymore
[d2df-sdl.git] / src / flexui / fui_events.pas
index a5fa38606137b0f938bf9fd09cdc9991faa28683..b5785b70743e37ed4f7320b45a6b1040531277c0 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
@@ -26,8 +25,16 @@ uses
 
 // ////////////////////////////////////////////////////////////////////////// //
 type
-  THMouseEvent = record
+  TFUIEvent = packed record
   public
+    // keyboard modifiers
+    const
+      ModCtrl = $0001;
+      ModAlt = $0002;
+      ModShift = $0004;
+      ModHyper = $0008;
+
+    // mouse buttons
     const
       // both for but and for bstate
       None = 0;
@@ -39,73 +46,82 @@ type
 
     // event types
     type
-      TKind = (Release, Press, Motion);
+      TType = (Key, Mouse, User);
+      TKind = (Release, Press, Motion, SimpleChar, Other);
+      // SimpleChar: keyboard event with `ch`, but without `scan` (it is zero)
+
+    // event state
+    type
+      TState = (
+        None, // or "mine"
+        Sinking,
+        Bubbling,
+        Eaten,
+        Cancelled
+      );
 
   private
-    mEaten: Boolean;
-    mCancelled: Boolean;
+    mType: TType; // event type: keyboard, mouse, etc...
+    mKind: TKind; // motion, press, release
+    mState: TState;
+
+    function getEaten (): Boolean; inline;
+    function getCancelled (): Boolean; inline;
+    function getNoState (): Boolean; inline;
+    function getSinking (): Boolean; inline;
+    function getBubbling (): Boolean; inline;
 
   public
-    kind: TKind; // motion, press, release
     x, y: Integer; // current mouse position
     dx, dy: Integer; // for wheel this is wheel motion, otherwise this is relative mouse motion
-    but: Word; // current pressed/released button, or 0 for motion
     bstate: Word; // button state BEFORE event (i.e. press/release modifications aren't done yet)
-    kstate: Word; // keyboard state (see THKeyEvent);
+    kstate: Word; // keyboard state (see TFUIKeyEvent);
+    // mouse events
+    but: Word; // current pressed/released button, or 0 for motion
+    // keyboard events
+    scan: Word; // SDL_SCANCODE_XXX or 0 for character event
+    ch: AnsiChar; // converted to 1251; can be #0
+    // user tags
+    itag: Integer;
+    ptag: Pointer;
 
   public
-    procedure intrInit (); inline; // init hidden fields
+    // initial state is "None"
+    constructor Create (atype: TType; akind: TKind);
+
+    // event type checkers
+    function mouse (): Boolean; inline;
+    function key (): Boolean; inline;
+    function user (): Boolean; inline;
 
     function press (): Boolean; inline;
     function release (): Boolean; inline;
     function motion (): Boolean; inline;
-    procedure eat (); inline;
-    procedure cancel (); inline;
-
-  public
-    property eaten: Boolean read mEaten;
-    property cancelled: Boolean read mCancelled;
-  end;
-
-  THKeyEvent = record
-  public
-    const
-      // modifiers
-      ModCtrl = $0001;
-      ModAlt = $0002;
-      ModShift = $0004;
-      ModHyper = $0008;
-
-    // event types
-    type
-      TKind = (Release, Press);
-
-  private
-    mEaten: Boolean;
-    mCancelled: Boolean;
+    function other (): Boolean; inline;
+    function simpleChar (): Boolean; inline;
 
-  public
-    kind: TKind;
-    scan: Word; // SDL_SCANCODE_XXX
-    //sym: LongWord; // SDLK_XXX
-    ch: AnsiChar; // converted to 1251; can be #0
-    x, y: Integer; // current mouse position
-    bstate: Word; // button state
-    kstate: Word; // keyboard state BEFORE event (i.e. press/release modifications aren't done yet)
-
-  public
-    procedure intrInit (); inline; // init hidden fields
-
-    function press (): Boolean; inline;
-    function release (): Boolean; inline;
+    function alive (): Boolean; inline; // not eaten and not cancelled
     procedure eat (); inline;
     procedure cancel (); inline;
 
+    procedure setSinking (); inline;
+    procedure setBubbling (); inline;
+    procedure setMine (); inline;
+
+    // compares `scan` with `c`
     function isHot (c: AnsiChar): Boolean;
 
   public
-    property eaten: Boolean read mEaten;
-    property cancelled: Boolean read mCancelled;
+    property etype: TType read mType; // event type: keyboard, mouse, etc...
+    property ekind: TKind read mKind; // motion, press, release
+    property state: TState read mState;
+
+    property eaten: Boolean read getEaten;
+    property cancelled: Boolean read getCancelled;
+    property nostate: Boolean read getNoState;
+    property mine: Boolean read getNoState;
+    property sinking: Boolean read getSinking;
+    property bubbling: Boolean read getBubbling;
   end;
 
 
@@ -117,8 +133,7 @@ procedure fuiResetKMState (sendEvents: Boolean=true);
 // ////////////////////////////////////////////////////////////////////////// //
 // event handlers
 var
-  evMouseCB: procedure (var ev: THMouseEvent) = nil;
-  evKeyCB: procedure (var ev: THKeyEvent) = nil;
+  fuiEventCB: procedure (var ev: TFUIEvent) = nil;
 
 
 // ////////////////////////////////////////////////////////////////////////// //
@@ -137,11 +152,8 @@ procedure fuiSetModState (v: Word); inline;
 // any mods = 255: nothing was defined
 function parseModKeys (const s: AnsiString; out kmods: Byte; out mbuts: Byte): AnsiString;
 
-operator = (constref ev: THKeyEvent; const s: AnsiString): Boolean;
-operator = (const s: AnsiString; constref ev: THKeyEvent): Boolean;
-
-operator = (constref ev: THMouseEvent; const s: AnsiString): Boolean;
-operator = (const s: AnsiString; constref ev: THMouseEvent): Boolean;
+operator = (constref ev: TFUIEvent; const s: AnsiString): Boolean;
+operator = (const s: AnsiString; constref ev: TFUIEvent): Boolean;
 
 
 implementation
@@ -154,6 +166,30 @@ var
 
 
 // ////////////////////////////////////////////////////////////////////////// //
+function locase1251 (ch: AnsiChar): AnsiChar; inline;
+begin
+  if ch < #128 then
+  begin
+    if (ch >= 'A') and (ch <= 'Z') then Inc(ch, 32);
+  end
+  else
+  begin
+    if (ch >= #192) and (ch <= #223) then
+    begin
+      Inc(ch, 32);
+    end
+    else
+    begin
+      case ch of
+        #168, #170, #175: Inc(ch, 16);
+        #161, #178: Inc(ch);
+      end;
+    end;
+  end;
+  result := ch;
+end;
+
+
 function strEquCI (const s0, s1: AnsiString): Boolean;
 var
   f: Integer;
@@ -187,80 +223,109 @@ procedure fuiSetModState (v: Word); inline; begin curModState := v; end;
 
 
 // ////////////////////////////////////////////////////////////////////////// //
-procedure THMouseEvent.intrInit (); inline; begin mEaten := false; mCancelled := false; end;
-function THMouseEvent.press (): Boolean; inline; begin result := (kind = TKind.Press); end;
-function THMouseEvent.release (): Boolean; inline; begin result := (kind = TKind.Release); end;
-function THMouseEvent.motion (): Boolean; inline; begin result := (kind = TKind.Motion); end;
-procedure THMouseEvent.eat (); inline; begin mEaten := true; end;
-procedure THMouseEvent.cancel (); inline; begin mCancelled := true; end;
-
-procedure THKeyEvent.intrInit (); inline; begin mEaten := false; mCancelled := false; end;
-function THKeyEvent.press (): Boolean; inline; begin result := (kind = TKind.Press); end;
-function THKeyEvent.release (): Boolean; inline; begin result := (kind = TKind.Release); end;
-procedure THKeyEvent.eat (); inline; begin mEaten := true; end;
-procedure THKeyEvent.cancel (); inline; begin mCancelled := true; end;
-
-function THKeyEvent.isHot (c: AnsiChar): Boolean;
+constructor TFUIEvent.Create (atype: TType; akind: TKind);
 begin
-  if (c = #0) or (scan = 0) or (scan = $FFFF) then begin result := false; exit; end;
-  case scan of
-    SDL_SCANCODE_A: result := (c = 'A') or (c = 'a') or (c = 'Ô') or (c = 'ô');
-    SDL_SCANCODE_B: result := (c = 'B') or (c = 'b') or (c = 'È') or (c = 'è');
-    SDL_SCANCODE_C: result := (c = 'C') or (c = 'c') or (c = 'Ñ') or (c = 'ñ');
-    SDL_SCANCODE_D: result := (c = 'D') or (c = 'd') or (c = 'Â') or (c = 'â');
-    SDL_SCANCODE_E: result := (c = 'E') or (c = 'e') or (c = 'Ó') or (c = 'ó');
-    SDL_SCANCODE_F: result := (c = 'F') or (c = 'f') or (c = 'À') or (c = 'à');
-    SDL_SCANCODE_G: result := (c = 'G') or (c = 'g') or (c = 'Ï') or (c = 'ï');
-    SDL_SCANCODE_H: result := (c = 'H') or (c = 'h') or (c = 'Ð') or (c = 'ð');
-    SDL_SCANCODE_I: result := (c = 'I') or (c = 'i') or (c = 'Ø') or (c = 'ø');
-    SDL_SCANCODE_J: result := (c = 'J') or (c = 'j') or (c = 'Î') or (c = 'î');
-    SDL_SCANCODE_K: result := (c = 'K') or (c = 'k') or (c = 'Ë') or (c = 'ë');
-    SDL_SCANCODE_L: result := (c = 'L') or (c = 'l') or (c = 'Ä') or (c = 'ä');
-    SDL_SCANCODE_M: result := (c = 'M') or (c = 'm') or (c = 'Ü') or (c = 'ü');
-    SDL_SCANCODE_N: result := (c = 'N') or (c = 'n') or (c = 'Ò') or (c = 'ò');
-    SDL_SCANCODE_O: result := (c = 'O') or (c = 'o') or (c = 'Ù') or (c = 'ù');
-    SDL_SCANCODE_P: result := (c = 'P') or (c = 'p') or (c = 'Ç') or (c = 'ç');
-    SDL_SCANCODE_Q: result := (c = 'Q') or (c = 'q') or (c = 'É') or (c = 'é');
-    SDL_SCANCODE_R: result := (c = 'R') or (c = 'r') or (c = 'Ê') or (c = 'ê');
-    SDL_SCANCODE_S: result := (c = 'S') or (c = 's') or (c = 'Û') or (c = 'û');
-    SDL_SCANCODE_T: result := (c = 'T') or (c = 't') or (c = 'Å') or (c = 'å');
-    SDL_SCANCODE_U: result := (c = 'U') or (c = 'u') or (c = 'Ã') or (c = 'ã');
-    SDL_SCANCODE_V: result := (c = 'V') or (c = 'v') or (c = 'Ì') or (c = 'ì');
-    SDL_SCANCODE_W: result := (c = 'W') or (c = 'w') or (c = 'Ö') or (c = 'ö');
-    SDL_SCANCODE_X: result := (c = 'X') or (c = 'x') or (c = '×') or (c = '÷');
-    SDL_SCANCODE_Y: result := (c = 'Y') or (c = 'y') or (c = 'Í') or (c = 'í');
-    SDL_SCANCODE_Z: result := (c = 'Z') or (c = 'z') or (c = 'ß') or (c = 'ÿ');
-
-    SDL_SCANCODE_1: result := (c = '1') or (c = '!');
-    SDL_SCANCODE_2: result := (c = '2') or (c = '@');
-    SDL_SCANCODE_3: result := (c = '3') or (c = '#');
-    SDL_SCANCODE_4: result := (c = '4') or (c = '$');
-    SDL_SCANCODE_5: result := (c = '5') or (c = '%');
-    SDL_SCANCODE_6: result := (c = '6') or (c = '^');
-    SDL_SCANCODE_7: result := (c = '7') or (c = '&');
-    SDL_SCANCODE_8: result := (c = '8') or (c = '*');
-    SDL_SCANCODE_9: result := (c = '9') or (c = '(');
-    SDL_SCANCODE_0: result := (c = '0') or (c = ')');
-
-    SDL_SCANCODE_RETURN: result := (c = #13) or (c = #10);
-    SDL_SCANCODE_ESCAPE: result := (c = #27);
-    SDL_SCANCODE_BACKSPACE: result := (c = #8);
-    SDL_SCANCODE_TAB: result := (c = #9);
-    SDL_SCANCODE_SPACE: result := (c = ' ');
-
-    SDL_SCANCODE_MINUS: result := (c = '-');
-    SDL_SCANCODE_EQUALS: result := (c = '=');
-    SDL_SCANCODE_LEFTBRACKET: result := (c = '[') or (c = '{');
-    SDL_SCANCODE_RIGHTBRACKET: result := (c = ']') or (c = '}');
-    SDL_SCANCODE_BACKSLASH, SDL_SCANCODE_NONUSHASH: result := (c = '\') or (c = '|');
-    SDL_SCANCODE_SEMICOLON: result := (c = ';') or (c = ':');
-    SDL_SCANCODE_APOSTROPHE: result := (c = '''') or (c = '"');
-    SDL_SCANCODE_GRAVE: result := (c = '`') or (c = '~');
-    SDL_SCANCODE_COMMA: result := (c = ',') or (c = '<');
-    SDL_SCANCODE_PERIOD: result := (c = '.') or (c = '>');
-    SDL_SCANCODE_SLASH: result := (c = '/') or (c = '?');
-
-    else result := false;
+  FillChar(self, sizeof(self), 0);
+  mType := atype;
+  mKind := akind;
+  mState := TState.None;
+end;
+
+function TFUIEvent.mouse (): Boolean; inline; begin result := (mType = TType.Mouse); end;
+function TFUIEvent.key (): Boolean; inline; begin result := (mType = TType.Key); end;
+function TFUIEvent.user (): Boolean; inline; begin result := (mType = TType.User); end;
+
+function TFUIEvent.press (): Boolean; inline; begin result := (mKind = TKind.Press); end;
+function TFUIEvent.release (): Boolean; inline; begin result := (mKind = TKind.Release); end;
+function TFUIEvent.motion (): Boolean; inline; begin result := (mKind = TKind.Motion); end;
+function TFUIEvent.other (): Boolean; inline; begin result := (mKind = TKind.Other); end;
+function TFUIEvent.simpleChar (): Boolean; inline; begin result := (mKind = TKind.SimpleChar); end;
+
+function TFUIEvent.alive (): Boolean; inline; begin result := (mState <> TState.Cancelled) and (mState <> TState.Eaten); end;
+procedure TFUIEvent.eat (); inline; begin if (alive) then mState := TState.Eaten; end;
+procedure TFUIEvent.cancel (); inline; begin if (alive) then mState := TState.Cancelled; end;
+procedure TFUIEvent.setSinking (); inline; begin if (alive) then mState := TState.Sinking; end;
+procedure TFUIEvent.setBubbling (); inline; begin if (alive) then mState := TState.Bubbling; end;
+procedure TFUIEvent.setMine (); inline; begin if (alive) then mState := TState.None; end;
+
+
+function TFUIEvent.getEaten (): Boolean; inline; begin result := (mState = TState.Eaten); end;
+function TFUIEvent.getCancelled (): Boolean; inline; begin result := (mState = TState.Cancelled); end;
+function TFUIEvent.getNoState (): Boolean; inline; begin result := (mState = TState.None); end;
+function TFUIEvent.getSinking (): Boolean; inline; begin result := (mState = TState.Sinking); end;
+function TFUIEvent.getBubbling (): Boolean; inline; begin result := (mState = TState.Bubbling); end;
+
+
+function TFUIEvent.isHot (c: AnsiChar): Boolean;
+begin
+  result := false;
+  if (c = #0) then exit;
+  if (not alive) or (not key) then exit;
+  c := locase1251(c);
+  if (simpleChar) then
+  begin
+    if (ch = #0) then exit;
+    result := (locase1251(ch) = c);
+  end
+  else
+  begin
+    case scan of
+      SDL_SCANCODE_A: result := (c = 'a') or (c = 'ô');
+      SDL_SCANCODE_B: result := (c = 'b') or (c = 'è');
+      SDL_SCANCODE_C: result := (c = 'c') or (c = 'ñ');
+      SDL_SCANCODE_D: result := (c = 'd') or (c = 'â');
+      SDL_SCANCODE_E: result := (c = 'e') or (c = 'ó');
+      SDL_SCANCODE_F: result := (c = 'f') or (c = 'à');
+      SDL_SCANCODE_G: result := (c = 'g') or (c = 'ï');
+      SDL_SCANCODE_H: result := (c = 'h') or (c = 'ð');
+      SDL_SCANCODE_I: result := (c = 'i') or (c = 'ø');
+      SDL_SCANCODE_J: result := (c = 'j') or (c = 'î');
+      SDL_SCANCODE_K: result := (c = 'k') or (c = 'ë');
+      SDL_SCANCODE_L: result := (c = 'l') or (c = 'ä');
+      SDL_SCANCODE_M: result := (c = 'm') or (c = 'ü');
+      SDL_SCANCODE_N: result := (c = 'n') or (c = 'ò');
+      SDL_SCANCODE_O: result := (c = 'o') or (c = 'ù');
+      SDL_SCANCODE_P: result := (c = 'p') or (c = 'ç');
+      SDL_SCANCODE_Q: result := (c = 'q') or (c = 'é');
+      SDL_SCANCODE_R: result := (c = 'r') or (c = 'ê');
+      SDL_SCANCODE_S: result := (c = 's') or (c = 'û');
+      SDL_SCANCODE_T: result := (c = 't') or (c = 'å');
+      SDL_SCANCODE_U: result := (c = 'u') or (c = 'ã');
+      SDL_SCANCODE_V: result := (c = 'v') or (c = 'ì');
+      SDL_SCANCODE_W: result := (c = 'w') or (c = 'ö');
+      SDL_SCANCODE_X: result := (c = 'x') or (c = '÷');
+      SDL_SCANCODE_Y: result := (c = 'y') or (c = 'í');
+      SDL_SCANCODE_Z: result := (c = 'z') or (c = 'ÿ');
+
+      SDL_SCANCODE_1: result := (c = '1') or (c = '!');
+      SDL_SCANCODE_2: result := (c = '2') or (c = '@');
+      SDL_SCANCODE_3: result := (c = '3') or (c = '#');
+      SDL_SCANCODE_4: result := (c = '4') or (c = '$');
+      SDL_SCANCODE_5: result := (c = '5') or (c = '%');
+      SDL_SCANCODE_6: result := (c = '6') or (c = '^');
+      SDL_SCANCODE_7: result := (c = '7') or (c = '&');
+      SDL_SCANCODE_8: result := (c = '8') or (c = '*');
+      SDL_SCANCODE_9: result := (c = '9') or (c = '(');
+      SDL_SCANCODE_0: result := (c = '0') or (c = ')');
+
+      SDL_SCANCODE_RETURN: result := (c = #13) or (c = #10);
+      SDL_SCANCODE_ESCAPE: result := (c = #27);
+      SDL_SCANCODE_BACKSPACE: result := (c = #8);
+      SDL_SCANCODE_TAB: result := (c = #9);
+      SDL_SCANCODE_SPACE: result := (c = ' ');
+
+      SDL_SCANCODE_MINUS: result := (c = '-');
+      SDL_SCANCODE_EQUALS: result := (c = '=');
+      SDL_SCANCODE_LEFTBRACKET: result := (c = '[') or (c = '{') or (c = 'õ');
+      SDL_SCANCODE_RIGHTBRACKET: result := (c = ']') or (c = '}') or (c = 'ú');
+      SDL_SCANCODE_BACKSLASH, SDL_SCANCODE_NONUSHASH: result := (c = '\') or (c = '|');
+      SDL_SCANCODE_SEMICOLON: result := (c = ';') or (c = ':') or (c = 'æ');
+      SDL_SCANCODE_APOSTROPHE: result := (c = '''') or (c = '"') or (c = 'ý');
+      SDL_SCANCODE_GRAVE: result := (c = '`') or (c = '~') or (c = '¸');
+      SDL_SCANCODE_COMMA: result := (c = ',') or (c = '<') or (c = 'á');
+      SDL_SCANCODE_PERIOD: result := (c = '.') or (c = '>') or (c = '.') or (c = 'þ');
+      SDL_SCANCODE_SLASH: result := (c = '/') or (c = '?') or (c = 'þ'); // ju: not a bug!
+    end;
   end;
 end;
 
@@ -281,18 +346,18 @@ begin
     if (Length(s)-pos >= 1) and (s[pos+1] = '-') then
     begin
       case s[pos] of
-        'C', 'c': begin if (kmods = 255) then kmods := 0; kmods := kmods or THKeyEvent.ModCtrl; Inc(pos, 2); continue; end;
-        'M', 'm': begin if (kmods = 255) then kmods := 0; kmods := kmods or THKeyEvent.ModAlt; Inc(pos, 2); continue; end;
-        'S', 's': begin if (kmods = 255) then kmods := 0; kmods := kmods or THKeyEvent.ModShift; Inc(pos, 2); continue; end;
+        'C', 'c': begin if (kmods = 255) then kmods := 0; kmods := kmods or TFUIEvent.ModCtrl; Inc(pos, 2); continue; end;
+        'M', 'm': begin if (kmods = 255) then kmods := 0; kmods := kmods or TFUIEvent.ModAlt; Inc(pos, 2); continue; end;
+        'S', 's': begin if (kmods = 255) then kmods := 0; kmods := kmods or TFUIEvent.ModShift; Inc(pos, 2); continue; end;
       end;
       break;
     end;
     if (Length(s)-pos >= 3) and (s[pos+3] = '-') and ((s[pos+1] = 'M') or (s[pos+1] = 'm')) and ((s[pos+2] = 'B') or (s[pos+2] = 'b')) then
     begin
       case s[pos] of
-        'L', 'l': begin if (mbuts = 255) then mbuts := 0; mbuts := mbuts or THMouseEvent.Left; Inc(pos, 4); continue; end;
-        'R', 'r': begin if (mbuts = 255) then mbuts := 0; mbuts := mbuts or THMouseEvent.Right; Inc(pos, 4); continue; end;
-        'M', 'm': begin if (mbuts = 255) then mbuts := 0; mbuts := mbuts or THMouseEvent.Middle; Inc(pos, 4); continue; end;
+        'L', 'l': begin if (mbuts = 255) then mbuts := 0; mbuts := mbuts or TFUIEvent.Left; Inc(pos, 4); continue; end;
+        'R', 'r': begin if (mbuts = 255) then mbuts := 0; mbuts := mbuts or TFUIEvent.Right; Inc(pos, 4); continue; end;
+        'M', 'm': begin if (mbuts = 255) then mbuts := 0; mbuts := mbuts or TFUIEvent.Middle; Inc(pos, 4); continue; end;
       end;
       break;
     end;
@@ -304,84 +369,81 @@ begin
 end;
 
 
-operator = (constref ev: THKeyEvent; const s: AnsiString): Boolean;
-var
-  f: Integer;
-  kmods: Byte = 255;
-  mbuts: Byte = 255;
-  kname: AnsiString;
-begin
-  result := false;
-  if (Length(s) > 0) then
-  begin
-         if (s[1] = '+') then begin if (not ev.press) then exit; end
-    else if (s[1] = '-') then begin if (not ev.release) then exit; end
-    else if (s[1] = '*') then begin end
-    else if (not ev.press) then exit;
-  end;
-  kname := parseModKeys(s, kmods, mbuts);
-  if (kmods = 255) then kmods := 0;
-  if (ev.kstate <> kmods) then exit;
-  if (mbuts <> 255) and (ev.bstate <> mbuts) then exit;
-
-  if (strEquCI(kname, 'Enter')) then kname := 'RETURN';
-
-  for f := 0 to SDL_NUM_SCANCODES-1 do
-  begin
-    if strEquCI(kname, SDL_GetScancodeName(f)) then
-    begin
-      result := (ev.scan = f);
-      exit;
-    end;
-  end;
-end;
-
-
-operator = (const s: AnsiString; constref ev: THKeyEvent): Boolean;
+operator = (const s: AnsiString; constref ev: TFUIEvent): Boolean;
 begin
   result := (ev = s);
 end;
 
 
-operator = (constref ev: THMouseEvent; const s: AnsiString): Boolean;
+operator = (constref ev: TFUIEvent; const s: AnsiString): Boolean;
 var
   kmods: Byte = 255;
   mbuts: Byte = 255;
   kname: AnsiString;
   but: Integer = -1;
   modch: AnsiChar = ' ';
+  kfound: Boolean;
+  f: Integer;
 begin
   result := false;
+  if (Length(s) = 0) then exit;
+  // oops; i still want to compare dead events
+  //if (not ev.alive) then exit; // dead events aren't equal to anything
+  if (ev.user) then exit; // user events aren't equal to anything
+  if (ev.simpleChar) or (ev.other) then exit; // those events are uncomparable for now
 
-  if (Length(s) > 0) then
-  begin
-         if (s[1] = '+') then begin if (not ev.press) then exit; modch := '+'; end
-    else if (s[1] = '-') then begin if (not ev.release) then exit; modch := '-'; end
-    else if (s[1] = '*') then begin if (not ev.motion) then exit; end
-    else if (not ev.press) then exit;
-  end;
+       if (s[1] = '+') then begin if (not ev.press) then exit; modch := '+'; end
+  else if (s[1] = '-') then begin if (not ev.release) then exit; modch := '-'; end
+  else if (s[1] = '*') then begin if (not ev.motion) then exit; end
+  else if (not ev.press) then exit;
 
   kname := parseModKeys(s, kmods, mbuts);
-       if strEquCI(kname, 'LMB') then but := THMouseEvent.Left
-  else if strEquCI(kname, 'RMB') then but := THMouseEvent.Right
-  else if strEquCI(kname, 'MMB') then but := THMouseEvent.Middle
-  else if strEquCI(kname, 'WheelUp') or strEquCI(kname, 'WUP') then but := THMouseEvent.WheelUp
-  else if strEquCI(kname, 'WheelDown') or strEquCI(kname, 'WDN') or strEquCI(kname, 'WDOWN') then but := THMouseEvent.WheelDown
-  else if strEquCI(kname, 'None') then but := 0
-  else exit;
+  //if (ev.mouse) then writeln('compare: ', ev.mKind, ':', ev.mType, '; kstate=', ev.kstate, '; bstate=', ev.bstate, '; s=<', s, '>; kname=<', kname, '>; kmods=', kmods, '; mbuts=', mbuts);
+  if (Length(kname) = 0) then exit; // some error occured
+  if (strEquCI(kname, 'Enter')) then kname := 'RETURN';
 
   if (mbuts = 255) then mbuts := 0;
   if (kmods = 255) then kmods := 0;
   if (ev.kstate <> kmods) then exit;
-  if (modch = '-') then mbuts := mbuts or but else if (modch = '+') then mbuts := mbuts and (not but);
 
-  result := (ev.bstate = mbuts) and (ev.but = but);
-end;
+       if (strEquCI(kname, 'LMB')) then but := TFUIEvent.Left
+  else if (strEquCI(kname, 'RMB')) then but := TFUIEvent.Right
+  else if (strEquCI(kname, 'MMB')) then but := TFUIEvent.Middle
+  else if (strEquCI(kname, 'WheelUp')) or strEquCI(kname, 'WUP') then but := TFUIEvent.WheelUp
+  else if (strEquCI(kname, 'WheelDown')) or strEquCI(kname, 'WDN') or strEquCI(kname, 'WDOWN') then but := TFUIEvent.WheelDown
+  else if (strEquCI(kname, 'None')) then but := 0
+  else
+  begin
+    // try keyboard
+    if (not ev.key) then exit;
+    if (strEquCI(kname, 'Empty')) or (strEquCI(kname, 'NoKey')) then
+    begin
+      kfound := (ev.scan = 0);
+    end
+    else
+    begin
+      kfound := false;
+      for f := 1 to SDL_NUM_SCANCODES-1 do
+      begin
+        if (strEquCI(kname, SDL_GetScancodeName(f))) then begin kfound := (ev.scan = f); break; end;
+      end;
+    end;
+    if (not kfound) then exit;
+  end;
+  //writeln('  scan=', ev.scan, '; found=', kfound);
 
+  if (but <> -1) and (not ev.mouse) then exit; // mouse kname, but not mouse event
 
-operator = (const s: AnsiString; constref ev: THMouseEvent): Boolean;
-begin
-  result := (ev = s);
+  // fix mouse buttons
+  if (ev.mouse) then
+  begin
+    if (modch = '-') then mbuts := mbuts or but else if (modch = '+') then mbuts := mbuts and (not but);
+    result := (ev.bstate = mbuts) and (ev.but = but);
+  end
+  else
+  begin
+    result := (ev.bstate = mbuts);
+  end;
 end;
 
 
@@ -389,32 +451,27 @@ end;
 procedure fuiResetKMState (sendEvents: Boolean=true);
 var
   mask: Word;
-  mev: THMouseEvent;
-  kev: THKeyEvent;
+  ev: TFUIEvent;
 begin
   // generate mouse release events
   if (curButState <> 0) then
   begin
-    if sendEvents then
+    if (sendEvents) then
     begin
       mask := 1;
       while (mask <> 0) do
       begin
         // checked each time, 'cause `evMouseCB` can be changed from the handler
-        if ((curButState and mask) <> 0) and assigned(evMouseCB) then
+        if ((curButState and mask) <> 0) and (assigned(fuiEventCB)) then
         begin
-          FillChar(mev, sizeof(mev), 0);
-          mev.intrInit();
-          mev.kind := mev.TKind.Release;
-          mev.x := curMsX;
-          mev.y := curMsY;
-          mev.dx := 0;
-          mev.dy := 0;
-          mev.but := mask;
-          mev.bstate := curButState;
-          mev.kstate := curModState;
+          ev := TFUIEvent.Create(TFUIEvent.TType.Mouse, TFUIEvent.TKind.Release);
+          ev.x := curMsX;
+          ev.y := curMsY;
+          ev.but := mask;
+          ev.bstate := curButState;
+          ev.kstate := curModState;
           curButState := curButState and (not mask);
-          evMouseCB(mev);
+          fuiEventCB(ev);
         end;
         mask := mask shl 1;
       end;
@@ -425,30 +482,28 @@ begin
   // generate modifier release events
   if (curModState <> 0) then
   begin
-    if sendEvents then
+    if (sendEvents) then
     begin
       mask := 1;
       while (mask <= 8) do
       begin
         // checked each time, 'cause `evMouseCB` can be changed from the handler
-        if ((curModState and mask) <> 0) and assigned(evKeyCB) then
+        if ((curModState and mask) <> 0) and (assigned(fuiEventCB)) then
         begin
-          FillChar(kev, sizeof(kev), 0);
-          kev.intrInit();
-          kev.kind := kev.TKind.Release;
+          ev := TFUIEvent.Create(TFUIEvent.TType.Key, TFUIEvent.TKind.Release);
           case mask of
-            THKeyEvent.ModCtrl: begin kev.scan := SDL_SCANCODE_LCTRL; {kev.sym := SDLK_LCTRL;}{arbitrary} end;
-            THKeyEvent.ModAlt: begin kev.scan := SDL_SCANCODE_LALT; {kev.sym := SDLK_LALT;}{arbitrary} end;
-            THKeyEvent.ModShift: begin kev.scan := SDL_SCANCODE_LSHIFT; {kev.sym := SDLK_LSHIFT;}{arbitrary} end;
-            THKeyEvent.ModHyper: begin kev.scan := SDL_SCANCODE_LGUI; {kev.sym := SDLK_LGUI;}{arbitrary} end;
+            TFUIEvent.ModCtrl: begin ev.scan := SDL_SCANCODE_LCTRL; {kev.sym := SDLK_LCTRL;}{arbitrary} end;
+            TFUIEvent.ModAlt: begin ev.scan := SDL_SCANCODE_LALT; {kev.sym := SDLK_LALT;}{arbitrary} end;
+            TFUIEvent.ModShift: begin ev.scan := SDL_SCANCODE_LSHIFT; {kev.sym := SDLK_LSHIFT;}{arbitrary} end;
+            TFUIEvent.ModHyper: begin ev.scan := SDL_SCANCODE_LGUI; {kev.sym := SDLK_LGUI;}{arbitrary} end;
             else assert(false);
           end;
-          kev.x := curMsX;
-          kev.y := curMsY;
-          mev.bstate := 0{curMsButState}; // anyway
-          mev.kstate := curModState;
+          ev.x := curMsX;
+          ev.y := curMsY;
+          //mev.bstate := 0{curMsButState}; // anyway
+          ev.kstate := curModState;
           curModState := curModState and (not mask);
-          evKeyCB(kev);
+          fuiEventCB(ev);
         end;
         mask := mask shl 1;
       end;