R3 GUI Actors
Author: Carl Sassenrath, Saphirion AG
2. Defining Actors
3. Standard Actor Names
4. Default Actors
5. Calling an Actor
6. Actors for Initialization
7. Actors for Events
9. Other Actors
An actor implements a style function.
If you think of a style as an object class, its actors are like the methods, and they operate on the instance of that class, the face object. Essentially, they are functions that are reused for each instance of a style, each face.
We call them actors because they are are not true methods and are similar to the actor functions used in schemes.
GUI styles define actors to provide functions that handle events, process input, modify attributes, and setup graphics rendering.
Within a style an w:actors block holds the actor definitions. The block is a collection of actor names followed by their function body blocks. No function creators or argument specifications are needed, the f:make-style function will create those.
Note that actor blocks are treated in the same way as the body block of a Funct or Closure construct so that the set-words used in the actor block define variables local to actor (initialized to none). If you need to set the value of a variable that isn't local to the actor, you can either use a set-path or f:set.
The naming convention for actors begins with w:on- followed by a verb or verb-noun that best describes the action. Examples are: w:on-draw or w:on-view. Although not strictly required, using this convention helps make GUI code more clear.
Here's an example actors block as it would look within a style definintion:
actors: [ on-make: [ ... ] on-update: [ ... ] on-resize: [ ... ] on-scroll: [ ... ] on-over: [ ... ] ]
Here is a complete style definition of w:clicker, the base-style that implements buttons.
clicker: [ about: "Single-action button without text. Basis of other styles." tags: [internal] facets: [ init-size: 28x28 bg-color: 80.100.120 border-color: 0.0.0.128 pen-color: ; set by on-draw area-fill: ; set by on-draw material: 'chrome focus-color: guie/colors/focus draw-mode: 'normal materials: none face-width: none ] options: [ face-width: [integer!] init-size: [pair!] bg-color: [tuple!] ] state: [ validity: none ] draw: [ normal: [ pen pen-color line-width 1 grad-pen linear 1x1 0 (viewport-box/bottom-right/y) 90 area-fill box 1x1 (viewport-box/bottom-right - 2) 1 ] focus: [ fill-pen focus-color box -1x-1 viewport-box/bottom-right 5 pen pen-color line-width 1 grad-pen linear 1x1 0 (viewport-box/bottom-right/y) 90 area-fill box 1x1 (viewport-box/bottom-right - 2) 1 ] ] actors: [ on-make: [ if face/facets/face-width [ face/facets/init-size/x: face/facets/min-size/x: face/facets/max-size/x: face/facets/face-width ] ] on-init: [ set-facet face 'materials make-material face get-facet face 'material ] on-draw: [ set-material face face/state/mode color: get-facet face 'border-color if face/state/mode = 'over [color: color / 2] face/facets/pen-color: color arg ; return draw block ] on-over: [ ; arg: offset or none face/state/mode: pick [over up] face/state/over: not not arg draw-face face ] on-click: [ ; arg: event face/state/mode: arg/type if 'up = face/state/mode [face/state/mode: 'over] draw-face face if arg/type = 'up [ focus face do-face face ] true ;don't do unfocus ] on-focus: [ ; arg/1: TRUE for focus, FALSE for unfocus; arg/2 - forced re-focus flag set-facet face 'draw-mode either get arg/1 ['focus]['normal] set-facet face 'focus-color either get arg/1 [guie/colors/focus][255.255.255.255] draw-face face ] on-key: [ ; arg: event if arg/type = 'key [ switch arg/key [ #" " [ do-face face ] ] ] ] on-validate: [ face/state/validity: validate-face face ] ] ]
Actor arguments are fixed and standardized. The arguments are:
|face||The face upon which the actor acts.|
|arg||A single value or block of multiple values.|
When an actor is called, argument values are passed to the actor body block. Local variables can be defined using set-words (same way as in FUNCT method).
When a style is derived from another style its actors are inherited.
It should be the derived actors are not bound to any particular style, but are simple functions. This allows actors to be efficiently reused in all instances of a face object.
For example, w:button uses the w:clicker actors here:
button: clicker [ about: "Single action button with text." tags: [action tab] facets: [ init-size: 130x24 text: "Button" text-style: 'button max-size: 260x24 min-size: 24x24 text-size-pad: 20x0 ] options: [ text: [string! block!] bg-color: [tuple!] init-size: [pair!] face-width: [integer! issue!] ] actors: [ on-make: [ either face/facets/face-width = #auto [ face/facets/max-size: face/facets/init-size: face/facets/text-size-pad + as-pair first font-text-size? face-font? face face/facets/text 24 ][ do-actor/style face 'on-make arg 'clicker ] ] on-set: [ if arg/1 = 'value [ face/facets/text: form any [arg/2 ""] show-later face ] ] on-get: [ if arg = 'value [ face/facets/text ] ] on-draw: [ t: get-facet face 'text ; limit-text-size modifies, so we need to copy ; size is made 20px smaller to incorporate "..." (see text-size-pad) l: limit-text-size copy/deep t face/gob/size - face/facets/text-size-pad face-font? face set-facet face 'text-body either equal? t l [t][join l "..."] do-actor/style face 'on-draw arg 'clicker ] ] ]
Notice, no actor definitions from w:clicker are needed to be duplicated.
For any given style a facet value may be stored in either the style object itself, or within the face instance. The location depends on whether the facet is static for all face instances, or changes for each instance.
Because you don't know and shouldn't care where it is stored, the f:get-facet function is provided to get the value and the f:set-facet function to set it.
For example, if the w:on-resize actor needs to know the size facet, it would use:
size: get-facet face 'size
If the size is found in the w:face/facets object, that will be used. Otherwise, the w:style/facets object will be used.
A number of actor names are predefined for standard actions, and we recommend that you use these for their equivalent actors within your GUI styles:
|on-make||when face is first created to initialize special values|
|on-click||when mouse button clicked on face|
|on-drag||when dragging inside a face|
|on-drag-over||when dragging and are over a target face|
|on-drop||when drag has stopped or when a file is dropped|
|on-focus||when we have been given or are losing focus|
|on-get||to fetch state values|
|on-set||when state values are set|
|on-clear||when the state values are cleared|
|on-key||when key has been pressed (for our focus)|
|on-move||when mouse has moved|
|on-over||when mouse passes over or away|
|on-reset||when reset is needed|
|on-resize||when size values have changed|
|on-update||when face contents have changed|
|on-draw||when system starts to draw the face (create DRAW block)|
|on-scroll||when scrolling is needed|
|on-scroll-event||when the mouse wheel is used|
|on-init||when face and its parent pane are ready (including initial sizing)|
|on-attach||when a face is attached from another face|
|on-attached||when a face is triggered in the attach reaction chain|
A small number of actors are defined by default to work for all styles. They are:
|on-resize||Recompute the area-size facet.|
|on-get||Return the face/state of a given name.|
|locate||A special actor to map an event to an offset position.|
|on-attached||By default sets the attached face value to the current face value.|
Style definitions are allowed to override these actors with their own definition specific to their operation.
There will be times when one actor function will call another. This is done with the f:do-actor function. Actors are always called this way, never directly.
do-actor face 'on-click true
It should be noted that the call will be ignored if the face has no w:on-click actor.
Sometimes you can reuse actor code from another style to avoid duplicate code in multiple styles.
on-resize: [ ;call the standard on-resize code from FACE style do-actor/style face 'on-resize arg 'face ;here follows specific on-resize code for this style ... ]
The w:on-make actor is called when the face is created within the layout engine.
This actor can be used to setup face facet values such as blocks of data, colors, face orientation, etc. and anything else unique to the face. If you are defining a compound style, this is a good actor to set-up any sub-faces.
It is not necessary to compute the face size within w:on-make because w:on-resize will be called later to do that.
Example that sets colors unique to the new face:
on-make: [ face/facets/colors: copy [255.255.255 128.128.128] ]
The on-init actor is called when a new layout is created.
When a new layout is created, the w:on-init actor will be called for each face within the layout. At this point the face objects exist and the layout has been initialized, including any special bindings.
It is also called for any trigger faces (special faces that act on w:when layout events.)
Resetting the face on initialization:
on-init: [ do-actor face 'on-reset ]
The w:on-attach actor is called when this face gets attached from another face.
the face requesting attachment
Here is simple example hwo it works:
stylize [ node: box [ facets: [ text-style: 'title ] options: [ bg-color: [tuple!] text-body: [string!] ] actors: [ on-attach: [ print ["ON-ATTACH: Node" face/name "has been requested to attach from node" arg/name rejoin ["[" arg/name "->" face/name "]"]] ] ] ] ] view [ A: node red "Node A" attach 'B B: node green "Node B" attach 'A C: node blue "Node C" attach 'A ]
You should see following output in the console:
ON-ATTACH: Node B has been requested to attach from node A [A->B] ON-ATTACH: Node A has been requested to attach from node B [B->A] ON-ATTACH: Node A has been requested to attach from node C [C->A]
The w:on-attached is called on every attached face. It is triggered by w:do-face function call sequence on a face that attaches to one or more face targets.
The w:on-click is called each time a mouse button press or release occurs.
the event object for the mouse click.
on down, can be none/false(unfocus is called), true(unfocus is not called) or the drag object. On up, none.
This example prints the button event that occurred:
on-click: [ probe arg/type ]
w:on-click is also the precursor to dragging (holding down a mouse button, while moving the mouse), so you can create a drag object in w:on-click using the f:init-drag function. If you return the drag object the w:on-drag actor will be invoked.
on-click: [ if arg/type = 'down [ return init-drag face arg/offset ] none ; return value ]
The w:on-drag actor is called when the drag object is created or when the mouse is moving inside its target face.
drag object (created earlier)
w:on-drag usually comes after w:on-click and ceases to be used when the drag object is destroyed, which happens right after on-drop.
Note that when a drag object exists, the w:on-move and w:on-over actors are not called.
Here is an example used by a slider that changes a value constantly during drag, updates its graphics, and calls its attached faces (if any).
on-drag: [ ; arg: drag do-actor face 'on-offset arg/delta + arg/base draw-face face do-face face ]
The w:on-drag-over actor is called when the mouse is moving over a foreign face (not the target of the drag.)
Block of values related to the drag [drag-object offset ???]
on-drag-over: [ ; write this example ]
The w:on-drop actor is called when the drag operation is released.
the drag object created earlier.
At the end of a drag operation, when one of the mouse buttons is released, the w:on-click action is called with the event, then w:on-drop is called.
After w:on-drop, the drag object is automatically destroyed. The w:on-drop actor is caleld regardless of whether the drag operation was started over this face or a different face.
The w:on-drop actor is also called if a file is dragged and dropped from the system desktop over the face. This is a special case.
on-drop: [ ; write an example here ]
The w:on-focus actor is called every time the face gains focus using the f:focus or f:next-focus function or loses focus with the f:unfocus function.
true for focus and none for unfocus.
Nothing is called after w:on-focus, so if the face is changing appearance, a f:show-later should be called within the actor.
This example makes the background color yello (selection variation of yellow) when the face is focused and makes it white, when the face is unfocused:
on-focus: [ face/facets/bg-color: pick reduce [yello white] arg draw-face face ]
The w:on-key actor is called when a face has focus and a key is pressed.
the input event
the same event
The w:on-key actor has a default value for all faces, to return the event argument.
Simple keyboard navigation in a street map face:
on-key: [ if arg/type = 'key [ ;detect only "key-down" event types (use 'key-up for up events) dx: dy: 0 switch arg/key [ right [dx: 1] left [dx: -1] up [dy: 1] down [dy: -1] ] if find arg/flags 'shift [ dx: dx * 3 dy: dy * 3 ] move-map as-pair dx dy ] arg ; return same event ]
The w:on-move actor is called every time the mouse moves.
the event value (with face-relative positions)
- Not required for most styles.
- Will be called often, so don't define it unless you need it.
- The w:on-over actor will be called after this actor.
- Not be called during a drag operation.
The w:on-over actor is called every time the mouse moves over or away from the face.
the face relative position or none
If the face has the w:all-over value specified as true, this actor will be run continuously as long as the mouse is over the face. If w:all-over is not true, it does not report continuously, only on enter and exit.
The w:on-resize actor is called every time the layout is resized.
new size (pair! value)
Normally, you use this actor to modify the size fields of the draw block and any GOBs used within it. If your style is a compound style, the faces inside may also need to be resized.
Resizing a single face:
on-resize: [ face/gob/size: arg set-facet face 'size arg ]
Resizing a compound face (of 'panel' layout type):
on-resize: [ do-actor/style face 'on-resize arg 'hpanel ;style specific code follows .... ]
The w:on-scroll actor is called from one face to scroll another face.
the face object of the scroller
true if the attach was successful
When a scroll-bar is moved it will call this function to tell its target face to scroll. This is how a scroll-bar informs the other face that it's time to update. So, this actor must be defined for styles that are scrollable.
When a face is scrolled, this actor can be called directly by the w:scroll reactor or by a prior attachment that occurs when a scrollable face is defined before a scrolling style.
Do not confuse this with w:on-scroll-event which takes an event to perform.
Scroll a text area face:
on-scroll: [ ; arg: scroller gob: sub-gob? face size: gob/size - gob/text/scroll tsize: size-text gob gob/text/scroll/y: min 0 arg/state/value * negate tsize/y - gob/size/y + 5 show-later face ]
The w:on-scroll-event actor is called when a scroll event occurs.
the same event
Scroll events can be caused by the mouse-wheel or other such devices.
Do not confuse this actor with w:on-scroll which is used to scroll a face from another face, such as a w:scroller style.
Here's an example:
on-scroll-event: [ dy: none switch arg/type [ scroll-line [dy: arg/offset/y / -30] scroll-page [dy: negate arg/offset/y] ] if dy [bump-scroll face dy] none ]
Note that w:bump-scroll is a function specific to this style.
The w:on-get actor returns values stored in the face object and is normally called by f:get-face. Normally, the default actor is all you need.
the name of the variable to fetch (default is value)
The value fetched.
The default definition of w:on-get is:
on-get: [ ; arg: the field to get select face/state arg ]
The w:on-set actor is called by f:set-face for setting the state value of a face or any other value in the face.
A block containing the name and its value.
The default name is w:value, which means set the primary value of the face.
Here's an example of w:on-set used by a clock style to set its time:
on-set: [ if arg/1 = 'value [ if date? time: arg/2 [time: time/time] face/state/value: time face/facets/clock/set-clock time show-later face ] ]
The w:on-clear actor is called when a face needs to be cleared.
This actor is called by the w:clear reactor. It is also called when a layout that contains input fields is cleared with the f:clear-layout function.
on-clear: [ clear face/facets/text-edit show-later face ]
The w:on-reset actor is called when the face needs to be reset to a predefined initial value.
This actor is mainly used for the w:reset reactor.
on-reset: [ do-actor face 'on-set 0 ]
The w:on-draw actor allows you modify a draw block immediately before it is rendered.
the current draw block for the face
the modified draw block
This function may be called often, every time a f:draw-face is needed. You should make this actor as efficient as possible. If it is possible for computations can be performed in other actors, such as w:on-click, that should be done.
Here is an example of the w:on-draw used by buttons (from the clicker style):
on-draw: [ set-material face face/state/mode color: get-facet face 'border-color if face/state/mode = 'over [color: color / 2] face/facets/pen-color: color arg ; return draw block ]