REBOL

REBOL/View Graphic System Reference

Draft of 9-Aug-2005
Copyright 2005 REBOL Technologies

Note: Some images still pending.
Please send us comments/corrections

Contents:

1. Overview
1.1 Design Objectives
1.2 Architecture
1.3 How VID Relates to View
1.4 About Examples
2. Face Object
2.1 Definition
2.2 Fields (Facets/Attributes)
2.3 Making Faces
2.4 Deleting Faces
2.5 Debugging Faces
3. Face Hierarchy
3.1 Panes
3.2 Relative Coordinates
3.3 Depth Arrangement
3.4 Screen Face
3.5 Window Faces
3.6 Panels
3.7 Important Notes
4. Face Functions
4.1 Show Function for Display and Refresh
4.2 Hide Function
4.3 View and Unview
4.4 Special Face Changes
4.5 Window Face Options
4.6 Other Functions
5. Event Handling
5.1 Event Flow
5.2 Feel object
5.3 Feel Functions
5.4 Redraw Function
5.5 Over Function
5.6 Engage Function
5.7 Detect Function
5.8 Timers
5.9 Event Datatype
5.10 Event Types
5.11 Event Offset Meaning
5.12 The Event Port
5.13 Global Event Handler
6. Face Text
6.1 Text Field
6.2 Font Facet Object
6.3 Para Facet Object
6.4 Focus and Keyboard Events
6.5 Text Caret Position
6.6 Text Position Mapping
6.7 Text Highlighting
6.8 Text Scrolling
6.9 Text Sizing
6.10 Text Clipboard
7. Face Images
8. Face Edges
8.1 Definition
9. Face Effects
9.1 Merging Background
9.2 Scaling
9.3 Tiling
9.4 Subimages
9.5 Rotation, Reflection, Flipping
9.6 Image Processing
9.7 Gradients
9.8 Keys
9.9 Algorithmic Shapes
9.10 Pending Docs
10. Iterated Virtual Faces
10.1 The Iteration Function
10.2 The Iteration Index
10.3 Iteration Termination
10.4 Iteration Example
11. Special Notes
11.1 Image Datatype
11.2 Converting a Face to an Image
11.3 Face Scrolling
11.4 Known Problems
12. Examples
12.1 A Drag and Drop Example

1. Overview

This document describes the lower level graphical compositing and user interface system of REBOL. This is the fundamental graphics engine on which all REBOL graphics displays and user interfaces are built.

This is a low level technical document for programmers, developers, and engineers. It does not describe higher level user interface methods such as VID (visual interface dialect), but many parts of this document apply equally to the components used within VID. These concepts are useful to both experts and beginners who require a deeper understanding of REBOL user interfaces of all kinds. It is recommended that you read this document carefully if you plan to build more sophisticated types of user interfaces in REBOL.

1.1 Design Objectives

The REBOL/View engine was designed to be powerful yet minimal. Rather than build a user interface system based on static interface elements (buttons, fields, and other "widgets"), we decided to design a system for building interface systems. We engineered the system to be:

  • Flexible - We wanted the ability to implement not only desktop user interfaces, but also rich interfaces like those used for consumer catalogs or kiosks, presentation systems such as those used for lectures or televisions programs, and even a wide variety of games.

  • Dynamic - We wanted any and all graphical objects to be able to move, resize, or modify any of their attributes at any time. This allows users to create not only standard, static, computer-style user interfaces, but also dynamic, animated, interfaces.

  • Easy to use - The View graphics system adheres to the REBOL principle that simple things should be simple to do, that complex results should be possible, and that there should be a relatively smooth ramp between the two. You can create a functional user interface with almost no knowledge of the system, and you can use many parts of the graphics system without the need to understand it in its entirety.

  • Minimal implementation - At the lower levels, we wanted to reduce the number of required functions and objects to an absolute minimum; and we succeeded. The primary system is implemented with only two native functions (show and hide) and a single object definition (face).

  • Minimize memory - To help reduce memory consumption, we decided to recompute (regenerate) the bitmaps for all objects on the fly, as required for display. The system does not store intermediate bitmap images (unless specified by the program).

  • Built-in filters - We wanted to include a broad set of built in image processing filters for creating rich, good-looking applications. These are implemented as face effects within the system, and are very easy to use.

1.2 Architecture

The View graphics system is a two dimensional compositing engine. It takes a hierarchy of graphical objects and combines them in layers to create displays and interfaces.

REBOL uses a single face object for all graphics. A face can be used to show images, create buttons, generate text, detect mouse events, or group multiple faces together within a single pane. User interfaces consist of multiple faces arranged in panes. A typical application may be hundreds of faces that create its final display.

Each face object handles its own image, text, alignment, clipping, edges (borders), special effects, and event processing. In addition, a face can contain sub-faces and act as its own coordinate system.

1.3 How VID Relates to View

VID, the Visual Interface Dialect, provides an easy-to-use descriptive method of creating user interfaces. It allows programmers to concentrate more on results, and less on the programming details.

VID is really just a translator. When you create interfaces with VID, you are actually using the View graphics system indirectly. As a dialect, VID converts simple descriptive expressions into sets of faces and panes that are processed by the View graphics system.

VID is just one of many possible ways to create an interface. You can also write programs that use the View graphics system directly, bypassing VID. Or, more typically, you use a combination of both VID and the graphics system. VID creates the same face object(s) you would if you used the graphics system directly; it just reduces the amount of code you have to write for common scenarios, while still providing access to the low-level system for those times you need total control. This deep level of integration is powerful, flexible, and easy to use.

In addition, you can create your own interface dialect on top of the View graphics system. For instance, some REBOL users have created dialects that are more efficient at expressing presentations, plotting graphs, and producing games.

1.4 About Examples

Most of the examples in this document have been tested and verified to work properly in REBOL/View 1.3.1 (and higher). If you are using an older version of REBOL, you should upgrade.

To try an example for yourself, cut and paste it into a text file, then add a REBOL header such as this to the top of the file:

REBOL [Title: "Example"]

Many of the examples will also work if you simply past them into REBOL at the console prompt.

If you discover an example that does not work, please let us know by clicking on the contact link above.

2. Face Object

The entire REBOL graphics engine is built around a single, powerful element: the face object (named after interface). REBOL's design is unique in that faces provide not only the lower level graphical objects of the system, but also the drawing canvas (raster or bitmap image), as well as the view (display port or window that projects the canvas). The look and feel of every graphical and user interface element is created and controlled by a face. Each REBOL window consists one or more faces, and a typical window is often built from hundreds of such faces.

2.1 Definition

The face object is defined by the standard object found in system/standard/face. This master face is the parent face for all other faces. Everything else is based on it, and when creating your own faces, it is important to inherit from this face object due to its relationship with the underlying REBOL implementation. Do not use your own definition of face or your programs may not work correctly.

This master face is used so often that a global variable called face also holds its value.

Note that the face object used by VID is derived from this master face. The VID face extends the object to provide additional fields required by the VID system. However, you can create useful and functional GUIs with the master face alone. You do not need the VID object definition unless you are using the VID system.

2.2 Fields (Facets/Attributes)

The fields below define the attributes of a face. In REBOL, these fields are called facets (named after the word "face") and they define every aspect of a face, including its state, properties, and behavior. Most of these variables are optional and should be set to NONE to save memory if they are not required.

To view the fields of the face object you can type:

>> help face

(This is handy to know while you are programming.)

Field

Datatype

Description

type

word!

This field is used internally, by the system, to verify that the object is structured as a face. It must be set to the word 'face.

offset

pair!

An xy pair that specifies the horizontal and vertical position of the upper left corner of a face. The default is 0x0. If the offset falls outside its parent face area (such as a negative offset value) the face will be clipped. If the parent face is the screen-face, then the offset is the location of the window on the screen. (See more below.)

size

pair!

An xy pair that specifies the width and height of the face in pixels. The defaults is 100x100. Negative values are not valid. The size includes the edge (border) if it is provided (see below). If the face is a window, then this size does not include the window borders and title bar.

span

pair!

Reserved. Must be set to none to be compatible.

pane

block! | object! | function!

A face or block of sub-faces that are to be displayed within the face. The pane allows you to create faces that contain other faces, to any depth. This is how all graphical interfaces are constructed in REBOL. A pane establishes a new coordinate system where each of its subfaces is positioned relative to its upper left corner. When a function is provided, the pane will be iterated based on the values produced by the function. This is useful for tables, lists, and more. We'll talk more about iterated faces below.

text

any-type

The text contents of a face. The text can be just a few letters or thousands of lines. Note that any printable (form-able) REBOL datatype can be specified; however, strings are optimal if a fast redraw speed is required, such as those used in iterated faces. (Non-string values must be converted to strings during the redraw operation, slowing down the redraw.) The attributes of the text (color, size, font, style) are determined by the font and para facets (see below). For large text areas, please read the line-list notes below.

color

tuple!

The color of a face. The default value is 128.128.128. A value of 0.0.0 is black, 255.255.255 is white, etc. Set color to none to make a face transparent. This color value is also used as the default for some types of face effects, such as gradients. (Note that semi-transparent faces are created using face effects described below, not by specifying an alpha value in this field).

image

image!

A background image for the face. This field must be an image! value, not a file name. Use the load function to load an image file prior to assigning it to this variable. Set this field to none for transparent or colored faces. Note that the image may be modified according to the effects block (scaling the image to the size of the face, changing its color, modifying the brightness, etc.)

effect

block! | word!

Special effects for a face. A wide range of image processing effects are available. Multiple effects can be applied at the same time using the effect dialect. For example, you can change both the contrast and brightness of an object. See the Effects section for more information.

data

user-defined

This is a variable for storing state and other higher level information about the face. The type of data is determined by the programmer and the purpose of the face. For example, VID uses this variable to store the primary data of the face. Note that this field is not the only way to add "user" data to a face. The face object itself can be extended (using object inheritance) to allow additional data fields.

edge

object!

Specifies the border attributes of a face. This value is an object that includes the size, color, and effects for the edge (e.g. bevel, inverse bevel). If you want to modify the edge object, you should copy it first. See the Edge section below.

font

object!

Specifies the text font attributes of a face. This value is an object that describes the font style, size, color, alignment, shadow, and other attributes that can be provided. If you want to modify the font object, you should copy it first. See Font section.

para

object!

Provides the text paragraph attributes of a face. This object controls the text origin, margin, indentation, tabs, scrolling and other attributes. If you want to modify the para object, you should copy it first. See Para section.

feel

object!

Defines the behavior of a face. The feel object consists of a set of callback functions that are executed when specific face events occur. See the event handling section below.

saved-area

logic! | image!

Enables faster rendering for transparent faces. When a face is transparent on a static (unchanging) backdrop, this field can be used to accelerate redrawing. It allows the face to change without requiring the backdrop (or any other faces that are behind this face) to be recomputed each time. If this variable is set to true initially by the programmer, when the system shows the face, the background image that it covers will be stored here.

rate

integer! | time!

An integer! or time! that specifies periodic timer events for a face. This is used for animation or repetitive events (such as holding the mouse down on certain types of user interface styles). An integer! value indicates the number of events per second. A time! provides the period between events. The timer event is sent to the face feel Engage function with an event type of time.

show?

logic!

A flag that enables/disables showing of the face. This flag can be used to temporarily remove a face from view.

options

block!

Set options in a window face. This allows you to set whether a window has a title bar and borders; allows resizing; changes how over (hover) events are handled, and more.

parent-face

object!

The parent of the face. This is set by the system when the face is first shown and it is used to maintain the face hierarchy in memory. If necessary, you can use this field to determine a face's parent face. (Be sure to expect a none value if no parent exists, such as the top-level window face).

old-offset

pair!

Internal bookkeeping variable. Do not modify. Used by the system to determine dirty regions when faces are moved or resized.

old-size

pair!

Internal bookkeeping variable. Do not modify. Used by the system to determine dirty regions when faces are moved or resized.

line-list

block!

A block that holds text line information. This block is created, updated, and used by the system to optimize the updating of text. When you modify large areas of text (greater than 200 characters) you should set this variable to zero to force the system to recompute the positions of all lines. If you do not, you may see garbage characters appearing within the text.

changes

block!

Tell the graphics system what fields have changed. This is not normally required, but can be used to signal specific changes in a face that require special attention. For example, if you want to change a window title (caption) or activate a window, this variable must be set prior to the show function.

face-flags

integer!

Internal bookkeeping variable. Do not modify.

action

any-type

A user-defined field normally used to hold a function that will be called when a user-interface action is taken. If you are using faces directly (not in VID) you can use this field to store your own action function.

2.3 Making Faces

Within applications, there are several ways to create the necessary faces. For instance, you can use VID to create the face objects. It's easy and quick to use.

Of course, as objects, you can create various prototypical faces (classes) for different types of display objects or control objects (input methods like fields, buttons, etc.) Then you can make new object instances easily for each type of face.

New faces (and/or prototypes) can always be created directly from the master face. The standard object creation method is used:

a-face: make face [
    offset: 100x100
    size: 200x100
    color: red
    effect: [gradient]
]

This creates a new face object called a-face. All fields are inherited from the master face, except those new fields that are specified.

To see what the face looks like, use the view function:

view a-face

The face will be shown in a window that looks like this:

This window appeared at 100x100 on the screen, and shows a horizontal red to cyan gradient (based on the face's color).

Actually, since this is a top level face, it defines a window and has a few special properties. For example, if you attempt to use text, it will appear in the window caption. More about that later.

2.4 Deleting Faces

You do not need to delete faces. Once you no longer use a face and there are no other references to it, REBOL will automatically garbage collect the face (recycle its memory).

For complex user interfaces, or those requiring a lot of images or special effects, you can help the garbage collector by setting fields to none that contain large structures (such as images). This allows the image memory to be recycled even if you still have references to the face (often from non-obvious locations or constructs in your program).

2.5 Debugging Faces

To display the fields of a face, you can use the help (?) function:

>> ? a-face
A-FACE is an object of value:
    type            word!     face
    offset          pair!     100x100
    size            pair!     200x100
    span            none!     none
    pane            none!     none
    text            string!   ""
    color           tuple!    255.0.0
    image           none!     none
    effect          block!    length: 1
    data            none!     none
    edge            object!   [color image effect size]
    font            object!   [name style size color offset space...
    para            object!   [origin margin indent tabs wrap? scroll]
    feel            object!   [redraw detect over engage]
    saved-area      none!     none
    rate            none!     none
    show?           logic!    true
    options         none!     none
    parent-face     none!     none
    old-offset      pair!     100x100
    old-size        pair!     200x100
    line-list       none!     none
    changes         none!     none
    face-flags      integer!  1
    action          none!     none

As your faces become more advanced (containing multiple layers of sub-faces) the dump-face function can be useful for debugging. But, for the face above, minimal information is displayed:

>> dump-face a-face
Style: none Offset: 100x100 Size: 200x100 Text:

3. Face Hierarchy

REBOL/View provides a system for the construction of graphical displays and graphical user interfaces in a straightforward manner. Such displays normally consist of much more than just one face. Typical interfaces may require dozens, if not hundreds, of faces that are layered or composited together within one or more windows.

To reduce the complexity of the implementation of a user interface, faces are normally organized into a hierarchy. The hierarchy defines the relationship between faces, such as the way the faces overlay each other or how they are positioned relative to each other. The hierarchy also allows the efficient update of areas of the screen as faces change, and helps speed the propagation of events to their target faces.

In REBOL, the view system hierarchy is implemented in a direct and easy-to-understand manner. It is simple to construct the layers of faces and achieve the desired results. The system design also supports dynamic changes in the hierarchy, allowing it to be changed on the fly according to the requirements of your application.

3.1 Panes

The graphical hierarchy is constructed using the pane field that is part of every face. A pane is simply a block of faces (or a single face) that are graphically related, or event related, to a given parent face. The faces within the pane are subfaces of the parent face.

For example, a display face may be constructed from many other faces. When you view it, the result may look like this:

but, under the hood, this display is built from many separate faces as shown here:

Using the pane field of faces, the graphical hierarchy of objects is created. The top level face provides a pane that lists the objects of the next level, and each of those objects includes panes that list the objects of the next level beyond that.

Only faces that appear within the pane blocks of the hierarchy will be displayed on the screen. If a face is not in one of these panes, it will not appear.

3.2 Relative Coordinates

The offset field of a face always specifies its location relative to its parent face. If a face has an offset of 0x0 it will appear at the same upper left coordinate as its parent face. This relative positioning of faces is used for all of the faces in a pane, and it makes it easy to position or move entire sections of the graphical display by simply changing the offset location of a single parent face.

3.3 Depth Arrangement

The order of faces in a pane determines which faces are on top of others. The first face of the pane block is in the back; the last is in the front.

For example, if a pane is has this order:

pane: [city flower dance]

The faces will overlay like this:

However, if you reverse the pane:

pane: [dance flower city]

You will see this order:

Note that the positions of the faces did not change. They are at the same coordinates, but appear in a different order.

Here is an actual example you can try:

the-pane: reduce [
    make face [
        text: "Face 1"
        color: red
    ]
    make face [
        offset: 50x50
        text: "Face 2"
        color: green
    ]
]

window: make face [
    offset: 100x100
    size: 150x150
    pane: the-pane
]

view window

reverse the-pane

view window

3.4 Screen Face

A special face object is predefined to represent the screen of the computer. This face is system/view/screen-face. You can view its field values with the line:

? system/view/screen-face

The main fields of interest are the size and the pane. The size indicates the width and height of the computer screen in pixels. The pane holds a block of the REBOL windows that are open on the screen.

You don't normally need to be concerned about the screen-face unless you want the screen size for your application. Functions like view isolate the need to access the screen-face directly, and it is recommended that you use them.

3.5 Window Faces

Every REBOL window is also a face. The face appears on screen whenever it is added to the screen-face pane (above).

The depth arrangement of windows is the same as it is for any face. The last face is the topmost window.

The fields of a window face have the following meanings, fairly consistent with faces in general:

 offsetlocation of the window on the screen, not accounting for its border or title bar (which is operating system dependent).
 sizethe size of the window, not counting its border or title bar.
 textthe title text (caption) for the window.
 colorthe backdrop color for the window.
 imagethe backdrop image for the window.
 panethe top level faces to be displayed in the window.

A window will be displayed when it is added to the screen-face pane and the show function is called on the screen-face. This is normally handled by the view function and you do not need to do it yourself manually.

Similarly, the unview function removes windows from the screen by removing them from the screen-face pane and calling show on the screen-face.

3.6 Panels

When creating more complex user interfaces, there are times when you need to group several user interface elements together. The pane of a face can be used to create such panels, even if the face itself contains no graphics of its own (no image or color backdrop, etc.)

The advantage of such panels is that they allow you to move all the elements within them together as a single unit. You do not need to update the positions of each element.

In addition, these panels are clipped in the same manner as all faces, allowing you to easily create scrolling subpanels that hold more user interface elements than can be shown in the window at one time. The user preferences panel of REBOL/View is a good example of such a panel.

To move or scroll a panel, just change the offset value of its face and call show on the face.

3.7 Important Notes

There are just a few areas where problems occur that may be difficult to debug, so we want to mention them here...

  1. Face not in a pane. - You do a show on a face, but it is not part of any pane. The face will not be shown.
  2. Face in more than one pane. - It is not valid to add the same face object to more than one pane. If you need to do that, make a clone of the object, and use that in the other pane.
  3. Negative face sizes. - A face size can never be negative (either in x or in y). Trying to do that is considered an error.

4. Face Functions

The View system was designed to keep the number of native (internal) graphics functions to an absolute minimum. The system only requires two lower-level functions to operate:

 showDisplay or refresh a face or block of faces.
 hideHide a face or block of faces.

Although a few other native functions exist to help with text mapping and color conversion, they are not critical (and are covered in separate parts of this document).

4.1 Show Function for Display and Refresh

The show function is used to display a face either for the first time or after some kind of change has been made to the face.

If the face has not been shown before, it will be displayed. If the face is already displayed, it will be refreshed (redrawn). If the face contains subfaces, they will all be refreshed as well. If the face is a window, the entire window including all of its faces will be refreshed.

The show function takes a single argument: a face or block of faces.

show face

or

show [face1 face2 face3]

Note that the block can contain words (variables) for faces, but it is otherwise not evaluated. (Use reduce if you need to do that.)

In order to be affected, the face or faces must be part of face hierarchy (part of a pane somewhere). If you attempt to show a face and nothing happens, make sure that the face is part of the display hierarchy. That is, the face must be present in the pane list of another face that is being displayed.

Normally when you display a face for the first time, you use the view function to create a new window. The view function includes a call to the show function, so you do not need to call it again unless you make changes to the faces within the window.

The example below shows how the show function is used within an event handler (engage) to refresh the face on the screen.

view make face [
    offset: 100x100
    pane: reduce [
        make face [
            text: "Click to see the color change."
            color: yellow
            edge: none
            feel: make feel [
                engage: func [face action event] [
                    if action = 'down [
                        color: random white
                        show face
                    ]
                ]
            ]
        ]
    ]
]

Note that the show function frequently appears within VID object actions (because VID is built on top of the View graphics system). For instance, a button may change the attributes for another face and show must be called to see the result.

Here is an example of how show is used with VID:

view layout [
    bx: box teal "Example"
    btn "Show" [
        bx/text: now/time
        bx/color: random white
        show bx
    ]
]

Beginner Note:

Beginners often make the mistake of changing some of the fields of a face but forget to call the show function. As a result, the face does not change, and the user is not sure why. All that is needed is to correct the problem is to call show after the change has been made. (The face accessor functions (e.g. set-face) were added in View 1.3 to make this easier.)

Also note that you do not want to call show over and over for each small change, unless you really need the screen to refresh. Note that the above example changes both the text and the color but only calls show once.

4.2 Hide Function

The hide function temporarily removes the face from view. It does not remove the face from its parent face's pane.

Note that the face will become visible again the next time the face is shown either directly or indirectly through one of its parent faces.

The hide function is rarely used in most applications. It is better to remove a face from the display hierarchy then call show to refresh the parent face (hence, removing the face on the screen).

For window faces, the hide function closes the window. Normally, you will want to use the unview function to close the window, because unview also removes the window face from the screen-face pane.

4.3 View and Unview

There are two higher-level functions that are frequently used in the View system:

 viewOpens a window face, displaying its contents.
 unviewShuts a window face.

4.3.1 View Function

The view function creates and updates windows. It takes a face as its argument. The contents of the window is determined from a face that holds a pane of the graphical objects.

The position and size of the window are determined from the face fields of the window. The window's title (caption) will be the text field of the face. To provide a different title, use the /title refinement and a string.

The first use of view within an application is special. It displays the window and initializes the graphical interface system. Subsequent calls to view update the window and do not create new windows unless the /new refinement is provided.

The first call to the view function will not return immediately. At that point your code becomes event driven, calling the feel functions associated with various faces. While the first call to view remains active any other calls to will return immediately. The first call will return only after all windows have been closed. This is equivalent to calling the do-events function. Here is an example to help clarify:

print "opening window..."
view make face [
    offset: 100x100
    color: papaya
    text: "Example"
]
print "continuing..."
halt

In the above example, note that print that follows the view is not printed until after the window is closed.

In some cases you may want to view a window but continue evaluating code after the window is open. You can do that by specifying the new refinement. Here is an example:

print "opening window..."
view/new make face [
    offset: 100x100
    color: papaya
    text: "Example"
]
print "continuing..."

The print will occur after the view opens the window. When you are ready to process events, call do-events:

do-events
print "all windows closed"
halt

The do-events will process events and wait until all windows are closed before resuming.

The new refinement is also used when you want to open additional windows without closing the current window.

Calls to view can specify options, such as whether the window has borders and is resizable. Single options are provided as a word and multiple options are specified in a block.

To better understand the view function, type:

source view

4.3.2 Unview Function

The unview function is used to close a window previously opened with the view function.

By default, the last window that has been opened will be closed. To close a specific window, use the /only refinement and specify the window's face. All windows can be closed with the /all refinement.

Note that the view function will not return until all windows have been closed. (Use view/new to return immediately after the window is opened.)

4.4 Special Face Changes

The face changes field is provided for special types of operating system (or window system) actions that may occur on window faces.

These flags are executed as part of the show function.

 offsetthe offset has changed.
 textthe text has changed. This is used to update Window titles.
 activateactivate (make it the system focus) the window on the screen.
 maximizeexpand the window to full screen size.
 minimizeshrink the window to its minimum or iconic size.
 restorerestore to the prior window size.

Here is an example that shows how to use the changes field to maximize a window.

window: make face [
    offset: 100x100
    size: 100x100
    pane: reduce [
        make face [
            text: "Click"
            size: 80x24
            effect: [gradient 1x1 gold tan]
            edge: make edge [
                color: red
                size: 2x2
                effect: 'bevel
            ]
            feel: make feel [
                engage: func [face act evt][
                    if act = 'down [
                        window/changes: [maximize]
                        show window
                    ]
                ]
            ]
        ]
    ]
]

4.5 Window Face Options

A window face may have special values set in its options facet that control the window's appearance and behavior on screen. These options are set in the options field of the window's face object. The options field can be a single word or a block of words.

Here is list of the available options:

 no-titleThe window will display without a title (caption) bar. (Sometimes used for startup splash screens and special types of dialog boxes -- otherwise it should be used rarely.)
 no-borderThe window will display without borders. Note that the face's edge facet may still be used. If you want no borders at all, set the edge to none.
 resizeAllows the window to be resized by the user. On most operating systems, the borders will look different to indicate that the window can be resized.
 all-overCauses the over event to report a continuous stream of mouse positions as the mouse moves over the face. Do this only if you need to; it can impact performance..
 activate-on-showActivate the window when the show function is called for the face. This is used to keep a window on top. (Note, however, that newer versions of MS Windows will not bring the window to the top unless REBOL is the currently selected application. But, it will blink the REBOL task bar to show that the window has been activated.)
 min-sizeSet the minimum size for resizable windows. This value is passed along to the operating system (it is not handled by REBOL directly). For MS Windows, the size is the full window size, including the title bar and borders (if any). The min-size word must be followed by a pair value that specifies the minimum window size. (So this option requires that you use an options block, not a single word.)

Window System Dependent

The no-title and other options depend to some degree on the windowing system being used. They may not work in an identical fashion on all operating/windowing systems.

Here is an example that will display a window with no title bar and no borders:

view make face [
    offset: 100x100
    edge: none
    color: papaya
    text: "Example"
    options: [no-title no-border]
]

The window options can also be specified as an argument to the view function:

view/options make face [
    offset: 100x100
    edge: none
    color: papaya
    text: "Example"
] [no-title no-border]

Note that if you need to change the options used for a window, you must unview the window and view it again to apply the changes. The next example demonstrates this:

this-face: make face [
    offset: 100x100
    size: 300x200
    text: "Click to change options"
    options: [no-border no-title]
    feel: make feel [
        engage: func [face action event] [
            if action = 'down [
                unview this-face
                options: [resize min-size 300x200]
                view this-face
            ]
        ]
    ]
]
view this-face

4.6 Other Functions

Other low level native View functions are listed here. These functions will be described in more detail within specific sections of this document.

 size-textReturns the size of the text in a face.
 textinfoSets the line text information in an object for a face.
 offset-to-caretMaps from a graphical position to a string position. Returns the string series position within the face/text field that corresponds with the given XY graphical location (a pair!).
 caret-to-offsetMaps from a string position to a graphical position. Returns XY graphical location (relative to the face) for a given string series position.
 rgb-to-hsvConverts RGB value to HSV (hue, saturation, value).
 hsv-to-rgbConverts HSV (hue, saturation, value) to RGB.
 drawDraws scalable vector graphics to an image (returned).

5. Event Handling

User interface events are handled by a set of functions defined in the context of the feel object that is part of every face object. These functions get called in response to mouse input, keyboard input, refresh (redraw), window changes, and other types of GUI events.

5.1 Event Flow

In general, events flow down through the faces of the screen starting from the screen-face object toward the target face where the event actually occurred.

The events normally flow downward in an uninterrupted fashion (as long as the detect function is not used, see below). When an event reaches the target face (where the event occurred), it looks for the proper event handling functions (over or engage). If the face contains no event handlers, the event climbs back up the hierarchy looking for a parent face that does include event handlers.

Although it is not necessary for most user applications to understand or deal with the precise mechanism of event flow, we will outline it here for users who want to know more about it. Here is the event sequence:

  1. The event occurs (e.g. mouse down event)
  2. The system makes an event! object to hold information about the event.
  3. The new event object is queued to the event port (a special kind of REBOL port that holds the stream of events).
  4. REBOL resumes normal processing until a user function calls the wait function or queries the event port.
  5. The wait function calls the event-port's awake function. Normally, this is the system/view/wake-event function.
  6. The awake function reads the event from the event port. The event is removed from the port at the same time.
  7. The awake function deals with the event. In most cases, the function will call do with the event. This causes the event to be processed by the system, eventually being handled by specific face detect, over, and engage functions.

The user application actually has a lot of control over this event flow mechanism. In normal use, default functions are setup in the system/view object to alleviate the need for users to provide event processing control. However, applications can modify and add to the system event functions as necessary.

Most of the processing associated with events happens within the do function of the event. Here is a summary of the process:

  1. The system checks if windows have been moved or resized. This is done first to make the necessary changes to the face object fields prior to calling any feel functions.
  2. The screen-face detect function is called (if one has been provided). This is the highest level of event processing (the global level) essentially able to handle any event for any window or face. (See the insert-event-func section below). If this top level detect function returns none, then the event will be ignored. Otherwise, it returns the event and processing continues.
  3. The window's detect function is called for whatever window the event occurred within. Again, a none return will stop event processing.
  4. The window flags are checked to determine that the window is still open. (The above detect functions could have closed it.) If the window is closed, event processing stops here.
  5. The system processes various parts of the close, resize, move, and time events.
  6. The system checks if a focal-face event has occurred (keyboard or scroll-wheel), if so it checks that focal-face and caret are set in order to call the correct face engage handler. See the text section regarding focus and caret.
  7. The detect functions for faces along the pane path from the window to the target face all get called (if they are present).
  8. And, finally, the engage or over handler is called for the target face. In the case of engage, if that function does not exist for the face, then its parent face engage will be called. If the parent has no engage, then it's parent gets called, and the pattern continues upward to the top-level face.

5.2 Feel object

The feel facet controls a face's behavior in response to system events like redraw, mouse input, keyboard input, and timers. Each face can have its own feel object, but more often a single feel object can be shared between all faces that require the same behavior. For instance, a set of button faces may share a single button-feel object. Such reuse helps reduce memory overhead.

The fields of the feel object are all functions that are called at various times by the View system. Collectively they are sometimes referred to as the feel of the face (as in Look and feel). They are listed below.

The specification of the feel object is defined by the system and, because it is used by lower level implementation code, it should not be altered. The master face object (system/standard/face, or the global face) provides the proper definition; you can use it to define your own feel object.

Sharing a Feel Object

For efficiency reasons, the feel functions are located in an object rather than defined as functions directly in the face object. This allows faces to share feel objects without creating a lot of extra overhead.

However, this sharing requires a bit of care. Many face objects share their feel between face instances. For example, VID button faces share a single feel object that specifies the event handling required for buttons.

You need to keep this sharing in mind if you decide to modify the functions of a feel object. For example, if you modify the feel object of a button, you may accidentally be modifying the feel object of other buttons.

To avoid the effect you need to make a copy of the feel object and only modify it for the face that you need. Examples are shown below.

On the other hand, if you want to change the behavior for all the faces of a certain style, you can do so in one place if they share a common feel object. Smalltalk programmers are used to being able to change the behavior of the system on a global level this way.

5.3 Feel Functions

The entire REBOL user interface system is handled by these four feel object functions:

redraw [face action position]

over [face action position]

engage [face action event]

detect [face event]

Each of these functions is described in detail in the sections that follow.

5.4 Redraw Function

The redraw function is called immediately before the face is drawn. This function allows code to dynamically modify the facets of the face prior to being displayed. However, such code should be minimized because this function will often get called many times a second as a user moves the mouse over the face.

If you do not need this function, it is strongly recommended that you set this field to none to speed up the display process.

The redraw function has the form:

redraw face action position

The arguments of the function are:

 faceThe face object being redrawn.
 actionA word that indicates the type of action that occurred on the face: show, draw, and hide. The show action occurs when the show function is called on the face (or any of its parents). The draw occurs immediately before the face is rendered. The hide action is called when the face is hidden.
 positionThe offset of the face if the face is iterated. Most of the time, you can ignore this argument. Iterated faces are covered below.

5.4.1 Example of Using Redraw

Here is an example that shows the behavior of the redraw function:

view make face [
    offset: 100x100
    pane: reduce [
        make face [
            text: "Left Click mouse here.^/^/Right click to hide."
            color: yellow
            edge: none
            feel: make feel [
                redraw: func [face action pos] [
                    print ["Redraw:" action pos]
                ]
                engage: func [face action event] [
                    switch action [
                        down [show face]
                        alt-down [hide face]
                    ]
                ]
            ]
        ]
    ]
]

5.5 Over Function

The over function is called whenever the mouse pointer passes over or off of a face. This function can be used to capture mouse hover events and provide user feedback by changing the appearance of the face. For example, hot text may change the color of the text as the mouse passes over it.

The over function may get called at a very high rate, because a user interface often consist of hundreds of faces and the user may move the mouse over those faces. (That's the reason why this is a single function and not combined with the engage function.) This function should be set to none if it is not needed, allowing the system to ignore the face as the mouse passes over it.

Use Engage for Drag and Drop

Over is only called if no mouse button is currently pressed. It is not meant to be used for drag operations; you should use the engage function with the over and away actions for that.

For example, if the user clicks and drags off the face, the away action is sent to the engage function, not the over function; If the user has dragged off the face, then drags back over the face, the over action is sent to engage, not over.

See the Drag and Drop example section near the end of this document.

The over function has the form:

over face into position

The arguments to the over function are:

 faceThe face object under the mouse pointer.
 intoA logic value: true indicates that the mouse is entering (over) the face, and false indicates that the mouse is exiting (away) the face.
 positionThe current X-Y position of the mouse.

5.5.1 Example Using Over

Here is an example that shows the behavior of the over function:

view make face [
    offset: 100x100
    pane: reduce [
        make face [
            text: "Move the mouse over this"
            color: yellow
            edge: none
            feel: make feel [
                over: func [face into pos] [
                    text: reform [
                        pick ["over" "away"] into
                        pos
                    ]
                    color: pick reduce [green red] into
                    show face
                ]
            ]
        ]
    ]
]

5.5.2 Example That Shows Over Behavior

The example below shows more clearly what happens with the over function when used with more than one face:

print "Move mouse over faces in the window..."

view make face [
    offset: 100x100
    size: 130x130
    color: navy
    edge: none
    pane: reduce [
        make face [
            offset: 10x10
            size: 75x75
            text: "Face1"
            color: red
            feel: make feel [
                over: func [face into pos] [print [text into]]
            ]
        ]
        make face [
            offset: 45x45
            size: 75x75
            text: "Face2"
            color: yellow
            feel: make feel [
                over: func [face into pos] [print [text into]]
            ]
        ]
    ]
]

Notice that the each face gets a false when it leaves each face even though it enters another face.

5.5.3 Continuous Over Events

If you run the code examples above, you will notice that it only reports the mouse event once as it moves around within the face. This is an optimization to prevent a flood of events.

If you need to receive all the mouse movement events, you can enable that as a special option for the window. This allows you to track the mouse constantly while it is over a face.

To enable mouse movement events, add the all-over option to the top level face (the window face) as shown here:

view make face [
    offset: 100x100
    options: [all-over] ;<--- added
    pane: reduce [
        make face [
            text: "Move the mouse over this"
            color: yellow
            edge: none
            feel: make feel [
                over: func [face action pos] [
                    text: reform [
                        pick ["over" "away"] action
                        pos
                    ]
                    color: pick reduce [green red] action
                    show face
                ]
            ]
        ]
    ]
]

The code now reports a continuous stream of mouse positions as the mouse moves over the face. Normally this is not necessary and it can slow down the user interface. Do it only when you need to.

5.6 Engage Function

The engage function is called whenever any event other than a redraw or an over occurs for a face. It is called for the majority of events that occur within a face. For example, it is called when the mouse pointer is over the face and either mouse button is pressed, or if a mouse button has been pressed and the mouse is moved over the face, or if the face is the focus of keyboard events and such an event happens, and finally when time events occur, such as for animation or repetitive selection events.

The engage function has the form:

engage face action event

Where its arguments are:

 faceThe face that got the event.
 actionA word that indicates the action that has occurred.
 eventThe event that provides detailed information about the action.

5.6.1 Example Using Engage

Here is an example that shows the behavior of the engage function:

view make face [
    offset: 100x100
    pane: reduce [
        make face [
            text: "Click, right click, drag mouse, and press keys."
            color: yellow
            edge: none
            feel: make feel [
                engage: func [face action event] [
                    text: reform [action event/key]
                    system/view/focal-face: face
                    system/view/caret: tail text
                    show face
                ]
            ]
        ]
    ]
]

The focal-face and caret must be set to allow the face to receive keyboard events. If they are not set, keyboard events do not know the target face.

Also, be sure to try holding down the mouse button and moving with it down. You will see that engage gets sent over and away events similar to the over function.

To see an example of how to implement a simple drag-and-drop operation using the engage function, see the examples section near the end of this document.

5.6.2 Summary of Engage Events

The main events received by engage are:

 downthe main mouse button was pressed.
 upthe main mouse button was released.
 alt-downthe alternate mouse button was pressed (right button on right handed mice).
 alt-upthe alternate mouse button was released.
 overthe mouse was moved over the face while either button was pressed.
 awaythe mouse has moved off the face while the button was pressed.
 keya key has been pressed.

5.7 Detect Function

The detect function is called whenever any event occurs for a face or for any of the faces that are contained within it. This allows a face to intercept events that are aimed at lower level face objects. For example, it is used by VID to process keyboard shortcuts.

Important Performance Warning:

The detect function gets called for all events, and there can be multiple detect functions that get called for a single event (as the event moves from the screen to the window and to each of the faces of the face hierarchy).

It is best to avoid using detect if possible. It is more of a last resort when you must filter an event and have no other way to catch the event. Detect should never be used if engage can deal with the event properly. Engage is much more efficient.

If you find that your user interface is running slowly, and you are using the detect function, review your code and determine if you can eliminate the use of detect or try to minimize its computation.

The detect function works as an event filter. When an event occurs, detect can decide how to handle the event. When it is ready to exit, the return value allows the event to continue to lower level faces or stop it immediately.

The detect function has the form:

detect face event

Where its arguments are:

 faceThe face that has the event.
 eventThe event that provides detailed information.

The detect function must return either:

 eventThe same event that it was passed as an argument.
 noneWhen the event is not to be processed by subfaces.

5.7.1 Example Using Detect

Here is a simple example of detect:

view make face [
    offset: 100x100
    pane: reduce [
        make face [
            text: "Test Detect"
            color: yellow
            edge: none
            feel: make feel [
                detect: func [face event] [
                    text: reform [event/type event/offset]
                    show face
                    event ; <-- do not forget this
                ]
            ]
        ]
    ]
]

Of course, this example is not that meaningful because the face has no subfaces. Here is a better example:

view make face [
    offset: 100x100
    size: 100x200
    pane: reduce [
        make face [
            text: "Face 1"
            color: yellow
        ]
        make face [
            offset: 0x100
            text: "Face 2"
            color: green
        ]
    ]
    feel: make feel [
        detect: func [face event] [
            print [event/type event/offset]
            event
        ]
    ]
]

Be sure to read the performance warning above.

5.7.2 Trapping Global Events

The detect function is often used at the window face or screen face level, but you should really use the insert-event-func function to trap such global events.

Bug Note: On current versions of REBOL, the event/face within the detect function is not set to the proper value. It should indicate the target face of the event, not the face in which the detect event is used.

5.8 Timers

Each face can have its own timer associated with it. When the timer expires, a time event will occur (which is normally handled by the face's engage function).

The rate field of the face object is used to set the desired time interval. The rate can specify either the number of events per second or the period between events.

view make face [
    offset: 100x100
    pane: reduce [
        make face [
            text: "Timer Example"
            color: yellow
            edge: none
            rate: 2
            feel: make feel [
                engage: func [face action event] [
                    if action = 'time [
                        text: now/time
                        color: get pick [green red] color = red
                        show face
                    ]
                ]
            ]
        ]
    ]
]

If you modify the above rate to:

rate: 0:00:10

Then, you would get the time event every ten seconds. If you set the rate to none, it will disable the timer (but see note below).

A Show is Required

If you change the time rate for a face, you must call the show function with that face to enable the new timer. This is also true if you disable the timer by setting rate to none.

5.9 Event Datatype

In REBOL, events are a special datatype, event!.

There are four general categories of events: mouse, keyboard, window and time events. Mouse events occur during motion and button selection. Keyboard events occur when a key is pressed. Window events occur when a window becomes active, inactive, resized, or closed. And, time events occur during clock ticks of the computer's internal clock.

Some of the feel functions take an event value as an argument. Each event contains information about itself that is accessible by using the refinements supported by the event! datatype. The following list describes these refinements:

 /faceReturns the top-level face object of the window that was active when the event occurred.
 /typeReturns event type such as down or up for the mouse button. The event types are listed in the next section.
 /offsetReturns the mouse cursor offset relative to the REBOL/View window. The offset returned has different meaning depending on the event type. See table below.
 /keyReturns the character for the event, if there is one. With key events, this returns the character pressed that triggered the event. The only exceptions are the arrow keys up, down, left, right, and the end and home keys, which return word! values.
 /timeReturns an integer representing a unique time index for the event.
 /controlReturns a logic value, true or false, depending on whether the control key is pressed when the event is sent. This works with the mouse up and down events as well as move, key, resize and time.
 /shiftReturns a logic value, true or false, depending on whether the shift key is pressed when the event is sent. This works with the mouse up and down events as well as move, key, resize and time.
 /double-clickReturns a logic true if a down event happened twice in succession, indicating that a double-click down event occurred. This is useful for some types of user interfaces.

These refinements allow events to be examined and actions taken based on the event.

For example, to see if the event is a left mouse click, check the event type using the /type:

if event/type = 'down [ ... ]

This next example checks whether the shift button is held down when the mouse-down event occurred:

if all [event/type = 'down  event/shift] [ ... ]

The following illustrates how different types of events can be handled:

switch event/type [
    down  [ ... ]
    up    [ ... ]
    key   [ ... ]
    close [ ... ]
]

Typically, an event will be checked in its face's feel/engage, or feel/detect function. These are the two functions of the feel object that take an event value as one of its arguments.

Note that you cannot currently set the fields of an event datatype. This may be added in a future version of REBOL.

5.10 Event Types

Event Type

Description

down, alt-down

mouse down event for the left or right mouse button (pressing down on the mouse button)

up, alt-up

mouse up event for the left or right mouse button (releasing the mouse button)

move

Mouse move event (position of the mouse cursor over a REBOL/View window)

offset

Window move event (offset of window)

key

Keyboard event (keyboard input)

time

Timer event (unique time index - latest event has the highest number)

resize

Resize window event (whenever the window is resized)

close

Close window event (exiting the REBOL/View window session)

active

Window becoming active; occurs when the window becomes active again after being inactive, while another application was active

inactive

Window becoming inactive; occurs when the window becomes inactive, because another application became active

minimize

The window has been minimized by the user.

maximize

The window has been maximized by the user (opened to full screen size).

scroll-line

Mouse wheel scrolled

scroll-page

Mouse wheel scrolled with the Control key held down

Note: In MS Windows there is an optional desktop setting to "Show window contents while dragging". If that option is used, when a window is moved, you will receive a number of offset events after the mouse is released; if not, you will receive only one offset event.

5.11 Event Offset Meaning

The meaning of the event offset will vary with different kinds of events. Here are the possibilities:

Event Type

Description

down, alt-down

The offset when the mouse button is pressed.

up, alt-up

The offset when the mouse button is released.

move

The offset at the moment the mouse is moved. The frequency at which mouse move events are sent to the event port will vary per system. When the mouse cursor is outside the REBOL/View window, no move events are sent to the event port.

key

The mouse cursor offset at the moment of keyboard input. When the mouse cursor is outside the REBOL/View window and a keyboard event occurs, the offset value will be positive or negative of the current REBOL/View window size.

time

The offset of the mouse at the moment the event is sent.

offset

The offset of window. Offset events are sent after the window is moved.

resize

The offset of the mouse as the window is being resized. Resize events are sent as the window is being resized.

scroll-line

0xN, where N is the number of lines to scroll vertically. Negative values for N mean the wheel was scrolled up instead of down.

scroll-page

0xN, where N is the number of pages to scroll vertically. Negative values for N mean the wheel was scrolled up instead of down.

Note: The scroll-line and scroll-page events don't currently reach non-window detect functions. You can only receive them from a window face. One good way is to use the insert-event-func function.

5.12 The Event Port

As events occur, they are queued to a special type of port called the event port. As events are processed, they are read from the event port and acted upon.

Of course, most applications require no knowledge of the event port and its specific action. However, if your application needs to handle events directly at the event queuing level (very low level) it can do so via the event port.

The event port handles these actions:

 openOpen the event port. The scheme is event, and normally a target of "events" is provided (for error output identification). The event port should only be opened once.
 closeIgnored. Does nothing.
 pickPick next event from the port. Index should always be set to one (because it always returns the first event, regardless).
 get-modesIf the activity mode is queried, returns true if user activity has occurred since the last check. This feature is used by IOS to determine if the user is actively using IOS (for updating the user activity status indicator).

At this time you cannot insert your own events into the event port.

To see the code for the default functions event port functions, type these lines into the console:

source open-events

source do-events

probe get in system/view 'wake-event

5.13 Global Event Handler

To make it easier for applications to cooperate on the handling of global events, the insert-event-func function has been provided to set global event handlers.

The argument you provide can be either a function! or a block!. If you provide a block, it is used as the body of a function that takes face and event args. insert-event-func is a mezzanine, so you can see exactly how it works.

insert-event-func: func [
    {Add a function to monitor global events. Return the func.}
    funct [block! function!] "A function or a function body block"
][
    if block? :funct [funct: func [face event] funct]
    insert system/view/screen-face/feel/event-funcs :funct
    :funct
]

You can install as many handler functions as you want this way, and they will remain active while the REBOL session is running. If you need to remove one--for example, to insert an updated version of it while the program is running--you can use remove-event-func.

remove-event-func: func [
    "Remove an event function previously added."
    funct [function!]
][
    remove any [find system/view/screen-face/feel/event-funcs :funct ""]
]

In order to use remove-event-func, you need to keep a reference to your event function so you can provide it to remove-event-func.

A common idiom is to use the switch function to handle different event types in a handler function. You can handle as many event types as you want this way and easily ignore events you don't need.

insert-event-func [
    switch event/type [
        key         [handle-hot-keys event/key]
        time        [show-current-time]
        close       [cleanup]
        offset      [save-window-pos]
        resize      [save-window-pos]
        scroll-line [scroll-lines event/offset/y]
        scroll-page [scroll-pages event/offset/y]
    ]
    event
]

You can compare against event/face to handle events for different windows that may require different logic. (Seen "Known Problems" section below.)

Your Handler Must Return the Event

If you want the system to continue to process an event, then your event handling function must return the event as its result. If your code has handled the event, and no other processing is required, your function should return none.

In the code above, the last value in your function should be the event value, unless you return from the function somewhere in the body; in that case you should still return the event passed to the function as an argument.

6. Face Text

The face text field allows users to add text to a face, from just a few characters to entire paragraphs and pages.

The face object includes a few fields that are related to how text is handled:

 textthe field that holds the text to be displayed in the face.
 fontthe font attributes for rendering the text.
 parathe paragraph spacing and alignment attributes.
 line-lista special cache of line positions used to speed up text operations. This field should not be used by applications, except to delete the cache when text changes are made.

6.1 Text Field

This field specifies the text to be displayed in the face.

The field accepts any printable REBOL value. The value will be converted to a string (formed) dynamically by the system. If you provide a string, no conversion is needed. This is important if the face must be shown (refreshed) at a high frequency (such as in the case of cells within large scrolling tables). In such cases, strings provide greater performance.

Note that if the text is greater than 200 characters in length and you make changes to the text, you must also delete the line-length cache to avoid possible garbage characters from being displayed in the face. See more below.

6.2 Font Facet Object

The font facet is an object that describes the attributes of the text to be used within a face. The font is specified as an sub-object within the face object.

The font object contains these fields:

Field

Type

Description

name

string!

The name of the font to use for the text. There are three predefined variables for machine independent fonts that have a similar appearance: font-serif (times-like), font-sans-serif (helvetica-like), and font-fixed (courier fixed width). To create machine independent programs, avoid specifying custom fonts. The default is font-sans-serif.

size

integer!

The point size of the font. The default size is 12.

style

word! | block!

The style of the text. Choices are: bold, italic, and underline. When set to none, no styles are used (default).

color

tuple!

The color of the text. The default color is black (0.0.0).

align

word!

The alignment of the text within the face. Choices are: left, right, and center.

valign

word!

The vertical alignment of the text within the face. Choices are: top, bottom, and middle.

offset

pair!

The offset of the text from the upper left corner of the face. The para facet object also has an effect on this offset. Default is 2x2.

space

pair!

The spacing between characters and between lines. The x value affects the spacing between characters. The y value changes the spacing between lines. Positive values expand the text, negative values condense it. The default is 0x0.

shadow

pair!

The direction and offset of the drop shadow to use for the text. Positive values project a shadow toward the lower right corner. Negative values project toward the upper left. The default is none.

Here is an example that sets most of the above fields for the font object.

view make face [
    offset: 100x100
    size: 300x100
    edge: none
    pane: reduce [
        make face [
            offset: 0x0
            size: 300x100
            color: water
            edge: none
            text: "Modified Font Object Settings"
            font: make font [
                name: "times"
                size: 20
                style: 'bold
                color: white
                align: 'center
                valign: 'middle
                space: 4x4
                shadow: 2x2
            ]
        ]
    ]
]

6.3 Para Facet Object

The para facet is an object that controls the formatting of text paragraphs within the face. A para is specified as a sub-object within a face object.

The para object contains these fields:

Field

Type

Description

origin

pair!

The offset of the text from the upper left corner of a face. The default is 2x2.

margin

pair!

The right-most and bottom limits of text display within the face. The position is relative to the bottom right corner of the face. The default is 2x2.

indent

pair!

The offset of a the first line of a paragraph. The X value specifies the indentation used for the first line of the paragraph. Positive and negative values may be used. The Y value specifies the spacing between the end of the previous paragraph and the first line of the next paragraph. The Y value has no effect on the first paragraph. The default is 0x0.

scroll

pair!

Used for horizontal and vertical scrolling of text within a face. The scroll amount that modifies the offset of the text relative to the face. The origin and margin values are not affected. The default is 0x0.

tabs

integer! | block!

An integer! or block of integers that provide the tab spacing used within a paragraph. An integer! value indicates a fixed tab size spaced at regular intervals across the text. A block of integers provides the precise horizontal offset positions of each tab in order. The default is 40.

wrap?

logic!

Indicates that automatic line wrapping should occur. When set to true, text that exceeds the margin will be automatically wrapped to the origin. When set to false, text will not be wrapped.

Here is an example that shows how text is formatted when you change a few of the para fields:

; First, remove extra lines from text for proper wrapping:
para-text: copy system/license
replace/all para-text "^/^/" "!!"
trim/lines para-text
replace/all para-text "!!" newline

view make face [
    offset: 100x100
    size: 420x320
    edge: none
    color: white
    pane: reduce [
        make face [
            offset: 10x10
            size: 400x300
            color: white
            text: para-text
            font: make font [align: 'left]
            para: make para [
                origin: 30x30
                margin: 30x30
                indent: 30x10
                wrap?: on
            ]
        ]
    ]
]

One of the more interesting fields here is indent. It sets the indentation of the first line of each paragraph and the distance between paragraphs. If you provide a negative x value, it will also do "outdenting" (as sometimes used for bullet sections, etc.)

6.4 Focus and Keyboard Events

Keyboard events are not sent to all faces. Keyboard events are only sent to a single face, called the focal-face. This face provides an event target that is the implied "focus" of user input actions.

The focal-face is set in the system/view object. This face can be set directly, or you can use the focus function to set it and handle a few other details related to text input.

The focal-face must be set to a valid face object (one that is part of a pane in the face hierarchy) and the system/view/caret (explained in next section) must also be set in order to:

  1. Receive keyboard events
  2. Receive mouse scroll-wheel events (scroll-line and scroll-page)
  3. Handle text editing caret and text selection highlighting

If the system/view/focal-face has not been set (is set to none), the above events will be ignored by the system, even by the top-level detect function.

When the focus is no longer needed, you can set the focal-face field to none, or call the unfocus function.

The VID system provides useful functions for handling keyboard input into text fields. It is suggested that you use them if possible. However, here is a example that shows how text input can be handled for a very simple case:

aface: make face [
    offset: 100x100
    pane: reduce [
        tface: make face [
            text: "Type on keyboard."
            font: make font [align: 'left]
            color: snow
            edge: none
            feel: make feel [
                engage: func [face action event] [
                    if action = 'key [
                        switch/default event/key reduce [
                            bs [remove back tail face/text]
                            cr [unview]
                            escape [unview]
                        ][
                            append face/text event/key
                        ]
                        system/view/caret: tail face/text
                        show face
                    ]
                ]
            ]
        ]
    ]
]
system/view/focal-face: tface
system/view/caret: tail tface/text
view aface

This is a low level example, and only handles the simplest of keyboard input actions. Keyboard text input processing is a somewhat complicated matter, and it is recommended to use the functions provided by VID.

6.5 Text Caret Position

When entering text into fields and areas, it is customary to mark the edit point with a vertical bar, called the caret. This is where new text will be inserted or existing text is deleted.

The system/view/caret field determines the current text edit point within the text. You set this caret field to a position within your face/text string. For example, if your face is named tface, then:

To set the caret edit point to the start of the text:

system/view/caret: head tface/text

To set the caret edit point to the end of the text:

system/view/caret: tail tface/text

To set the caret edit point to the 10th character:

system/view/caret: at tface/text 10

Keep in mind that the caret field actually points into your string series, so in fact you can directly use it with insert and remove functions to change the text string.

system/view/caret: insert system/view/caret "test"

The above inserts the string "test" at the current edit point and updates the edit point (to just after the insert position). See the REBOL/Core Users Guide for more about Series functions.

6.6 Text Position Mapping

In graphical user interfaces, the mouse pointer is often used to set the caret to a location within the text string. To do so, a graphical xy position needs to be mapped to a series position within the string.

Sometimes the opposite is also required. You know a position within the string series and need to map it to an xy position within the face.

If you are using VID, these operations are performed for you automatically when fields, areas, or other selectable text is used.

In the lower level View system, REBOL provides two functions to perform these graphics-to-string mappings:

 offset-to-caretMaps from a graphical position to a string position. Returns the string series position within the face/text field that corresponds with the given XY graphical location (a pair!).
 caret-to-offsetMaps from a string position to a graphical position. Returns XY graphical location (relative to the face) for a given string series position.

The functions both take a face and an offset as arguments:

offset-to-caret face pair-offset

caret-to-offset face string-offset

Here is an example that shows how offset-to-caret works:

aface: make face [
    offset: 100x100
    pane: reduce [
        tface: make face [
            text: "Click the mouse somewhere within this text string."
            font: make font [align: 'left]
            color: snow
            edge: none
            feel: make feel [
                engage: func [face action event] [
                    if action = 'down [
                        print offset-to-caret face event/offset
                    ]
                ]
            ]
        ]
    ]
]
view aface

6.7 Text Highlighting

In addition to selecting the edit position (the caret), most text editing also allows the selection of a sequence of text characters. This is often called highlighting or selection of a string and is often used with text cut, copy, and paste operations.

REBOL highlights text by using inverse video for the selected text area. The range of the selection is controlled by two fields in the system/view object:

 highlight-startThe string position to begin highlighting text.
 highlight-endThe string position to stop highlighting text.

When using VID, these fields are set to their correct values automatically when using the mouse to select text.

However, you can set these values directly, as shown below. Both of these fields must refer to the same string series and the focal-face must refer to the face that contains the text.

aface: make face [
    offset: 100x100
    pane: reduce [
        tface: make face [
            text: "Example of text highlight selection."
            font: make font [align: 'left]
            color: snow
            edge: none
        ]
    ]
]
system/view/focal-face: tface
system/view/highlight-start: find tface/text "text"
system/view/highlight-end: find/tail tface/text "light"
view aface

There is currently no way to modify the highlight color. (Although we plan to include that as an enhancement in future versions of REBOL.)

6.8 Text Scrolling

Text can be scrolled within a face by setting the scroll field of the face's para object. The scroll field allows both horizontal and vertical scrolling depending on the pair! value you provide. Positive scroll values move text down (or to the right) and negative values move text up (or to the left). That is, usual text scrolling (where text goes up or to the left) is done by using negative numbers for x and y in the scroll field.

Sharing the Para Object

If you are going to change values in the para object, you should make a copy of it first. That allows you to make changes without affecting other faces that share the same para object. See the example below.

Here is a simple example. Each time you click the text, it will scroll up by 10 pixels. If you right-click (alt-click) the text it will scroll down by 10 pixels.

the-text: read http://www.rebol.com/speed.r

view make face [
    offset: 100x100
    size: 640x480
    pane: reduce [
        tface: make face [
            text: the-text
            size: 640x480
            font: make font [align: 'left]
            color: snow
            para: make para [scroll: 0x0 wrap?: true]
            feel: make feel [
                engage: func [face action event] [
                    if find [down alt-down] action [
                        face/para/scroll: face/para/scroll +
                            pick [0x-10 0x10] action = 'down
                        show face
                    ]
                ]
            ]
        ]
    ]
]

6.9 Text Sizing

At times you will need to know a little more about the rendered size of the text a face is creating. For instance, if you want to write functions to align or center text, you will need to know its size in pixels.

6.9.1 Size-Text

The size-text function returns the number of pixels in width and height required to render text for a face. For a single line of text, this function is mainly used to determine the width of the text. For multiple lines, it is normally used to compute the height of the text.

If the face wrap? field is true, the text will be wrapped at the width, and the height will be determined by the number of lines, including any wrapped text.

In addition to the width of the face, this function will also take into account the horizontal component (x) of the para/origin, para/margin, para/indent, and edge/size. The height of the face, para/offset, and para/scroll do not affect the result.

Note that the face does not need to be displayed for this function to work correctly.

Here is an example:

aface: make face [text: "This is some text"]
print size-text aface
74x30

Here is an example that uses a lot more text:

page: read http://www.rebol.com
print length? page
9902
aface: make face [text: page size: 600x500]
print size-text aface
593x3885

Note in the example above, the height (size) of the face did not matter in order to calculate the height of the text within it.

See the important notes below.

6.9.2 TextInfo

A lower-level text mapping function is textinfo. This is more of an internal function that was created for text editing purposes, but can be quite useful at times. (And notice that it is not named according to standard REBOL function naming conventions - making it more difficult to remember.)

The textinfo function provides text line information for the text that is rendered in a face. The form of the function is:

result: textinfo face line-info line

The result is either none if the text cannot be rendered, or it is the line-info object (when successful).

The function arguments are:

 faceThe face that specifies the text and its facets.
 line-infoA special text line object that is passed to the function and gets modified to hold the result. See details below.
 lineThe string index position or line number (zero based) to get information about.

The line-info object is defined by system/view/line-info. It is set by calling the textinfo function. It includes these fields:

 startOffset of start of line in the string (string!)
 num-charsNumber of chars in the line (integer!)
 offsetXY position of the line (pair!)
 sizeWidth and height of the line (pair!)

Here is an example that fetches information about the third line of text in a face:

aface: make face [
    text: trim/lines {This is a longer line of text that is
        used to illustrate the example.}
]

line-list: make system/view/line-info []
if textinfo aface line-list 2 [
    probe line-list 
]
make object! [
    start: "used to illustrate the example."
    num-chars: 19
    offset: 2x32
    size: 93x15
]

Important Notes:

  1. If you change the text for a face, be sure to set the face's line-list field to none to force the font system to re-render all the text.
  2. You must use a copy of the system/view/line-info object with the textinfo function. Doing otherwise could cause a fatal error in current versions of REBOL.
  3. The line number is zero based, not one-based (unlike other index positions in REBOL).

6.10 Text Clipboard

For text cut and paste operations between other applications, REBOL provides the clipboard port. This port allows you to insert text and copy text as you would with other REBOL ports. The text will be stored in the OS clipboard an can be pasted into other applications. If other applications store 8-bit text, they can be copied into REBOL.

The standard read/write port operations are:

 readRead a text clip from the clipboard.
 writeWrite a text clip to the clipboard.

For example, to write text to the clipboard, you simply write to the clipboard using its URL scheme:

write clipboard:// "This is text"

To read from the clipboard:

print read clipboard://

Other port functions are also allowed:

 openOpen the clipboard for text read and write.
 closeClose the previously opened clipboard.
 insertInsert text into the clipboard.
 copyCopy text from the clipboard and return it as a string.

Here is an interesting example that shows how to use the clipboard. It opens a window and displays the current contents of the clipboard. Go to another application, such as your web browser, then select and copy some text. You will see the clip text appear in the REBOL window. (It updates four times second.)

clipboard: open clipboard://

view make face [
    offset: 100x100
    size: 640x480
    pane: reduce [
        tface: make face [
            text: "Shows clipboard"
            size: 640x480
            font: make font [align: 'left]
            color: snow
            rate: 4
            feel: make feel [
                engage: func [face action event] [
                    if action = 'time [
                        face/text: copy clipboard
                        show face
                    ]
                ]
            ]
        ]
    ]
]

close clipboard

Note that REBOL does not currently support images or other datatypes in the clipboard. (However, we plan to improve the clipboard capabilities in the future.)

7. Face Images

Any face can include an image. When an image is part of a face, it will appear behind the edge image, if an edge is specified.

The image is not scaled by default, but if it is too large, it will be clipped and if it is too small, the remaining area will be filled with the color specified in the face color field. To make the image scale to the size of the face, use the fit effect. To keep the image in proper aspect (the same ratio as its source, not to the ratio of the face), use the aspect effect.

Note that it is a common mistake for beginners to specify a file name for this field, rather than an image. You must write:

image: load %photo.jpg

or:

image: load-image %photo.jpg  ; caches image in memory

not:

image: %photo.jpg

A variety of special effects can be applied to the image. See the effects section below.

Images can also be used for the edge of a face. See the edge section below.

8. Face Edges

The edge field of a face can optionally contain an object that describes a rectangular frame that borders a face. It is used for creating image frames, button edges, table cell dividers, and other border effects.

8.1 Definition

An edge object contains these fields:

Field

Datatype

Description

color

tuple!

The color of the edge. For effects like BEVEL, the final color will be based on this original color plus or minus a small amount to provide proper shading.

size

pair!

The thickness of the edge. The x value refers to the thickness of the vertical edges on the left and right, and the Y value refers to the horizontal edges at the top and bottom.

effect

word! | block!

A word! or block! that describes the effect to use for the edge. In addition to the normal face effects described below, edges also include bevel, ibevel, bezel,ibezel, and nubs effects. These are useful for quickly creating button and box looks.

image

image!

Use an image for the edge area. The image can be processed using the above effect if necessary (e.g. colorize, etc.)

9. Face Effects

The effect field can be set to a word! or a block! that describes image processing operations to perform on the backdrop of a face. When a block! is used, multiple effects can be specified; they are applied in the order in which they appear within the block. A wide range of effects can be produced with effects (there are over 30 individual effects that can be combined in any number and sequence--you do the math).

PENDING! - the effect pipeline

9.1 Merging Background

It was necessary to add the merge effect on newer versions of REBOL in order to explicitly determine when an effect involves its background.

Effect

Arguments

Description

merge

none

Merges the bitmaps of faces that fall behind the current face into the current effect. This is how an effect can be applied to areas of the background.

The code below shows the merge effect in action. The first face object (the "kid" image) is shown both with and without merge. Below it are two other effect faces that are merged. Try deleting the merge words to see what happens.

palms: load-thru/binary http://www.rebol.com/view/palms.jpg
kid: load-thru/binary http://www.rebol.com/docs/graphics/kid.png

view f: make face [
    offset: 100x100
    size: palms/size
    image: palms
    text: ""
    effect: [gradcol 0x1 0.0.255 255.0.0]
    pane: reduce [

        make face [
            offset: 90x20
            size: kid/size
            edge: none 
            image: kid
        ]

        make face [
            offset: 160x20
            size: kid/size
            edge: none 
            image: kid
            effect: [merge]
        ]

        make face [
            offset: 0x110
            size: palms/size * 1x0 + 0x10
            edge: none 
            effect: [merge gradmul black white]
        ]

        make face [
            offset: 0x130
            size: palms/size * 1x0 + 0x40
            edge: none 
            effect: [merge invert]
        ]
    ]
]

The resulting image is:

Older Scripts

If you run some older REBOL scripts and the effect is not appearing correctly, usually it is just a matter of adding the word merge to the head of the effect block.

9.2 Scaling

These effects control how the bitmap within the face is scaled relative to the face's size field.

Effect

Arguments

Description

fit

none

Scales an image to the size of the face, less the edge of the face if it exists. The image will be scaled both horizontally and vertically to fit within the face.

aspect

none

Similar to fit, but preserve