REBOL/View Graphics - Other Special Features
Iterated Virtual Faces
Sometimes it is not practical to specify every face that is to be displayed. For instance, in a table, file list, or email inbox, there may be hundreds of identical faces that vary by content and position. In REBOL, these "virtual faces" can be generated dynamically through a special function that creates iterated faces.
The Visual Interface Dialect (VID) uses iterated faces frequently for generating lists of all kinds.
The Iteration Function
To create an iterated face, the pane field of a face object is set to a function, rather than to a face or block. This function is called by the system to supply a face during both the rendering and event handling stages of the user interface.
The specification of the iterator function is:
func [face [object!] index [integer! pair!]]
Where face is the parent face whose pane function is being called, and index is an integer or pair (more details below).
The iterator function must return either a face object or block of face objects, an integer index, or none. See next section.
The Iteration Index
The index value that is passed to the iterator function is used by the function to determine the "instance" of the virtual face. There are two possible datatype for the index:
|integer!||When drawing the display, this allows you to set the offset, text, color or any other facets of the face before it is rendered. When the index is an integer, your iterator function should return either a face object, block of faces, or none.|
|pair!||When processing events such as over, this is the mouse location, used to determine which virtual face should be the target of the event. When the index is a pair, your iterator function should return either an integer or none.|
When your iterator function is being called with an integer index (during the draw operation), it will continue to be called for each instance of a face until your code returns a none value to indicate that the iteration has finished. This will be shown in the code below.
This example shows how an iterated face is used to create a pane of virtual faces, and how events are processed for such faces.
print "Click on the iterated faces." selected: 0 aface: make face [ size: 100x20 text: "Test" edge: none feel: make feel [ engage: func [face action event] [ print ["Engage" face/data action] if action = 'down [ selected: face/data show main-face ] ] ] data: 0 ] pane-func: func [face index] [ ; RETURNS: face, index number, or none ; ?? index either integer? index [ ; Draw needs to know offset and text: if index <= 10 [ aface/data: index aface/offset/y: index - 1 * 20 aface/text: form aface/offset aface/color: if selected = index [gold] return aface ] ][ ; Events need to know iteration number: return to-integer index/y / 20 + 1 ] ] view main-face: make face [ offset: 100x100 size: 100x220 edge: none pane: :pane-func ]
To see what's going on, uncomment the ?? index line.
Notice that the pane-func returns either a face object or an integer, depending on the datatype of the index argument. Also note that it will return none in the case where the index is out of range.
The image datatype is structured as a standard REBOL series. It has a head, a tail, and can be positioned to any point in-between. In addition, the image datatype allows two dimensional positioning and sizing through the use of an XY pair.
The elements of an image datatype are pixels. Pixels are RGBA tuple values (r.g.b.a) and indexing is done on a pixel basis rather than on an individual byte basis. The values all range from 0 to 255 (dark to light); for the alpha channel 0 is opaque and 255 is totally transparent. For example, 255.255.0.0 is opaque yellow.
The pixel values within an image can be manipulated using the standard series functions of REBOL such as change, insert, remove, clear, etc. In addition, there are three datatype accessors that are available for the image datatype.
For more about images, see the REBOL/View Image Datatype reference documentation.
Converting a Face to an Image
It is sometimes useful to convert a face (any face, even entire windows) to an image!. Some reasons for doing this might be:
- To save the face display as a file
- To use as screen-shots or examples in manuals
- To reduce computation for commonly composed images (precompute an image)
To convert a face, you can use the to-image function.
Here is an example that outputs a modified image to a file:
palms: load-thru/binary http://www.rebol.com/view/palms.jpg palm-face: make face [ offset: 100x100 size: palms/size edge: none pane: reduce [ make face [ offset: 0x0 text: "Example Image" size: palms/size image: palms edge: none font: make font [color: white size: 20 valign: 'middle] effect: [gradmul 1x1 red blue] ] ] ] view palm-face save/png %palm-face.png to-image palm-face
The design of the display system allows scrolling to be implemented easily. To scroll a face, you simply change its offset value relative to another face and call the show function.
Note that you can scroll any face, even faces that contain other faces (panes), such as ones that hold controls and other user interface elements. See the VID documentation for more information on this technique.
The example below scrolls an image both horizontally and vertically at the same time. In this example we implement an endless scroll (the image repeats) that updates itself based on a timer. You can easily modify the example to scroll based on any other type of action, such as a scroll-bar.
palms: load-thru/binary http://www.rebol.com/view/palms.jpg ; Duplicate image 4 times to make it appear endless: img: draw palms/size * 2x2 compose [ image 0x0 palms image (palms/size * 1x0) palms image (palms/size * 0x1) palms image (palms/size * 1x1) palms ] view make face [ offset: 100x100 size: palms/size edge: none pane: reduce [ make face [ offset: 0x0 size: img/size image: img edge: none rate: 50 feel: make feel [ engage: func [face action event] [ if action = 'time [ offset: offset - 1x2 if offset/x <= (negate palms/size/x) [ offset/x: 0 ] if offset/y <= (negate palms/size/y) [ offset/y: 0 ] show face ] ] ] ] ] ]
If you need to scroll text, you can use this technique as well. However, it is more efficient to use the text scrolling techniques described in the text section above.
- 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.
- 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.
- The over function when used with iterators may have the wrong logic sense for its into argument. More testing required.
A Drag and Drop Example
The code below shows how easy it is to implement a drag-and-drop style operation using only the engage function.
To perform the drag and drop, the engage function is used. First, we wait for a down event in the face, and we store the offset of the mouse pointer. Next, we look for over or away events and we update the offset of the face each time we get one. This causes the face to move.
view make face [ offset: 100x100 size: 300x300 edge: none color: water pane: reduce [ make face [ text: "Drag This Face" offset: 100x100 color: gold edge: none feel: make feel [ engage: func [face action event] [ if action = 'down [data: event/offset] if find [over away] action [ offset: offset + event/offset - data text: offset show face ] ] ] ] ] ]
Note in the code above that we must subtract the offset of the original mouse down event in order to keep the face at the correct location as we move it around.
Also note that we take action not only on over events, but also on away events. This is necessary because if you move the mouse quickly, it may occasionally slip outside the coordinates of the face and the event type will change from over to away.