Event Handling

Handling (querying and processing) keyboard, mouse, and window Events.

Preamble:

   Events are basically user actions like key press, clicks, mouse 
   movements, etc., or some occurrence like system generated notifications.

   The Event type plus the ScreenEvent function constitute a built-in 
   interface provided by FreeBASIC for accessing events (keyboard, mouse, 
   and window events).
   ScreenEvent signals the events so the user can write his own event 
   handlers (which require that he keeps track of the data himself).

   By using ScreenEvent, the user can check the events as they are returned 
   by the function.
   It is assumed that this function is regularly called to fetch events as 
   they are queued internally by the system.

Event type
   The Event type is a pre-defined structure in "fbgfx.bi" (in the FB 
   Namespace for the lang fb dialect only). 
   When the user calls ScreenEvent passing an instance of Event type, 
   ScreenEvent fills in it with the event data (if an event flag is 
   returned).

   Syntax
      #include once "fbgfx.bi"
      Using FB
      Dim variable As Event

   "Event" structure
      This structure is extracted from "fbgfx.bi".

   Type Event Field = 1
      Type As Long
      Union
         Type
            scancode As Long
            ascii As Long
         End Type
         Type
            x As Long
            y As Long
            dx As Long
            dy As Long
         End Type
         button As Long
         z As Long
         w As Long
      End Union
   End Type
         

      * .type field contains the event type ID value corresponding to one 
        of the following symbols defined in "fbgfx.bi":
            - For key events, one among:
                  EVENT_KEY_PRESS
                  EVENT_KEY_RELEASE
                  EVENT_KEY_REPEAT
            - For mouse event, one among:
                  EVENT_MOUSE_MOVE
                  EVENT_MOUSE_BUTTON_PRESS
                  EVENT_MOUSE_BUTTON_RELEASE
                  EVENT_MOUSE_DOUBLE_CLICK
                  EVENT_MOUSE_WHEEL
                  EVENT_MOUSE_HWHEEL
                  EVENT_MOUSE_ENTER
                  EVENT_MOUSE_EXIT
            - For window event, one among:
                  EVENT_WINDOW_GOT_FOCUS
                  EVENT_WINDOW_LOST_FOCUS
                  EVENT_WINDOW_CLOSE
      * .scancode and .ascii fields contain respectively the scancode 
        value and the ascii representation (if exists, otherwise 0) of the 
        key impacted by one event among:
            EVENT_KEY_PRESS
            EVENT_KEY_RELEASE
            EVENT_KEY_REPEAT
      * .x, .y and .dx, .dy fields contain respectively the new mouse 
        position (x, y) relative to the upper-left corner of the screen and 
        the motion deltas (dx, dy) induced by the event:
            EVENT_MOUSE_MOVE
      * .button field contains the identification (bit 0: left button, bit 
        1: right button, and bit 2: middle button) of the mouse button 
        impacted by one event among:
            EVENT_MOUSE_BUTTON_PRESS
            EVENT_MOUSE_BUTTON_RELEASE
            EVENT_MOUSE_DOUBLE_CLICK
      * .z field contains the new wheel position induces by the event:
            EVENT_MOUSE_WHEEL
      * .w field contains the new horizontal wheel position induces by the 
        event:
            EVENT_MOUSE_HWHEEL

      Note:
         - The last five field blocks [.scancode and .ascii], [.x, .y and 
         .dx, .dy], [.button], [.z] and [.w] are aligned on the same memory 
         location (because declared inside a Union type), so that filling 
         in one field block overwrites the other field blocks previously 
         filled in.
         - Therefore for example if the mouse position (mx, my) must be 
         available when the MOUSE_BUTTON_PRESS event is detected, this 
         position must have been previously stored in (mx, my) at each 
         MOUSE_MOVE event from its own event data (.x, .y), because (.x, .y
         ) is overwritten by any other event returned by ScreenEvent.

ScreenEvent function
   The ScreenEvent function allows querying and retrieving system events.

   Syntax
      Declare Function ScreenEvent ( ByVal event_instance_ptr As Any Ptr = 
      0 ) As Long

   Usage
      result = ScreenEvent( [ event_instance_ptr ] )
         with:
            event_instance_ptr: the buffer address where the function 
            should fill in the event data with a structure as the Event 
            type defined above.
            result: -1 if there are pending events to be retrieved, 0 
            otherwise.

Description
   The ScreenEvent function checks if there are any pending system events:
      - If there are no events, the function simply returns 0 (false).
      - If there is any, it returns -1 (true), and if the 
      event_instance_ptr pointer is not NULL, it copies the event data into 
      the user passed structure and removes the event (the latest 
      available) from the internal GfxLib events queue.
      - So to simply check if there are any pending events without 
      retrieving them (if there are any), nor even dequeue it from the 
      internal events queue, it is enough to call the function without 
      parameters (or with a NULL parameter).

   The user must first call ScreenEvent once to get one event, and then 
   check that one event against each event type he is interested in.
   Once the user has handled that event, he must quickly call ScreenEvent 
   again to handle the next event. Otherwise he will almost certainly miss 
   events (like for example EVENT_MOUSE_MOVE at least).
   This is why the program loop which tests the successive events must have 
   a period compatible with the rate of the events that the user wants to 
   intercept (for example, difference between keyboard input and mouse 
   move).
   Conversely, having an event test loop with a period significantly less 
   than 10 ms unnecessarily hogs the CPU.

   Event querying program skeleton
      As only one event is served by the ScreenEvent function at a given 
      time, the event querying program skeleton can be properly structured 
      around a [Select Case As Const...End Select] block.
      The different cases correspond to the different events that the user 
      wishes to handle.

   #include Once "fbgfx.bi"
   Using FB
   ' .....
   Dim e As Event
   ' .....
   Do
      If (ScreenEvent(@e)) Then
         Select Case As Const e.type
         Case EVENT_xxx
            ' handle the event xxx
         Case EVENT_yyy
            ' handle the event yyy
         ' .....
         ' .....
         Case EVENT_zzz
            ' handle the event zzz
         Case Else
            ' event not handled
         End Select
      End If
      Sleep 10, 1
   Loop
   ' .....
         

   If for example a key is held down for some seconds, this will induce:
      - a first EVENT_KEY_PRESS,
      - followed by several EVENT_KEY_REPEAT (for as long as the key is 
      held down),
      - and EVENT_KEY_RELEASE when the key is released.

   Two successive simple-click actions on a mouse obviously induce four 
   successive mouse events:
      EVENT_MOUSE_BUTTON_PRESS
      EVENT_MOUSE_BUTTON_RELEASE
      EVENT_MOUSE_BUTTON_PRESS
      EVENT_MOUSE_BUTTON_RELEASE
   An only double-click action on a mouse also induces four successive 
   mouse events, but:
      EVENT_MOUSE_BUTTON_PRESS
      EVENT_MOUSE_BUTTON_RELEASE
      EVENT_MOUSE_DOUBLE_CLICK
      EVENT_MOUSE_BUTTON_RELEASE

   Note:
      - If the user program retrieves  a KEY_... event, this does not clear 
      the keyboard buffer. If the keyboard buffer needs to be cleared after 
      user retrieves the event, he will need to clear it manually (see Inkey
      ).
      - Event queuing and InKey buffering are two completely independent 
      ways to test the keyboard state. They can coexist, even in two 
      competing threads without any conflict.
      - There are currently three ways to test input from the keyboard 
      using gfxlib:
            using Inkey (user can test only keys that have an ascii 
            representation),
            using MultiKey (user can query the state pressed/released of 
            any key on keyboard),
            using ScreenEvent (user can check the KEY_PRESS, KEY_RELEASE 
            and KEY_REPEAT events).

Examples
   Example of simple ScreenEvent mechanism to implement a key-pressed 
   events handling:
   (click on the window-close button [X] to exit)
   '   The main code tests events in a loop:
   '       - calls a user Sub each time a key-pressed event is retrieved
   '       - exits the loop if a window-close event is retrieved (by click on window-close button)
   '   The user Sub prints the character of the key pressed, the ascci code and the scancode.

   #include Once "fbgfx.bi"
   Using FB

   '' user callback Sub definition
   Sub printInkeyData (ByVal ascii As Long, ByVal scancode As Long)
      Print "'" & Chr(ascii) & "' (" & ascii & ")", scancode
   End Sub

   '' user main code
   Screen 12
   Dim e As Event
   Do
      If (ScreenEvent(@e)) Then
         Select Case As Const e.type
         Case EVENT_KEY_PRESS                     '' test key-pressed event
            printInkeyData(e.ascii, e.scancode)
         Case EVENT_WINDOW_CLOSE                  '' test window-close event
            Exit Do
         End Select
      End If
      Sleep 10, 1
   Loop
         

   Simple example highlighting that Event queuing and InKey buffering can 
   coexist, even in two competing threads without any conflict:
   (ESC to quit)
   '   The main code (main thread) tests Inkey in a loop.
   '   The other thread tests ScreenEvent in a loop.
   '   The ESC character allows to exit the two loops.

   #include "fbgfx.bi"
   Using FB

   Function getAscii (ByVal ascii As Long) As String
      If ((ascii>0) And (ascii<255)) Then
         Return "'" & Chr(ascii) & "'"
      Else
         Return "???"
      End If
   End Function

   Sub Thread (ByVal p As Any Ptr)
      Dim e As Event
      Do
         If (ScreenEvent(@e)) Then
            Select Case As Const e.Type
            Case EVENT_KEY_PRESS                                      '' test key-pressed event
               Print getAscii(e.ascii) &_ 
               " is pressed    (from ScreenEvent)   (other thread)"
               If (e.scancode=SC_ESCAPE) Then                        '' test ESC
                  Exit Sub
               End If
            Case EVENT_KEY_RELEASE                                    '' test key-released event
               Print getAscii(e.ascii) &_ 
               " is released   (from ScreenEvent)   (other thread)"
            Case EVENT_KEY_REPEAT                                     '' test key-repeated event
               Print getAscii(e.ascii) &_ 
               " is repeated   (from ScreenEvent)   (other thread)"
            End Select
         End If
         Sleep 10, 1
      Loop
   End Sub

   Screen 12

   Dim As String s
   Dim As Any Ptr pt
   pt = ThreadCreate(@Thread)

   Do
      s = Inkey
      If s <> "" Then                                                   '' test inkey return
         Print getAscii(s[0]) &_ 
         " is viewed     (from Inkey)         (main thread)"
      End If
      Sleep 10, 1
   Loop Until s = Chr(27)                                                '' test ESC

   ThreadWait(pt)
   Print "main and other thread completed"

   Sleep
         

   Example of mouse event handling where cursor position and buttons state 
   are stored in a Type derived of EVENT to be available at the time of 
   events other than those that normally provide them:
   (click on the window-close button [X] to exit)
   ' Memorization in (.mx, .my) of the cursor position of the mouse:
   '     - at MOUSE_MOVE event : e.mx = e.x, e.my = e.y
   ' Memorization in .mbutton of the buttons state of the mouse:
   '     - at MOUSE_BUTTON_PRESS event and MOUSE_DOUBLE_CLICK event : e.mbutton = e.mbutton Or e.button
   '     - at MOUSE_BUTTON_RELEASE event : e.mbutton = e.mbutton Xor e.button

   #include Once "fbgfx.bi"
   Using FB

   Type EVENTstore Extends Event
      mx As Long
      my As Long
      mbutton As Long
   End Type

   Screen 19
   Dim e As EVENTstore
   Do
      If ScreenEvent(@e) Then
         Select Case As Const e.Type
         Case EVENT_MOUSE_MOVE
            e.mx = e.x
            e.my = e.y
            Print Using "Mouse move:                  x=####   /   y=####      dx=####  /  dy=####      button=##";_ 
            e.x; e.y; e.dx; e.dy; e.mbutton
         Case EVENT_MOUSE_BUTTON_PRESS
            e.mbutton = e.mbutton Or e.button
            Print Using "Mouse button press:          button =##               x=####   /   y=####";_ 
            e.button; e.mx; e.my
         Case EVENT_MOUSE_BUTTON_RELEASE
            e.mbutton = e.mbutton Xor e.button
            Print Using "Mouse button release:        button =##               x=####   /   y=####";_ 
            e.button; e.mx; e.my
         Case EVENT_MOUSE_DOUBLE_CLICK
            e.mbutton = e.mbutton Or e.button
            Print Using "Mouse button double click:   button =##               x=####   /   y=####";_ 
            e.button; e.mx; e.my
         Case EVENT_MOUSE_WHEEL
            Print Using "Mouse wheel:                 wheel=  ###########      x=####   /   y=####";_ 
            e.z; e.mx; e.my
         Case EVENT_MOUSE_HWHEEL
            Print Using "Mouse hwheel:                hwheel= ###########      x=####   /   y=####";_ 
            e.z; e.mx; e.my
         Case EVENT_WINDOW_CLOSE
            Exit Do
         End Select
      End If
      Sleep 10, 1
   Loop
         

See also
   * Event
   * ScreenEvent
   * Inkey
   * MultiKey
   * GetMouse
   * Keyboard scancodes

