REBOL
Docs Blog Get-it

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.
Do not assume the index sequence.

There are times when the display system will call an iterator function with an integer value (for a redraw) but not continue to sequence through remaining integers. This is required by the system to obtain a valid object for internal operations.

In addition, you should not assume that your iterator function will be called in any specific sequence. This is important. Your code should recompute its face values based on the index value alone, not based on prior index values.

Iteration Termination

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.

Iteration Example

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.

Special Notes

Image Datatype

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 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://data.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

Face Scrolling

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://data.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.

Known Problems

  1. 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.
  2. 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.
  3. The over function when used with iterators may have the wrong logic sense for its into argument. More testing required.

Examples

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.

Shared data in feel objects

In the above example, the data field holds the offset where the down event first occurred. This works fine in the above case because the feel object is unique to this face. It is not shared with other faces.

However, the feel object is often shared. In such cases, a direct reference to the data field would not work. It's important to be aware of when feel objects are shared and it is usually a good idea to design them so they can be shared. In the above example, we could have used face/data to store the offset if we wanted to share the feel with multiple faces. The code would be:

engage: func [face action event] [
    if action = 'down [face/data: event/offset]
    if find [over away] action [
        face/offset: face/offset + event/offset - face/data
        face/text: form face/offset
        show face
    ]
]
About | Contact | PrivacyREBOL Technologies 2024