DEADSOFTWARE

FlexUI: switched to universal event struct; changed event dispatching to sink/mine...
authorKetmar Dark <ketmar@ketmar.no-ip.org>
Wed, 4 Oct 2017 18:51:25 +0000 (21:51 +0300)
committerKetmar Dark <ketmar@ketmar.no-ip.org>
Wed, 4 Oct 2017 20:09:56 +0000 (23:09 +0300)
src/flexui/fui_ctls.pas
src/flexui/fui_events.pas
src/flexui/sdlcarcass.pas
src/game/g_holmes.pas
src/game/g_holmes_cmd.inc

index 27f14fd0fd007acfcca858be4f6fff6278e08daa..38f17fd0c928a893b7bbb1bc75662ea46da8b9ef 100644 (file)
@@ -230,10 +230,15 @@ type
 
     procedure doAction (); virtual; // so user controls can override it
 
-    procedure mouseEvent (var ev: TFUIMouseEvent); virtual; // returns `true` if event was eaten
-    procedure keyEvent (var ev: TFUIKeyEvent); virtual; // returns `true` if event was eaten
-    procedure keyEventPre (var ev: TFUIKeyEvent); virtual; // will be called before dispatching the event
-    procedure keyEventPost (var ev: TFUIKeyEvent); virtual; // will be called after if nobody processed the event
+    procedure onEvent (var ev: TFUIEvent); virtual; // general dispatcher
+
+    procedure mouseEvent (var ev: TFUIEvent); virtual;
+    procedure mouseEventSink (var ev: TFUIEvent); virtual;
+    procedure mouseEventBubble (var ev: TFUIEvent); virtual;
+
+    procedure keyEvent (var ev: TFUIEvent); virtual;
+    procedure keyEventSink (var ev: TFUIEvent); virtual;
+    procedure keyEventBubble (var ev: TFUIEvent); virtual;
 
     function prevSibling (): TUIControl;
     function nextSibling (): TUIControl;
@@ -306,8 +311,8 @@ type
     procedure drawControl (gx, gy: Integer); override;
     procedure drawControlPost (gx, gy: Integer); override;
 
-    procedure keyEvent (var ev: TFUIKeyEvent); override; // returns `true` if event was eaten
-    procedure mouseEvent (var ev: TFUIMouseEvent); override; // returns `true` if event was eaten
+    procedure keyEventBubble (var ev: TFUIEvent); override; // returns `true` if event was eaten
+    procedure mouseEvent (var ev: TFUIEvent); override; // returns `true` if event was eaten
 
   public
     property freeOnClose: Boolean read mFreeOnClose write mFreeOnClose;
@@ -334,8 +339,8 @@ type
 
     procedure drawControl (gx, gy: Integer); override;
 
-    procedure mouseEvent (var ev: TFUIMouseEvent); override;
-    procedure keyEvent (var ev: TFUIKeyEvent); override;
+    procedure mouseEvent (var ev: TFUIEvent); override;
+    procedure keyEvent (var ev: TFUIEvent); override;
 
   public
     property caption: AnsiString read mCaption write setCaption;
@@ -429,8 +434,8 @@ type
 
     procedure drawControl (gx, gy: Integer); override;
 
-    procedure mouseEvent (var ev: TFUIMouseEvent); override;
-    procedure keyEventPost (var ev: TFUIKeyEvent); override;
+    procedure mouseEvent (var ev: TFUIEvent); override;
+    procedure keyEventBubble (var ev: TFUIEvent); override;
 
   public
     property text: AnsiString read mText write setText;
@@ -461,8 +466,8 @@ type
 
     procedure drawControl (gx, gy: Integer); override;
 
-    procedure mouseEvent (var ev: TFUIMouseEvent); override;
-    procedure keyEvent (var ev: TFUIKeyEvent); override;
+    procedure mouseEvent (var ev: TFUIEvent); override;
+    procedure keyEvent (var ev: TFUIEvent); override;
   end;
 
   // ////////////////////////////////////////////////////////////////////// //
@@ -501,8 +506,8 @@ type
 
     procedure drawControl (gx, gy: Integer); override;
 
-    procedure mouseEvent (var ev: TFUIMouseEvent); override;
-    procedure keyEvent (var ev: TFUIKeyEvent); override;
+    procedure mouseEvent (var ev: TFUIEvent); override;
+    procedure keyEvent (var ev: TFUIEvent); override;
 
     procedure setVar (pvar: PBoolean);
 
@@ -540,8 +545,7 @@ type
 
 
 // ////////////////////////////////////////////////////////////////////////// //
-procedure uiMouseEvent (var evt: TFUIMouseEvent);
-procedure uiKeyEvent (var evt: TFUIKeyEvent);
+procedure uiDispatchEvent (var evt: TFUIEvent);
 procedure uiDraw ();
 
 procedure uiFocus ();
@@ -553,6 +557,9 @@ procedure uiAddWindow (ctl: TUIControl);
 procedure uiRemoveWindow (ctl: TUIControl); // will free window if `mFreeOnClose` is `true`
 function uiVisibleWindow (ctl: TUIControl): Boolean;
 
+// this can return `nil` or disabled control
+function uiGetFocusedCtl (): TUIControl;
+
 procedure uiUpdateStyles ();
 
 
@@ -579,6 +586,11 @@ uses
   utils;
 
 
+var
+  uiTopList: array of TUIControl = nil;
+  uiGrabCtl: TUIControl = nil;
+
+
 // ////////////////////////////////////////////////////////////////////////// //
 procedure uiDeinitialize ();
 begin
@@ -623,6 +635,7 @@ begin
   begin
     ctl := ctlsToKill[f];
     if (ctl = nil) then break;
+    if (uiGrabCtl <> nil) and (ctl.isMyChild(uiGrabCtl)) then uiGrabCtl := nil; // just in case
     ctlsToKill[f] := nil;
     FreeAndNil(ctl);
   end;
@@ -723,11 +736,6 @@ end;
 
 
 // ////////////////////////////////////////////////////////////////////////// //
-var
-  uiTopList: array of TUIControl = nil;
-  uiGrabCtl: TUIControl = nil;
-
-
 procedure uiUpdateStyles ();
 var
   ctl: TUIControl;
@@ -736,76 +744,167 @@ begin
 end;
 
 
-procedure uiMouseEvent (var evt: TFUIMouseEvent);
+procedure uiDispatchEvent (var evt: TFUIEvent);
 var
-  ev: TFUIMouseEvent;
-  f, c: Integer;
-  lx, ly: Integer;
-  ctmp: TUIControl;
-begin
-  processKills();
-  if (not evt.alive) then exit;
-  ev := evt;
-  ev.x := trunc(ev.x/fuiRenderScale);
-  ev.y := trunc(ev.y/fuiRenderScale);
-  ev.dx := trunc(ev.dx/fuiRenderScale); //FIXME
-  ev.dy := trunc(ev.dy/fuiRenderScale); //FIXME
-  try
+  ev: TFUIEvent;
+  destCtl: TUIControl;
+
+  procedure doSink (ctl: TUIControl);
+  begin
+    if (ctl = nil) or (not ev.alive) then exit;
+    if (ctl.mParent <> nil) then
+    begin
+      doSink(ctl.mParent);
+      if (not ev.alive) then exit;
+    end;
+    //if (ctl = destCtl) then writeln(' SINK: MINE! <', ctl.className, '>');
+    ev.setSinking();
+    ctl.onEvent(ev);
+    if (ctl = destCtl) and (ev.alive) then
+    begin
+      ev.setMine();
+      ctl.onEvent(ev);
+    end;
+  end;
+
+  procedure dispatchTo (ctl: TUIControl);
+  begin
+    if (ctl = nil) then exit;
+    destCtl := ctl;
+    // sink
+    doSink(ctl);
+    // bubble
+    //ctl := ctl.mParent; // 'cause "mine" is processed in `doSink()`
+    while (ctl <> nil) and (ev.alive) do
+    begin
+      ev.setBubbling();
+      ctl.onEvent(ev);
+      ctl := ctl.mParent;
+    end;
+  end;
+
+  procedure doMouseEvent ();
+  var
+    doUngrab: Boolean;
+    ctl: TUIControl;
+    win: TUIControl;
+    lx, ly: Integer;
+    f, c: Integer;
+  begin
+    // pass mouse events to control with grab, if there is any
     if (uiGrabCtl <> nil) then
     begin
-      uiGrabCtl.mouseEvent(ev);
-      if (ev.release) and ((ev.bstate and (not ev.but)) = 0) then uiGrabCtl := nil;
+      //writeln('GRABBED: ', uiGrabCtl.className);
+      doUngrab := (ev.release) and ((ev.bstate and (not ev.but)) = 0);
+      dispatchTo(uiGrabCtl);
+      //FIXME: create API to get grabs, so control can regrab itself event on release
+      if (doUngrab) and (uiGrabCtl = destCtl) then uiGrabCtl := nil;
       ev.eat();
       exit;
     end;
-    if (Length(uiTopList) > 0) and (uiTopList[High(uiTopList)].enabled) then uiTopList[High(uiTopList)].mouseEvent(ev);
-    if (ev.alive) and (ev.press) then
+    // get top window
+    if (Length(uiTopList) > 0) then win := uiTopList[High(uiTopList)] else win := nil;
+    // check if we're still in top window
+    if (ev.press) and (win <> nil) and (not win.toLocal(0, 0, lx, ly)) then
     begin
-      for f := High(uiTopList) downto 0 do
+      // we have other windows too; check for window switching
+      for f := High(uiTopList)-1 downto 0 do
       begin
-        if uiTopList[f].toLocal(ev.x, ev.y, lx, ly) then
+        if (uiTopList[f].enabled) and (uiTopList[f].toLocal(ev.x, ev.y, lx, ly)) then
         begin
-          if (uiTopList[f].enabled) and (f <> High(uiTopList)) then
-          begin
-            if (Length(uiTopList) > 0) and (uiTopList[High(uiTopList)].enabled) then uiTopList[High(uiTopList)].blurred();
-            ctmp := uiTopList[f];
-            uiGrabCtl := nil;
-            for c := f+1 to High(uiTopList) do uiTopList[c-1] := uiTopList[c];
-            uiTopList[High(uiTopList)] := ctmp;
-            ctmp.activated();
-            ctmp.mouseEvent(ev);
-          end;
-          ev.eat();
-          exit;
+          // switch
+          win.blurred();
+          win := uiTopList[f];
+          for c := f+1 to High(uiTopList) do uiTopList[c-1] := uiTopList[c];
+          uiTopList[High(uiTopList)] := win;
+          win.activated();
+          break;
         end;
       end;
     end;
-  finally
-    if (ev.eaten) then evt.eat();
-    if (ev.cancelled) then evt.cancel();
+    // dispatch event
+    if (win <> nil) and (win.toLocal(ev.x, ev.y, lx, ly)) then
+    begin
+      ctl := win.controlAtXY(ev.x, ev.y); // don't allow disabled controls
+      if (ctl = nil) or (not ctl.canFocus) or (not ctl.enabled) then ctl := win;
+      // pass focus to another event and set grab, if necessary
+      if (ev.press) then
+      begin
+        // pass focus, if necessary
+        if (win.mFocused <> ctl) then
+        begin
+          if (win.mFocused <> nil) then win.mFocused.blurred();
+          uiGrabCtl := ctl;
+          win.mFocused := ctl;
+          if (ctl <> win) then ctl.activated();
+        end
+        else
+        begin
+          uiGrabCtl := ctl;
+        end;
+      end;
+      dispatchTo(ctl);
+    end;
   end;
-end;
-
 
-procedure uiKeyEvent (var evt: TFUIKeyEvent);
 var
-  ev: TFUIKeyEvent;
+  svx, svy, svdx, svdy: Integer;
+  svscale: Single;
 begin
   processKills();
   if (not evt.alive) then exit;
+  //writeln('ENTER: FUI DISPATCH');
   ev := evt;
-  ev.x := trunc(ev.x/fuiRenderScale);
-  ev.y := trunc(ev.y/fuiRenderScale);
+  // normalize mouse coordinates
+  svscale := fuiRenderScale;
+  ev.x := trunc(ev.x/svscale);
+  ev.y := trunc(ev.y/svscale);
+  ev.dx := trunc(ev.dx/svscale); //FIXME
+  ev.dy := trunc(ev.dy/svscale); //FIXME
+  svx := ev.x;
+  svy := ev.y;
+  svdx := ev.dx;
+  svdy := ev.dy;
   try
-    if (Length(uiTopList) > 0) and (uiTopList[High(uiTopList)].enabled) then uiTopList[High(uiTopList)].keyEvent(ev);
-    //if (ev.release) then begin ev.eat(); exit; end;
+    // "event grab" eats only mouse events
+    if (ev.mouse) then
+    begin
+      // we need to so some special processing here
+      doMouseEvent();
+    end
+    else
+    begin
+      // simply dispatch to focused control
+      dispatchTo(uiGetFocusedCtl);
+    end;
   finally
-    if (ev.eaten) then evt.eat();
-    if (ev.cancelled) then evt.cancel();
+    if (ev.x = svx) and (ev.y = svy) and (ev.dx = svdx) and (ev.dy = svdy) then
+    begin
+      // due to possible precision loss
+      svx := evt.x;
+      svy := evt.y;
+      svdx := evt.dx;
+      svdy := evt.dy;
+      evt := ev;
+      evt.x := svx;
+      evt.y := svy;
+      evt.dx := svdx;
+      evt.dy := svdy;
+    end
+    else
+    begin
+      // scale back
+      evt := ev;
+      evt.x := trunc(evt.x*svscale);
+      evt.y := trunc(evt.y*svscale);
+      evt.dx := trunc(evt.dx*svscale);
+      evt.dy := trunc(evt.dy*svscale);
+    end;
   end;
+  processKills();
+  //writeln('EXIT: FUI DISPATCH');
 end;
 
-
 procedure uiFocus ();
 begin
   if (Length(uiTopList) > 0) and (uiTopList[High(uiTopList)].enabled) then uiTopList[High(uiTopList)].activated();
@@ -844,6 +943,12 @@ begin
 end;
 
 
+function uiGetFocusedCtl (): TUIControl;
+begin
+  if (Length(uiTopList) > 0) and (uiTopList[High(uiTopList)].enabled) then result := uiTopList[High(uiTopList)].mFocused else result := nil;
+end;
+
+
 procedure uiAddWindow (ctl: TUIControl);
 var
   f, c: Integer;
@@ -2050,122 +2155,102 @@ end;
 
 
 // ////////////////////////////////////////////////////////////////////////// //
-procedure TUIControl.mouseEvent (var ev: TFUIMouseEvent);
-var
-  ctl: TUIControl;
+procedure TUIControl.onEvent (var ev: TFUIEvent);
 begin
-  if (not enabled) then exit;
-  if (mWidth < 1) or (mHeight < 1) then exit;
-  ctl := controlAtXY(ev.x, ev.y);
-  if (ctl = nil) then exit;
-  if (ctl.canFocus) and (ev.press) then
+  if (not ev.alive) or (not enabled) then exit;
+  //if (ev.mine) then writeln(' MINE: <', className, '>');
+  if (ev.key) then
   begin
-    if (ctl <> topLevel.mFocused) then ctl.setFocused(true);
-    uiGrabCtl := ctl;
+         if (ev.sinking) then keyEventSink(ev)
+    else if (ev.bubbling) then keyEventBubble(ev)
+    else if (ev.mine) then keyEvent(ev);
+  end
+  else if (ev.mouse) then
+  begin
+         if (ev.sinking) then mouseEventSink(ev)
+    else if (ev.bubbling) then mouseEventBubble(ev)
+    else if (ev.mine) then mouseEvent(ev);
   end;
-  if (ctl <> self) then ctl.mouseEvent(ev);
-  //ev.eat();
 end;
 
 
-procedure TUIControl.keyEvent (var ev: TFUIKeyEvent);
+procedure TUIControl.mouseEventSink (var ev: TFUIEvent);
+begin
+end;
 
-  function doPreKey (ctl: TUIControl): Boolean;
-  begin
-    if (not ctl.enabled) then begin result := false; exit; end;
-    ctl.keyEventPre(ev);
-    result := (not ev.alive); // stop if event was consumed
-  end;
+procedure TUIControl.mouseEventBubble (var ev: TFUIEvent);
+begin
+end;
 
-  function doPostKey (ctl: TUIControl): Boolean;
-  begin
-    if (not ctl.enabled) then begin result := false; exit; end;
-    ctl.keyEventPost(ev);
-    result := (not ev.alive); // stop if event was consumed
-  end;
+procedure TUIControl.mouseEvent (var ev: TFUIEvent);
+begin
+end;
 
+
+procedure TUIControl.keyEventSink (var ev: TFUIEvent);
 var
   ctl: TUIControl;
 begin
   if (not enabled) then exit;
   if (not ev.alive) then exit;
-  // call pre-key
-  if (mParent = nil) then
-  begin
-    forEachControl(doPreKey);
-    if (not ev.alive) then exit;
-  end;
-  // focused control should process keyboard first
-  if (topLevel.mFocused <> self) and isMyChild(topLevel.mFocused) and (topLevel.mFocused.enabled) then
+  // for top-level controls
+  if (mParent <> nil) then exit;
+  if (mEscClose) and (ev = 'Escape') then
   begin
-    // bubble keyboard event
-    ctl := topLevel.mFocused;
-    while (ctl <> nil) and (ctl <> self) do
+    if (not assigned(closeRequestCB)) or (closeRequestCB(self)) then
     begin
-      ctl.keyEvent(ev);
-      if (not ev.alive) then exit;
-      ctl := ctl.mParent;
+      uiRemoveWindow(self);
     end;
+    ev.eat();
+    exit;
   end;
-  // for top-level controls
-  if (mParent = nil) then
+  if (ev = 'Enter') or (ev = 'C-Enter') then
   begin
-    if (ev = 'S-Tab') then
-    begin
-      ctl := findPrevFocus(mFocused, true);
-      if (ctl <> nil) and (ctl <> mFocused) then ctl.setFocused(true);
-      ev.eat();
-      exit;
-    end;
-    if (ev = 'Tab') then
+    ctl := findDefaulControl();
+    if (ctl <> nil) then
     begin
-      ctl := findNextFocus(mFocused, true);
-      if (ctl <> nil) and (ctl <> mFocused) then ctl.setFocused(true);
       ev.eat();
+      ctl.doAction();
       exit;
     end;
-    if (ev = 'Enter') or (ev = 'C-Enter') then
-    begin
-      ctl := findDefaulControl();
-      if (ctl <> nil) then
-      begin
-        ev.eat();
-        ctl.doAction();
-        exit;
-      end;
-    end;
-    if (ev = 'Escape') then
-    begin
-      ctl := findCancelControl();
-      if (ctl <> nil) then
-      begin
-        ev.eat();
-        ctl.doAction();
-        exit;
-      end;
-    end;
-    if mEscClose and (ev = 'Escape') then
+  end;
+  if (ev = 'Escape') then
+  begin
+    ctl := findCancelControl();
+    if (ctl <> nil) then
     begin
-      if (not assigned(closeRequestCB)) or (closeRequestCB(self)) then
-      begin
-        uiRemoveWindow(self);
-      end;
       ev.eat();
+      ctl.doAction();
       exit;
     end;
-    // call post-keys
-    if (not ev.alive) then exit;
-    forEachControl(doPostKey);
   end;
 end;
 
-
-procedure TUIControl.keyEventPre (var ev: TFUIKeyEvent);
+procedure TUIControl.keyEventBubble (var ev: TFUIEvent);
+var
+  ctl: TUIControl;
 begin
+  if (not enabled) then exit;
+  if (not ev.alive) then exit;
+  // for top-level controls
+  if (mParent <> nil) then exit;
+  if (ev = 'S-Tab') then
+  begin
+    ctl := findPrevFocus(mFocused, true);
+    if (ctl <> nil) and (ctl <> mFocused) then ctl.setFocused(true);
+    ev.eat();
+    exit;
+  end;
+  if (ev = 'Tab') then
+  begin
+    ctl := findNextFocus(mFocused, true);
+    if (ctl <> nil) and (ctl <> mFocused) then ctl.setFocused(true);
+    ev.eat();
+    exit;
+  end;
 end;
 
-
-procedure TUIControl.keyEventPost (var ev: TFUIKeyEvent);
+procedure TUIControl.keyEvent (var ev: TFUIEvent);
 begin
 end;
 
@@ -2324,7 +2409,7 @@ begin
 end;
 
 
-procedure TUITopWindow.keyEvent (var ev: TFUIKeyEvent);
+procedure TUITopWindow.keyEventBubble (var ev: TFUIEvent);
 begin
   inherited keyEvent(ev);
   if (not ev.alive) or (not enabled) {or (not getFocused)} then exit;
@@ -2340,7 +2425,7 @@ begin
 end;
 
 
-procedure TUITopWindow.mouseEvent (var ev: TFUIMouseEvent);
+procedure TUITopWindow.mouseEvent (var ev: TFUIEvent);
 var
   lx, ly: Integer;
   vhgt, ytop: Integer;
@@ -2583,7 +2668,7 @@ begin
 end;
 
 
-procedure TUIBox.mouseEvent (var ev: TFUIMouseEvent);
+procedure TUIBox.mouseEvent (var ev: TFUIEvent);
 var
   lx, ly: Integer;
 begin
@@ -2595,7 +2680,7 @@ begin
 end;
 
 
-procedure TUIBox.keyEvent (var ev: TFUIKeyEvent);
+procedure TUIBox.keyEvent (var ev: TFUIEvent);
 var
   dir: Integer = 0;
   cur, ctl: TUIControl;
@@ -2934,7 +3019,7 @@ begin
 end;
 
 
-procedure TUITextLabel.mouseEvent (var ev: TFUIMouseEvent);
+procedure TUITextLabel.mouseEvent (var ev: TFUIEvent);
 var
   lx, ly: Integer;
 begin
@@ -2965,7 +3050,7 @@ begin
 end;
 
 
-procedure TUITextLabel.keyEventPost (var ev: TFUIKeyEvent);
+procedure TUITextLabel.keyEventBubble (var ev: TFUIEvent);
 begin
   if (not enabled) then exit;
   if (mHotChar = #0) then exit;
@@ -3168,7 +3253,7 @@ begin
 end;
 
 
-procedure TUIButton.mouseEvent (var ev: TFUIMouseEvent);
+procedure TUIButton.mouseEvent (var ev: TFUIEvent);
 var
   lx, ly: Integer;
 begin
@@ -3177,7 +3262,7 @@ begin
   begin
     ev.eat();
     mPushed := toLocal(ev.x, ev.y, lx, ly);
-    if (ev = '-lmb') and focused and mPushed then
+    if (ev = '-lmb') and (focused) and (mPushed) then
     begin
       mPushed := false;
       doAction();
@@ -3190,7 +3275,7 @@ begin
 end;
 
 
-procedure TUIButton.keyEvent (var ev: TFUIKeyEvent);
+procedure TUIButton.keyEvent (var ev: TFUIEvent);
 begin
   inherited keyEvent(ev);
   if (ev.alive) and (enabled) then
@@ -3397,7 +3482,7 @@ begin
 end;
 
 
-procedure TUISwitchBox.mouseEvent (var ev: TFUIMouseEvent);
+procedure TUISwitchBox.mouseEvent (var ev: TFUIEvent);
 var
   lx, ly: Integer;
 begin
@@ -3416,7 +3501,7 @@ begin
 end;
 
 
-procedure TUISwitchBox.keyEvent (var ev: TFUIKeyEvent);
+procedure TUISwitchBox.keyEvent (var ev: TFUIEvent);
 begin
   inherited keyEvent(ev);
   if (ev.alive) and (enabled) then
index 110833f92d4f2adb47abeb945d6c7ff18309275b..aff63479faed253bd63c89f1794b05caa9846ddb 100644 (file)
@@ -26,8 +26,16 @@ uses
 
 // ////////////////////////////////////////////////////////////////////////// //
 type
-  TFUIMouseEvent = 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,77 +47,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 TFUIKeyEvent);
-
-  public
-    procedure intrInit (); inline; // init hidden fields
-
-    function press (): Boolean; inline;
-    function release (): Boolean; inline;
-    function motion (): Boolean; inline;
-    function isAlive (): Boolean; inline;
-    procedure eat (); inline;
-    procedure cancel (); inline;
-
-  public
-    property eaten: Boolean read mEaten;
-    property cancelled: Boolean read mCancelled;
-    property alive: Boolean read isAlive; // not eaten and not cancelled
-  end;
-
-  TFUIKeyEvent = record
-  public
-    const
-      // modifiers
-      ModCtrl = $0001;
-      ModAlt = $0002;
-      ModShift = $0004;
-      ModHyper = $0008;
-
-    // event types
-    type
-      TKind = (Release, Press);
-
-  private
-    mEaten: Boolean;
-    mCancelled: Boolean;
-
-  public
-    kind: TKind;
+    // mouse events
+    but: Word; // current pressed/released button, or 0 for motion
+    // keyboard events
     scan: Word; // SDL_SCANCODE_XXX or 0 for character event
-    //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)
+    // 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 isAlive (): Boolean; inline;
+    function motion (): Boolean; inline;
+    function other (): Boolean; inline;
+    function simpleChar (): 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 alive: Boolean read isAlive; // not eaten and not cancelled
+    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;
 
 
@@ -121,8 +134,7 @@ procedure fuiResetKMState (sendEvents: Boolean=true);
 // ////////////////////////////////////////////////////////////////////////// //
 // event handlers
 var
-  evMouseCB: procedure (var ev: TFUIMouseEvent) = nil;
-  evKeyCB: procedure (var ev: TFUIKeyEvent) = nil;
+  fuiEventCB: procedure (var ev: TFUIEvent) = nil;
 
 
 // ////////////////////////////////////////////////////////////////////////// //
@@ -141,11 +153,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: TFUIKeyEvent; const s: AnsiString): Boolean;
-operator = (const s: AnsiString; constref ev: TFUIKeyEvent): Boolean;
-
-operator = (constref ev: TFUIMouseEvent; const s: AnsiString): Boolean;
-operator = (const s: AnsiString; constref ev: TFUIMouseEvent): Boolean;
+operator = (constref ev: TFUIEvent; const s: AnsiString): Boolean;
+operator = (const s: AnsiString; constref ev: TFUIEvent): Boolean;
 
 
 implementation
@@ -158,6 +167,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;
@@ -191,82 +224,109 @@ procedure fuiSetModState (v: Word); inline; begin curModState := v; end;
 
 
 // ////////////////////////////////////////////////////////////////////////// //
-procedure TFUIMouseEvent.intrInit (); inline; begin mEaten := false; mCancelled := false; end;
-function TFUIMouseEvent.press (): Boolean; inline; begin result := (kind = TKind.Press); end;
-function TFUIMouseEvent.release (): Boolean; inline; begin result := (kind = TKind.Release); end;
-function TFUIMouseEvent.motion (): Boolean; inline; begin result := (kind = TKind.Motion); end;
-function TFUIMouseEvent.isAlive (): Boolean; inline; begin result := (not mEaten) and (not mCancelled); end;
-procedure TFUIMouseEvent.eat (); inline; begin mEaten := true; end;
-procedure TFUIMouseEvent.cancel (); inline; begin mCancelled := true; end;
-
-procedure TFUIKeyEvent.intrInit (); inline; begin mEaten := false; mCancelled := false; ch := #0; scan := 0; end;
-function TFUIKeyEvent.press (): Boolean; inline; begin result := (kind = TKind.Press); end;
-function TFUIKeyEvent.release (): Boolean; inline; begin result := (kind = TKind.Release); end;
-function TFUIKeyEvent.isAlive (): Boolean; inline; begin result := (not mEaten) and (not mCancelled); end;
-procedure TFUIKeyEvent.eat (); inline; begin mEaten := true; end;
-procedure TFUIKeyEvent.cancel (); inline; begin mCancelled := true; end;
-
-function TFUIKeyEvent.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;
 
@@ -287,18 +347,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 TFUIKeyEvent.ModCtrl; Inc(pos, 2); continue; end;
-        'M', 'm': begin if (kmods = 255) then kmods := 0; kmods := kmods or TFUIKeyEvent.ModAlt; Inc(pos, 2); continue; end;
-        'S', 's': begin if (kmods = 255) then kmods := 0; kmods := kmods or TFUIKeyEvent.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 TFUIMouseEvent.Left; Inc(pos, 4); continue; end;
-        'R', 'r': begin if (mbuts = 255) then mbuts := 0; mbuts := mbuts or TFUIMouseEvent.Right; Inc(pos, 4); continue; end;
-        'M', 'm': begin if (mbuts = 255) then mbuts := 0; mbuts := mbuts or TFUIMouseEvent.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;
@@ -310,84 +370,81 @@ begin
 end;
 
 
-operator = (constref ev: TFUIKeyEvent; 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: TFUIKeyEvent): Boolean;
+operator = (const s: AnsiString; constref ev: TFUIEvent): Boolean;
 begin
   result := (ev = s);
 end;
 
 
-operator = (constref ev: TFUIMouseEvent; 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 := TFUIMouseEvent.Left
-  else if strEquCI(kname, 'RMB') then but := TFUIMouseEvent.Right
-  else if strEquCI(kname, 'MMB') then but := TFUIMouseEvent.Middle
-  else if strEquCI(kname, 'WheelUp') or strEquCI(kname, 'WUP') then but := TFUIMouseEvent.WheelUp
-  else if strEquCI(kname, 'WheelDown') or strEquCI(kname, 'WDN') or strEquCI(kname, 'WDOWN') then but := TFUIMouseEvent.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: TFUIMouseEvent): 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;
 
 
@@ -395,32 +452,27 @@ end;
 procedure fuiResetKMState (sendEvents: Boolean=true);
 var
   mask: Word;
-  mev: TFUIMouseEvent;
-  kev: TFUIKeyEvent;
+  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;
@@ -431,30 +483,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
-            TFUIKeyEvent.ModCtrl: begin kev.scan := SDL_SCANCODE_LCTRL; {kev.sym := SDLK_LCTRL;}{arbitrary} end;
-            TFUIKeyEvent.ModAlt: begin kev.scan := SDL_SCANCODE_LALT; {kev.sym := SDLK_LALT;}{arbitrary} end;
-            TFUIKeyEvent.ModShift: begin kev.scan := SDL_SCANCODE_LSHIFT; {kev.sym := SDLK_LSHIFT;}{arbitrary} end;
-            TFUIKeyEvent.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;
index 71e7993674f2a639278b6cbbe55e720a4a658ba5..611c4f8f721129821ae5362a1d04e4989a3143d5 100644 (file)
@@ -182,8 +182,7 @@ end;
 // ////////////////////////////////////////////////////////////////////////// //
 function fuiOnSDLEvent (var ev: TSDL_Event): Boolean;
 var
-  mev: TFUIMouseEvent;
-  kev: TFUIKeyEvent;
+  fev: TFUIEvent;
   uc: UnicodeChar;
   keychr: Word;
 
@@ -191,9 +190,9 @@ var
   begin
     result := 0;
     case b of
-      SDL_BUTTON_LEFT: result := result or TFUIMouseEvent.Left;
-      SDL_BUTTON_MIDDLE: result := result or TFUIMouseEvent.Middle;
-      SDL_BUTTON_RIGHT: result := result or TFUIMouseEvent.Right;
+      SDL_BUTTON_LEFT: result := result or TFUIEvent.Left;
+      SDL_BUTTON_MIDDLE: result := result or TFUIEvent.Middle;
+      SDL_BUTTON_RIGHT: result := result or TFUIEvent.Right;
     end;
   end;
 
@@ -226,64 +225,66 @@ begin
     SDL_KEYDOWN, SDL_KEYUP:
       begin
         // fix left/right modifiers
-        FillChar(kev, sizeof(kev), 0);
-        kev.intrInit();
-        if (ev.type_ = SDL_KEYDOWN) then kev.kind := TFUIKeyEvent.TKind.Press else kev.kind := TFUIKeyEvent.TKind.Release;
-        kev.scan := ev.key.keysym.scancode;
-        //kev.sym := ev.key.keysym.sym;
-
-        if (kev.scan = SDL_SCANCODE_RCTRL) then kev.scan := SDL_SCANCODE_LCTRL;
-        if (kev.scan = SDL_SCANCODE_RALT) then kev.scan := SDL_SCANCODE_LALT;
-        if (kev.scan = SDL_SCANCODE_RSHIFT) then kev.scan := SDL_SCANCODE_LSHIFT;
-        if (kev.scan = SDL_SCANCODE_RGUI) then kev.scan := SDL_SCANCODE_LGUI;
-
-        {
-        if (kev.sym = SDLK_RCTRL) then kev.sym := SDLK_LCTRL;
-        if (kev.sym = SDLK_RALT) then kev.sym := SDLK_LALT;
-        if (kev.sym = SDLK_RSHIFT) then kev.sym := SDLK_LSHIFT;
-        if (kev.sym = SDLK_RGUI) then kev.sym := SDLK_LGUI;
-        }
-
-        kev.x := fuiMouseX;
-        kev.y := fuiMouseY;
-        kev.bstate := fuiButState;
-        kev.kstate := fuiModState;
-
-        case kev.scan of
-          SDL_SCANCODE_LCTRL: if (kev.press) then fuiSetModState(fuiModState or TFUIKeyEvent.ModCtrl) else fuiSetModState(fuiModState and (not TFUIKeyEvent.ModCtrl));
-          SDL_SCANCODE_LALT: if (kev.press) then fuiSetModState(fuiModState or TFUIKeyEvent.ModAlt) else fuiSetModState(fuiModState and (not TFUIKeyEvent.ModAlt));
-          SDL_SCANCODE_LSHIFT: if (kev.press) then fuiSetModState(fuiModState or TFUIKeyEvent.ModShift) else fuiSetModState(fuiModState and (not TFUIKeyEvent.ModShift));
+        if (ev.type_ = SDL_KEYDOWN) then
+        begin
+          fev := TFUIEvent.Create(TFUIEvent.TType.Key, TFUIEvent.TKind.Press);
+        end
+        else
+        begin
+          fev := TFUIEvent.Create(TFUIEvent.TType.Key, TFUIEvent.TKind.Release);
+        end;
+        fev.scan := ev.key.keysym.scancode;
+
+        if (fev.scan = SDL_SCANCODE_RCTRL) then fev.scan := SDL_SCANCODE_LCTRL;
+        if (fev.scan = SDL_SCANCODE_RALT) then fev.scan := SDL_SCANCODE_LALT;
+        if (fev.scan = SDL_SCANCODE_RSHIFT) then fev.scan := SDL_SCANCODE_LSHIFT;
+        if (fev.scan = SDL_SCANCODE_RGUI) then fev.scan := SDL_SCANCODE_LGUI;
+
+        fev.x := fuiMouseX;
+        fev.y := fuiMouseY;
+        fev.bstate := fuiButState;
+        fev.kstate := fuiModState;
+
+        case fev.scan of
+          SDL_SCANCODE_LCTRL: if (fev.press) then fuiSetModState(fuiModState or TFUIEvent.ModCtrl) else fuiSetModState(fuiModState and (not TFUIEvent.ModCtrl));
+          SDL_SCANCODE_LALT: if (fev.press) then fuiSetModState(fuiModState or TFUIEvent.ModAlt) else fuiSetModState(fuiModState and (not TFUIEvent.ModAlt));
+          SDL_SCANCODE_LSHIFT: if (fev.press) then fuiSetModState(fuiModState or TFUIEvent.ModShift) else fuiSetModState(fuiModState and (not TFUIEvent.ModShift));
         end;
 
-        if assigned(evKeyCB) then
+        if (assigned(fuiEventCB)) then
         begin
-          evKeyCB(kev);
-          result := kev.eaten;
+          fuiEventCB(fev);
+          result := fev.eaten;
         end;
       end;
 
     SDL_MOUSEBUTTONDOWN, SDL_MOUSEBUTTONUP:
       begin
-        FillChar(mev, sizeof(mev), 0);
-        mev.intrInit();
-        if (ev.type_ = SDL_MOUSEBUTTONDOWN) then mev.kind := TFUIMouseEvent.TKind.Press else mev.kind := TFUIMouseEvent.TKind.Release;
-        mev.dx := ev.button.x-fuiMouseX;
-        mev.dy := ev.button.y-fuiMouseY;
+        if (ev.type_ = SDL_MOUSEBUTTONDOWN) then
+        begin
+          fev := TFUIEvent.Create(TFUIEvent.TType.Mouse, TFUIEvent.TKind.Press);
+        end
+        else
+        begin
+          fev := TFUIEvent.Create(TFUIEvent.TType.Mouse, TFUIEvent.TKind.Release);
+        end;
+        fev.dx := ev.button.x-fuiMouseX;
+        fev.dy := ev.button.y-fuiMouseY;
         fuiSetMouseX(ev.button.x);
         fuiSetMouseY(ev.button.y);
-        mev.but := buildBut(ev.button.button);
-        mev.x := fuiMouseX;
-        mev.y := fuiMouseY;
-        mev.bstate := fuiButState;
-        mev.kstate := fuiModState;
-        if (mev.but <> 0) then
+        fev.but := buildBut(ev.button.button);
+        fev.x := fuiMouseX;
+        fev.y := fuiMouseY;
+        fev.bstate := fuiButState;
+        fev.kstate := fuiModState;
+        if (fev.but <> 0) then
         begin
           // ev.button.clicks: Byte
-          if (ev.type_ = SDL_MOUSEBUTTONDOWN) then fuiSetButState(fuiButState or mev.but) else fuiSetButState(fuiButState and (not mev.but));
-          if assigned(evMouseCB) then
+          if (ev.type_ = SDL_MOUSEBUTTONDOWN) then fuiSetButState(fuiButState or fev.but) else fuiSetButState(fuiButState and (not fev.but));
+          if (assigned(fuiEventCB)) then
           begin
-            evMouseCB(mev);
-            result := mev.eaten;
+            fuiEventCB(fev);
+            result := fev.eaten;
           end;
         end;
       end;
@@ -291,63 +292,56 @@ begin
       begin
         if (ev.wheel.y <> 0) then
         begin
-          FillChar(mev, sizeof(mev), 0);
-          mev.intrInit();
-          mev.kind := TFUIMouseEvent.TKind.Press;
-          mev.dx := 0;
-          mev.dy := ev.wheel.y;
-          if (ev.wheel.y < 0) then mev.but := TFUIMouseEvent.WheelUp else mev.but := TFUIMouseEvent.WheelDown;
-          mev.x := fuiMouseX;
-          mev.y := fuiMouseY;
-          mev.bstate := fuiButState;
-          mev.kstate := fuiModState;
-          if assigned(evMouseCB) then
+          fev := TFUIEvent.Create(TFUIEvent.TType.Mouse, TFUIEvent.TKind.Press);
+          fev.dx := 0;
+          fev.dy := ev.wheel.y;
+          if (ev.wheel.y < 0) then fev.but := TFUIEvent.WheelUp else fev.but := TFUIEvent.WheelDown;
+          fev.x := fuiMouseX;
+          fev.y := fuiMouseY;
+          fev.bstate := fuiButState;
+          fev.kstate := fuiModState;
+          if (assigned(fuiEventCB)) then
           begin
-            evMouseCB(mev);
-            result := mev.eaten;
+            fuiEventCB(fev);
+            result := fev.eaten;
           end;
         end;
       end;
     SDL_MOUSEMOTION:
       begin
-        FillChar(mev, sizeof(mev), 0);
-        mev.intrInit();
-        mev.kind := TFUIMouseEvent.TKind.Motion;
-        mev.dx := ev.button.x-fuiMouseX;
-        mev.dy := ev.button.y-fuiMouseY;
+        fev := TFUIEvent.Create(TFUIEvent.TType.Mouse, TFUIEvent.TKind.Motion);
+        fev.dx := ev.button.x-fuiMouseX;
+        fev.dy := ev.button.y-fuiMouseY;
         fuiSetMouseX(ev.button.x);
         fuiSetMouseY(ev.button.y);
-        mev.but := 0;
-        mev.x := fuiMouseX;
-        mev.y := fuiMouseY;
-        mev.bstate := fuiButState;
-        mev.kstate := fuiModState;
-        if assigned(evMouseCB) then
+        fev.but := 0;
+        fev.x := fuiMouseX;
+        fev.y := fuiMouseY;
+        fev.bstate := fuiButState;
+        fev.kstate := fuiModState;
+        if (assigned(fuiEventCB)) then
         begin
-          evMouseCB(mev);
-          result := mev.eaten;
+          fuiEventCB(fev);
+          result := fev.eaten;
         end;
       end;
 
     SDL_TEXTINPUT:
-      if ((fuiModState and (not TFUIKeyEvent.ModShift)) = 0) then
+      if ((fuiModState and (not TFUIEvent.ModShift)) = 0) then
       begin
         Utf8ToUnicode(@uc, PChar(ev.text.text), 1);
         keychr := Word(uc);
         if (keychr > 127) then keychr := Word(wchar2win(WideChar(keychr)));
-        if (keychr > 0) and assigned(evKeyCB) then
+        if (keychr > 0) and (assigned(fuiEventCB)) then
         begin
-          FillChar(kev, sizeof(kev), 0);
-          kev.intrInit();
-          kev.kind := TFUIKeyEvent.TKind.Press;
-          kev.scan := 0;
-          kev.ch := AnsiChar(keychr);
-          kev.x := fuiMouseX;
-          kev.y := fuiMouseY;
-          kev.bstate := fuiButState;
-          kev.kstate := fuiModState;
-          evKeyCB(kev);
-          result := kev.eaten;
+          fev := TFUIEvent.Create(TFUIEvent.TType.Key, TFUIEvent.TKind.SimpleChar);
+          fev.ch := AnsiChar(keychr);
+          fev.x := fuiMouseX;
+          fev.y := fuiMouseY;
+          fev.bstate := fuiButState;
+          fev.kstate := fuiModState;
+          fuiEventCB(fev);
+          result := fev.eaten;
         end;
       end;
   end;
index c3c29df5646f86a93c0c3c6b42474c611f6dd17d..166cb61b7f9039a4c304f55c2c58a8e933085f3f 100644 (file)
@@ -32,8 +32,7 @@ uses
 procedure g_Holmes_Draw ();
 procedure g_Holmes_DrawUI ();
 
-procedure g_Holmes_MouseEvent (var ev: TFUIMouseEvent);
-procedure g_Holmes_KeyEvent (var ev: TFUIKeyEvent);
+procedure g_Holmes_OnEvent (var ev: TFUIEvent);
 
 // hooks for player
 procedure g_Holmes_plrViewPos (viewPortX, viewPortY: Integer);
@@ -331,6 +330,7 @@ begin
   //winHelp.appendChild(llb);
 
   uiLayoutCtl(winHelp);
+  winHelp.escClose := true;
   winHelp.centerInScreen();
 end;
 
@@ -543,16 +543,6 @@ function pmsCurMapX (): Integer; inline; begin result := round(msX/g_dbg_scale)+
 function pmsCurMapY (): Integer; inline; begin result := round(msY/g_dbg_scale)+vpy; end;
 
 
-procedure plrDebugMouse (var ev: TFUIMouseEvent);
-begin
-  //e_WriteLog(Format('mouse: x=%d; y=%d; but=%d; bstate=%d', [msx, msy, but, bstate]), MSG_NOTIFY);
-  if (gPlayer1 = nil) or not vpSet then exit;
-  //if (ev.kind <> TFUIMouseEvent.Press) then exit;
-  //e_WriteLog(Format('mev: %d', [Integer(ev.kind)]), MSG_NOTIFY);
-  msbindExecute(ev);
-end;
-
-
 {$IFDEF HOLMES_OLD_OUTLINES}
 var
   edgeBmp: array of Byte = nil;
@@ -1317,35 +1307,9 @@ end;
 
 
 // ////////////////////////////////////////////////////////////////////////// //
-procedure g_Holmes_MouseEvent (var ev: TFUIMouseEvent);
-var
-  he: TFUIMouseEvent;
-begin
-  if g_Game_IsNet then exit;
-  if not g_holmes_enabled then exit;
-  if g_holmes_imfunctional then exit;
-
-  holmesInitCommands();
-  holmesInitBinds();
-  msX := ev.x;
-  msY := ev.y;
-  msB := ev.bstate;
-  kbS := ev.kstate;
-  msB := msB;
-  he := ev;
-  he.x := he.x;
-  he.y := he.y;
-  uiMouseEvent(he);
-  if (he.alive) then plrDebugMouse(he);
-  ev.eat();
-end;
-
-
-// ////////////////////////////////////////////////////////////////////////// //
-procedure g_Holmes_KeyEvent (var ev: TFUIKeyEvent);
-var
-  doeat: Boolean = false;
+procedure onKeyEvent (var ev: TFUIEvent);
 {$IF DEFINED(D2F_DEBUG)}
+var
   pan: TPanel;
   ex, ey: Integer;
   dx, dy: Integer;
@@ -1356,32 +1320,13 @@ var
   end;
 
 begin
-  if g_Game_IsNet then exit;
-  if not g_holmes_enabled then exit;
-  if g_holmes_imfunctional then exit;
-
-  holmesInitCommands();
-  holmesInitBinds();
-
-  msB := ev.bstate;
-  kbS := ev.kstate;
-  case ev.scan of
-    SDL_SCANCODE_LCTRL, SDL_SCANCODE_RCTRL,
-    SDL_SCANCODE_LALT, SDL_SCANCODE_RALT,
-    SDL_SCANCODE_LSHIFT, SDL_SCANCODE_RSHIFT:
-      doeat := true;
-  end;
-
-  uiKeyEvent(ev);
-  if (not ev.alive) then exit;
-  if keybindExecute(ev) then begin ev.eat(); exit; end;
   // press
   if (ev.press) then
   begin
     {$IF DEFINED(D2F_DEBUG)}
     // C-UP, C-DOWN, C-LEFT, C-RIGHT: trace 10 pixels from cursor in the respective direction
     if ((ev.scan = SDL_SCANCODE_UP) or (ev.scan = SDL_SCANCODE_DOWN) or (ev.scan = SDL_SCANCODE_LEFT) or (ev.scan = SDL_SCANCODE_RIGHT)) and
-       ((ev.kstate and TFUIKeyEvent.ModCtrl) <> 0) then
+       ((ev.kstate and TFUIEvent.ModCtrl) <> 0) then
     begin
       ev.eat();
       dx := pmsCurMapX;
@@ -1406,7 +1351,61 @@ begin
     end;
     {$ENDIF}
   end;
+end;
+
+
+// ////////////////////////////////////////////////////////////////////////// //
+procedure g_Holmes_OnEvent (var ev: TFUIEvent);
+{$IF not DEFINED(HEADLESS)}
+var
+  doeat: Boolean = false;
+{$ENDIF}
+begin
+{$IF not DEFINED(HEADLESS)}
+  if g_Game_IsNet then exit;
+  if not g_holmes_enabled then exit;
+  if g_holmes_imfunctional then exit;
+
+  holmesInitCommands();
+  holmesInitBinds();
+
+  msB := ev.bstate;
+  kbS := ev.kstate;
+
+  if (ev.key) then
+  begin
+    case ev.scan of
+      SDL_SCANCODE_LCTRL, SDL_SCANCODE_RCTRL,
+      SDL_SCANCODE_LALT, SDL_SCANCODE_RALT,
+      SDL_SCANCODE_LSHIFT, SDL_SCANCODE_RSHIFT:
+        doeat := true;
+    end;
+  end
+  else if (ev.mouse) then
+  begin
+    msX := ev.x;
+    msY := ev.y;
+    msB := ev.bstate;
+    kbS := ev.kstate;
+    msB := msB;
+  end;
+
+  uiDispatchEvent(ev);
+  if (not ev.alive) then exit;
+
+  if (ev.mouse) then
+  begin
+    if (gPlayer1 <> nil) and (vpSet) then msbindExecute(ev);
+    ev.eat();
+  end
+  else
+  begin
+    if keybindExecute(ev) then ev.eat();
+    if (ev.alive) then onKeyEvent(ev);
+  end;
+
   if (doeat) then ev.eat();
+{$ENDIF}
 end;
 
 
@@ -1414,6 +1413,8 @@ end;
 procedure g_Holmes_Draw ();
 begin
   if g_Game_IsNet then exit;
+  if not g_holmes_enabled then exit;
+  if g_holmes_imfunctional then exit;
 
   {$IF not DEFINED(HEADLESS)}
   holmesInitCommands();
@@ -1437,6 +1438,7 @@ begin
   if g_Game_IsNet then exit;
   if not g_holmes_enabled then exit;
   if g_holmes_imfunctional then exit;
+
   {$IF not DEFINED(HEADLESS)}
   gGfxDoClear := false;
   //if assigned(prerenderFrameCB) then prerenderFrameCB();
@@ -1715,24 +1717,12 @@ begin
 end;
 
 
-procedure onMouseEvent (var ev: TFUIMouseEvent);
-begin
-  if not g_holmes_enabled then exit;
-  if g_holmes_imfunctional then exit;
-  g_Holmes_MouseEvent(ev);
-end;
-
-procedure onKeyEvent (var ev: TFUIKeyEvent);
 begin
-  if not g_holmes_enabled then exit;
-  if g_holmes_imfunctional then exit;
-  g_Holmes_KeyEvent(ev);
-end;
-
+  // shut up, fpc!
+  msB := msB;
+  vpSet := vpSet;
 
-begin
-  evMouseCB := onMouseEvent;
-  evKeyCB := onKeyEvent;
+  fuiEventCB := g_Holmes_OnEvent;
   //uiContext.font := 'win14';
 
   conRegVar('hlm_ui_scale', @fuiRenderScale, 0.01, 5.0, 'Holmes UI scale', '', false);
index dfa49305db8bcee42699e72c38d2b42ea36d5848..c701f58c1ff40beb6a5c423564c6e7b068748c2b 100644 (file)
@@ -280,7 +280,7 @@ begin
 end;
 
 
-function keybindExecute (var ev: TFUIKeyEvent): Boolean;
+function keybindExecute (var ev: TFUIEvent): Boolean;
 var
   f: Integer;
 begin
@@ -298,7 +298,7 @@ begin
 end;
 
 
-function msbindExecute (var ev: TFUIMouseEvent): Boolean;
+function msbindExecute (var ev: TFUIEvent): Boolean;
 var
   f: Integer;
 begin