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:
| | offset | location of the window on the screen, not accounting for
its border or title bar (which is operating system dependent).
|
| | size | the size of the window, not counting its border or title bar.
|
| | text | the title text (caption) for the window.
|
| | color | the backdrop color for the window.
|
| | image | the backdrop image for the window.
|
| | pane | the 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...
- 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.
- 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.
- 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:
| | show | Display or refresh a face or block of faces.
|
| | hide | Hide 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:
| | view | Opens a window face, displaying its contents.
|
| | unview | Shuts 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.
| | offset | the offset has changed.
|
| | text | the text has changed. This is used to update Window titles.
|
| | activate | activate (make it the system focus) the window on the
screen.
|
| | maximize | expand the window to full screen size.
|
| | minimize | shrink the window to its minimum or iconic size.
|
| | restore | restore 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-title | The 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-border | The 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.
|
| | resize | Allows 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-over | Causes 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-show | Activate 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-size | Set 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-text | Returns the size of the text in a face.
|
| | textinfo | Sets the line text information in an object for a face.
|
| | offset-to-caret | Maps 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-offset | Maps from a string position to a graphical
position. Returns XY graphical location (relative to the face) for a
given string series position.
|
| | rgb-to-hsv | Converts RGB value to HSV (hue, saturation, value).
|
| | hsv-to-rgb | Converts HSV (hue, saturation, value) to RGB.
|
| | draw | Draws 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:
- The event occurs (e.g. mouse down event)
- The system makes an event! object to hold information about the
event.
- The new event object is queued to the event port (a special kind of
REBOL port that holds the stream of events).
- REBOL resumes normal processing until a user function calls the
wait function or queries the event port.
- The wait function calls the event-port's awake function.
Normally, this is the system/view/wake-event function.
- The awake function reads the event from the event port.
The event is removed from the port at the same time.
- 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:
- 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.
- 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.
- The window's detect function is called for whatever window
the event occurred within. Again, a none return will stop event
processing.
- 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.
- The system processes various parts of the close, resize,
move, and time events.
- 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.
- The detect functions for faces along the pane path from the
window to the target face all get called (if they are present).
- 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:
| | face | The face object being redrawn.
|
| | action | A 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.
|
| | position | The 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:
| | face | The face object under the mouse pointer.
|
| | into | A logic value: true indicates that the mouse is entering
(over) the face, and false indicates that the mouse is exiting
(away) the face.
|
| | position | The 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:
| | face | The face that got the event.
|
| | action | A word that indicates the action that has occurred.
|
| | event | The 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:
| | down | the main mouse button was pressed.
|
| | up | the main mouse button was released.
|
| | alt-down | the alternate mouse button was pressed (right button
on right handed mice).
|
| | alt-up | the alternate mouse button was released.
|
| | over | the mouse was moved over the face while either button was pressed.
|
| | away | the mouse has moved off the face while the button was pressed.
|
| | key | a 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:
| | face | The face that has the event.
|
| | event | The event that provides detailed information.
|
The detect function must return either:
| | event | The same event that it was passed as an argument.
|
| | none | When 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:
| | /face | Returns the top-level face object of the window that was
active when the event occurred.
|
| | /type | Returns event type such as down or up for
the mouse button. The event types are listed in the next section.
|
| | /offset | Returns the mouse cursor offset relative to the
REBOL/View window. The offset returned has different meaning
depending on the event type. See table below.
|
| | /key | Returns 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.
|
| | /time | Returns an integer representing a unique time index for the
event.
|
| | /control | Returns 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.
|
| | /shift | Returns 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-click | Returns 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:
| | open | Open 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.
|
| | close | Ignored. Does nothing.
|
| | pick | Pick next event from the port. Index should always be set to
one (because it always returns the first event, regardless).
|
| | get-modes | If 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:
| | text | the field that holds the text to be displayed in the face.
|
| | font | the font attributes for rendering the text.
|
| | para | the paragraph spacing and alignment attributes.
|
| | line-list | a 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:
- Receive keyboard events
- Receive mouse scroll-wheel events (scroll-line and scroll-page)
- 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-caret | Maps 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-offset | Maps 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-start | The string position to begin highlighting text.
|
| | highlight-end | The 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:
| | face | The face that specifies the text and its facets.
|
| | line-info | A special text line object that is passed to the function
and gets modified to hold the result. See details below.
|
| | line | The 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:
| | start | Offset of start of line in the string (string!)
|
| | num-chars | Number of chars in the line (integer!)
|
| | offset | XY position of the line (pair!)
|
| | size | Width 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:
- 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.
- 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.
- 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:
| | read | Read a text clip from the clipboard.
|
| | write | Write 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:
| | open | Open the clipboard for text read and write.
|
| | close | Close the previously opened clipboard.
|
| | insert | Insert text into the clipboard.
|
| | copy | Copy 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 | |