From 08b53e05935bfc8f5c8e6c18754fd3768917aa77 Mon Sep 17 00:00:00 2001 From: Ketmar Dark Date: Wed, 4 Oct 2017 21:51:25 +0300 Subject: [PATCH] FlexUI: switched to universal event struct; changed event dispatching to sink/mine/bubble model --- src/flexui/fui_ctls.pas | 407 ++++++++++++++++++------------- src/flexui/fui_events.pas | 496 +++++++++++++++++++++----------------- src/flexui/sdlcarcass.pas | 172 +++++++------ src/game/g_holmes.pas | 142 +++++------ src/game/g_holmes_cmd.inc | 4 +- 5 files changed, 670 insertions(+), 551 deletions(-) diff --git a/src/flexui/fui_ctls.pas b/src/flexui/fui_ctls.pas index 27f14fd..38f17fd 100644 --- a/src/flexui/fui_ctls.pas +++ b/src/flexui/fui_ctls.pas @@ -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 diff --git a/src/flexui/fui_events.pas b/src/flexui/fui_events.pas index 110833f..aff6347 100644 --- a/src/flexui/fui_events.pas +++ b/src/flexui/fui_events.pas @@ -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; diff --git a/src/flexui/sdlcarcass.pas b/src/flexui/sdlcarcass.pas index 71e7993..611c4f8 100644 --- a/src/flexui/sdlcarcass.pas +++ b/src/flexui/sdlcarcass.pas @@ -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; diff --git a/src/game/g_holmes.pas b/src/game/g_holmes.pas index c3c29df..166cb61 100644 --- a/src/game/g_holmes.pas +++ b/src/game/g_holmes.pas @@ -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); diff --git a/src/game/g_holmes_cmd.inc b/src/game/g_holmes_cmd.inc index dfa4930..c701f58 100644 --- a/src/game/g_holmes_cmd.inc +++ b/src/game/g_holmes_cmd.inc @@ -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 -- 2.29.2