R3 View - Event Handling
Contents | ||
R3 Graphical User Interface (GUI)
Concepts
An event is a signal that drives GUI actions.
It is a method of notification that includes an event type along with a set of related attributes. User inputs such as moving the mouse, clicking a mouse button, or pressing a key cause events to occur that are handled by mechanisms implemented within the GUI system.
For example, when a mouse button is pressed, you need to know which button was pressed, that it went down and also up, where the mouse was positioned, and other modifiers, such as the shift or control keys that were being held down at the time.
An event handler is software that processes an event when it occurs and makes the necessary changes to the state of GUI objects. For example, if the mouse button is clicked while the mouse was over a button image, then the handler will cause the logic of the button to operate, including its end result, a reactor.
See the lower level event system description for more information.
Mechanism
For simple GUI scripts, you do not need to understand the event system. You can create panels with buttons and other styles, and they will work just fine. However, some developers may want to know the basics, and the easiest way to understand the event system is to look at a specific example.
Basic Mechanism
Let's say the user has pressed down the left mouse button at position 100x100 within a specific window. Here's the basic sequence of events:
- User presses a mouse button.
- A sequence of event handlers is called to process the event.
- The the GUI handler calls style actors to update the display or take some other action.
- The actors may trigger a reactor that produces an end-result, such as opening a new window or launching a web browser.
This is the basic information you need to know to understand GUI events. However, if you need to do any special processing for events, such as write your own style actions, you may need to know more. See the next section.
Detailed Mechanism
For developers who want to understand the complete GUI event mechanism, here are the full details of the above mouse button click sequence.
- The user presses down the left mouse button.
- The underlying OS detects the event.
- The OS notifies the REBOL event device, low level C code functions.
- The device decodes the mouse information including the down state of the button, the window GOB, the position at 100x100, and any other details.
- An event! datatype is obtained (from a preallocated block). Its fields are set with the information above. For example, its type field is set to down.
- The event is queued to the system port. This is an event concentrator (a funnel.) From the C code side, all events of all kinds are queued here.
- Control returns to the REBOL interpreter.
- The event wakes up the system port.
- The system port redirects the event to the GUI event port. (The system port is an event dispatcher. All REBOL side events originate from here.)
- The GUI event port examines the event and uses the window GOB to locate a GUI handler specific to that window. Most of the time, this is the general GUI system handler, but users can provide custom handlers as well (e.g. for high-performance code, games, etc.)
- The GUI handler is called via its do-event function.
- The handler dispatches a specific function associated with the down event.
- The down event maps its location to a specific face, such as a button face that was displayed on screen earlier.
- If a face is present, the on-click actor for its style is called.
- The style actor then calls any reactor functions, such as setting a variable, evaluating a block of code, scrolling a panel, etc.
Missing image: /diagram-needed
All of this is done very efficiently with as few extra steps as possible in order to keep the GUI highly responsive to user interaction.
For more information, see the Event System documentation or browse the GUI source code in the R3 DevBase archive.
Event Handling Layers
As you can see from the above discussion, there are various layers where events are handled.
native level | The machine code native level written in C code. This is high-performance code and should only be modified by experts. (It's source is part of the R3 Host-kit.) |
system port | Used to isolate and concentrate the native level from the interpreter level. All events, including GUI and networking events, pass through this code. It is also highly tuned code, and should only be modified by experts. |
view handler | Handles events for the graphics system. REBOL provides two default handlers: a simple one for simple window event handling, such as done in GOB windows, and an advanced one for processing GUI events. Users can create their own window handlers. |
style actors | Every style has actor functions that process events for faces, and they can be written by users familiar with GUI style and face operation. |
face reactors | The highest level functions. These provide the end result for an event, such as opening a window, writing a file, evaluating code, etc. |
Although code can be added or modified at any layer, it is advised that you only change the layers that you understand completely. Improper changes can cause intermittent problems, lost events, or event queue overflows.
System Port Awake
As mentioned above, the system port provides the main handler for REBOL. It's main purpose is to isolate native events from interpreter events, it decouples the two subsystems. This is necessary to prevent the host and the interpreter from intermixing their specific environments in a way that would cause problems for system resources, including heap and stack allocation.
At the native host level, when host code is running, all events are appended to an event queue. Eventually, when control returns to the REBOL interpreter kernel, it examines the queue to determine what events have occurred. This process decouples the host from the interpreter.
At the core of the system port is an event handler called awake that examines the event queue and dispatches events to specific port-based event handlers. This is done through a special wake-up function.
Generally, you should not modify the system port awake handler unless you know what you're doing. This code needs to be efficient. Even adding a few more lines to it will slow down all REBOL port I/O event handling.
However, if you have discovered an improvement or tweak that improves the code without serious side effects, please post it on R3 Chat (DevBase).
The system port awake function as of publication of this document is:
awake: func [ sport "System port (State block holds events)" ports "Port list (Copy of block passed to WAIT)" /local event port waked ][ waked: sport/data ; The wake list (pending awakes) ; Process all events (even if no awake ports). ; Do only 8 events at a time (to prevent polling lockout). loop 8 [ unless event: take sport/state [break] port: event/port if wake-up port event [ ; Add port to wake list: ;print ["==System-waked:" port/spec/ref] unless find waked port [append waked port] ] ] ; No wake ports (just a timer), return now. unless block? ports [return none] ; Are any of the requested ports awake? forall ports [ if find waked first ports [return true] ] false ; keep waiting ]
It's archive location is in mezz-ports.r in the R3 Mezzanine section of DevBase.
View Handlers
The View graphics system provides the next layer of event handling. These types of events are related to user inputs that occur through windowing and related user input devices.
Event Scheme
The event scheme provides a port definition that is used for view system events. It stores a list of handler objects that are dispatched by a central view-based event awake function. When an event occurs, its window field is examined for a handler.
Handler Objects
The view system defines a standard handler object that consists of these fields:
Field | Datatype | Description |
---|---|---|
name | word! | Identification of the handler. Used for debugging purposes, and it's useful to figure out what's going on when you have more than one handler running |
about | string! | Short description of the handler. Used for help. |
status | word! | The status of the handler. |
priority | integer! | The urgency of the handler. Controls where the handler is inserted into the handler list. A higher values is processed earlier. |
do-event | function! | Main function to process events. Called whenever events occur. |
The base event handler object is defined as:
base-handler: context [ do-event: func [event] [ ; at top for performance print "(Missing event handler)" event ] win-gob: none status: 'made name: 'no-name priority: 0 about: "Main template for VIEW event handlers." ]
Handler API
The functions for view handlers are:
Function | Description |
---|---|
handler-events | Adds a handler to the view event system. |
unhandle-events | Removes a hander from the view event system. |
handled-events? | Returns event handler object matching a given name. |
do-events | Waits for window events. Returns when all windows are closed. |
Adding a Handler
There are two ways to add a view-level handler: you can provide it as an option to the view function or install it with the handle-events function.
The format of a handler is an object definition block of the form described above.
Here's an example handler, notice it's just a block here:
my-handler: [ name: 'my-handler priority: 50 handler: func [event] [ print ["event:" event/type event/offset] if switch event/type [ close [true] key [event/key = escape] ] [ unhandle-events self unview event/window quit ] show event/window none ] ]
It can be provided to the view function at the same time the window GOB is provided:
view/options window [handler: my-handler]
Or, it can be installed with:
handle-events my-handler
Notice that this view handler will remove itself with unhandle-events when its window is closed.
Event Fields
An event value can decode these fields using refinements. Some fields may be meaningless for specific events.
Field | Datatype | Description |
---|---|---|
type | word! | A word that indicates the type of event. See list below. |
port | port! | The port for the event. For the GUI, this is the event port; however, for non-GUI ports, this field is overloaded and can contain other information. |
gob | gob! | The GOB where the event occurred. By default, the window GOB. |
window | gob! | Alias for above. |
offset | pair! | The position (only valid for mouse and size events). |
key | char! word! | Key char or word (only valid for keyboard events). See list below for word-based characters. |
flags | block! none! | A block of possible modifiers: double control shift |
code | integer! | The integer code for a key down event. |
data | file! none! | For a drop-file event, provides the file name. |
Event Types
Possible event types are defined in system/view/event-types. They are:
ignore interrupt device custom error init open close connect accept read write wrote lookup ready done time show hide offset resize active inactive minimize maximize restore move down up alt-down alt-up aux-down aux-up key key-up scroll-line scroll-page drop-file
Keyboard Events
Keyboard events can be character value or word value for virtual keys like insert and :home.
Current virtual key words are defined in system/view/event-keys:
page-up page-down end home left up right down insert delete f1 f2 f3 f4 f5 f6 f7 f8 f9 f10 f11 f12
Of course, F10 may be a problem on most Windows systems.