DEV Styles documentation - GiuseppeChillemi/VID-Extension-Kit GitHub Wiki
VID Extension Kit - Style Creation - Build 003 (obsolete)
Author: Henrik Mikael Kristensen
Version: 0.0.3
Date: 18-Sep-2009
=options no-nums
Note that this document only fits build 003.
===Writing a Style
Writing a style is a simple task, as long as you know how VID works, when layout does its job and how the event system works against the feel object in your style.
This document describes not only the steps taken to create a style, but also why the steps are taken, and what internal parts of VID you are drawing from. This is done, so you have a better idea on why some things work the way they do. The internal structure of VID cannot be entirely encapsulated and has certain names, words and behaviors and structures formalized (or assumed), and it's important to know the mechanics behind those formalizations.
All styles in the VID Extension Kit was written with the knowledge that is in this document.
The standard VID styles were written a long time ago and they were written sparsely and condensed into one source file. Wherever resources were needed, the standard contexts that lurk in the background step in, and perform deeper operations, like providing feel objects or text editing capabilities for a style.
As such, a standard VID style can both be a marvel in simplicity to look at, but also frustrating, because it's an object, and objects in REBOL, particularly those that belong to the system, do not often show their true nature, by just looking at their contents. In short, you can't backtrack where code in an object comes from without having the original source available.
---Peeking into a Style
When using the get-style function to spy on the contents of a style, you don't see where feels come from and how various parts of code is bound to various background contexts. As an example, let's take a look at the FIELD style:
get-style 'field
This returns an object, a face with all contexts and objects calculated in. So if you decide you want to know how text editing works in the field, you can probe the feel object:
probe get in get-style 'field 'feel
Some text handling code appears. If you look closer, you'll find entries in the code, that are not present anywhere else in the face object, such as edit-text. That is because the feel object for this style is bound to the ctx-text context, which you can see like this:
? ctx-text
By using probe here, you would be probing a very large part of the system object, which is referenced in the ctx-text context; You might be watching code scrolling by for several minutes!
For a better experience, it helps to look at the original standard VID source for the styles. The original code for FIELD is more simple to look at:
FIELD: FACE 200x24 with [
color: none
colors: reduce [ctx-colors/colors/field ctx-colors/colors/field-select]
edge: [size: 2x2 color: ctx-colors/colors/bevel effect: 'ibevel]
font: [color: ctx-colors/colors/field-font style: colors: shadow: none]
para: [wrap?: off]
feel: ctx-text/edit
access: ctx-access/field
init: [
if color [colors: reduce [color colors/2]]
if not string? text [text: either text [form text][copy ""]]
if not flag-face? self hide [data: text]
]
flags: [field return tabbed on-unfocus input]
words: [hide [new/data: copy "" flag-face new hide args]]
]
Now the resources we use become clear:
-
There is ctx-colors/colors, which is short for system/view/vid/vid-colors.
-
There is ctx-text which is for text editing
-
There is ctx-access, which provides standard accessors to styles like FIELD.
Like when creating any REBOL object, the definition order is important. But the placement of the init block becomes less important, because it will not be executed until after it has been placed in the layout.
---Ways of Making Styles
The official way to create a new style in the VID Extension Kit, is by using stylize/master.
But VID can use many different ways it's so simple to derive new styles from older styles. Most of these can be seen more as short term solutions, for programming in the small.
+++Inline Style Creation
For example, you create a new temporary style for a layout, when creating a short form for an existing style:
view make-window [
style big-btn button 150 "Big Button"
big-btn big-btn big-btn
]
=image images/stylize1.png
The method is really useful for simplifying a single layout block, but nothing more than that.
+++Stylize
You can also use stylize and store your styles in a separate style sheet. Then you use your style sheets on a per-layout basis:
my-styles: stylize [
big-btn: button 150 "Big Button"
]
view make-window [
styles my-styles
big-btn big-btn big-btn
]
This was to be seen as useful in cases where you need to create a bunch of styles that could be used by another developer and avoid certain name clashing. However, when using multiple stylesheets with styles that have identical names, those will be overwritten anyway.
+++Stylize/master
The third way is by using stylize/master, the style becomes part of the master stylesheet in system/view/vid/vid-styles, and becomes available in all layouts immediately. You don't have to worry about style maintenance to make your style work where you want it.
The caution you must take shifts from worrying about using the correct stylesheet with stylize, to not silently overwriting existing styles with identical names on a global level with stylize/master.
stylize/master [
big-btn: button 150 "Big Button"
]
view make-window [
big-btn big-btn big-btn
]
As a factoid, VID3 for REBOL 3 allows only a method that is similar to stylize/master.
Notice the syntax for stylize and the style keyword in the layout block. They are nearly identical, except that in the case for stylize, you make the new style name as a set-word!, whereas during layout, the keyword style is used, followed by the style name, big-btn. Everything after that is the same.
You can use the standard keywords (facets) and input datatypes that follow a single style, but not layout words, such as pad or across.
An important word is with, which you may already have seen in layouts. with lets you provide details to the style in the same manner as you would when specifying a normal object. Any facet of the face can be altered using with, and you can run small bits of code as well.
Example:
view make-window [
button with [text: "Some Text"]
]
---Creating new Base Styles
This section will detail the creation of a single style from beginning to end. We will call it S for short.
Creating a base style involves deriving a style from FACE. FACE is the lowest level style, or the one, that contains the least amount of extraneous information.
We simply begin with:
stylize/master [
s: face
]
The style has been created, but it can't be used in a layout:
view make-window [s]
** Script Error: Cannot use add on none! value
** Where: forever
** Near: max-off: maximum max-off new/size
+++The Minimum Style
We have not reached the minimal level of necessary information for a usable style.
The necessary components are:
:size - For FACE, size is none, and in layout, the face size is required, for it to create the layout.
:init - The init block is required to be a block. For FACE, the default init is none.
As a quick measure, we can set size to 100x100.
The init block is bound to the face instance itself, as it's created. It's important here to discern binding the init block to the face that is made, from the style that we are making here; They are two different objects. The binding is handled in the layout function.
The modified style:
stylize/master [
s: face with [
size: 100x100
init: []
]
]
At this time, the style is ready for use, but it won't look like much:
view make-window [s]
=image images/basicstyle.png
We can then add text. In View (the basic graphics engine of REBOL), text is rendered from the contents of the string stored in text, using the font object stored in font with margins and origin stored in para.
stylize/master [
s: face with [
size: 100x100
init: []
]
]
+++Using Init
We can now use the init block in a useful manner. We can for example give the style a basic text and manipulate it during init.
stylize/master [
s: face with [
text: "My Face"
size: 100x100
init: [
text: uppercase text
]
]
]
This shows that the init block is bound to the face instance, which relieves you of referring to the face itself inside the block. If you need to, the word self is used. The result should be uppercase text:
view make-window [t: s]
=image images/styletext.png
As a side note, you may notice that if you study init in the face instance now (hence the set-word 't above), it's empty. Layout copies a new block in place to save memory, otherwise the full init block would exist in many copies across the layout.
With even greater usefulness, the text can be used to resize the face to fit around the text. This can be done using the size-text function, but here we must know that init performs its code during layout.
The function size-text requires an existing face as an input, and more importantly, identical font and para setup to work properly. The best method is to set up the face itself so it gets a very large width, to avoid the text wrapping in the face. We can do this without wasting much memory, in that nothing is displayed for this operation.
The init block will appear as so:
init: [
text: uppercase text
size/x: 1000
size: size-text self
]
To make the face more visible, we can set color to blue:
stylize/master [
s: face with [
text: "My Face"
size: 100x100
color: blue
init: [
text: uppercase text
size/x: 1000
size: size-text self
]
]
]
The result, after layout:
=image images/sizedstyletext1.png
There is not enough room for the text! The size needs to take the font offset as well as the paragraph origin into account:
init: [
text: uppercase text
size/x: 1000
size: size-text self
size: size + font/offset + para/origin
]
=image images/sizedstyletext2.png
...Caveats with init
One has to be aware of limitations in what can be done inside init, particularly when it's run; It's run right when the face has been built, but before subfaces or the entire layout has been built.
This means:
-
It's not possible to refer to future faces in the init block.
-
It's not possible to refer to the parent face, since all parent faces are not safely set until the entire layout is complete using the set-parent-faces function near the end of the make-window function.
-
Depending on when you layout faces in a pane inside the face, those faces may or may not have information available at init time.
+++User Defined Text
Next we want the text to be alterable by the user using a text string, specified in the layout block:
view make-window [s "This is a long text"]
=image images/sizedstyletext3.png
It already works! But why does it work?
+++Facets without Keywords
This is because text is a standard facet for this style. This particular facet is only represented by its datatype, and layout detects this facet as a string!.
We haven't done anything, but what we haven't seen yet, is that FACE provides a standard facet gathering object, called multi, which is short for multi-facet, which you can study like this:
probe get in get-style 'face 'multi
This object consists of functions that each are called inside a function called grow-facets during layout. The multi functions are used, when the facet is represented only by the datatype.
We can have a look at the mechanism in multi, that makes sure the user defined text ends up in the right place:
probe get in get in get-style 's 'multi 'text
== func [face blk] [
if pick blk 1 [
face/text: first blk
face/texts: copy blk
]
]
The input is the face itself and a block!. Basically, facets of the same datatype can occur multiple times, and multi can handle them.
Also note that the function is designed not to set anything, if the input blk does not contain anything. This is because grow-facets runs the function unconditionally, whether there are facets for it or not in the specification.
The layout function first gathers all facets that are not attached to keywords. As standard these are:
:texts - Input is one or more string!s, usually for setting face/text and face/texts.
:pairs - Input is a single pair! or integer!, usually for setting the size of the face.
:files - Input is a file!, which is loaded during layout. Typically, this is used for loading image data into face/image.
:images - Input is a single image!, set in face/image.
:colors - Input is one or two color tuple!s, usually for setting the foreground and background color in the face.
:blocks - Input is one or two block!s, for setting face/action and face/alt-action. Creative use of these in the init section of some styles allow them to be used for other things than face actions. A notable example of this is PANEL.
So, when you enter a string! in the layout block, it's parsed in grow-facets and placed in texts.
Then grow-facets calls each multi function sequentially in the order as shown above, and applies the facets to the face instance.
The illustration shows the sequence:
=dot images/growfacets.png rankdir=TB dpi=90 node [fontname="Helvetica" fontsize=11 shape=rectangle] edge [fontname="Helvetica" fontsize=10] "field 200x20 integer" -> "layout block" [color=blue] "field 200x20 integer" -> "layout block" [color=red] "layout block" -> "layout" [label="style name,\nspecification" color=blue] "layout block" -> "layout" [color=red] "vid-styles" -> "layout" [label="style object"] "layout" -> "grow-facets" [label="new,facets" color=blue] "layout" -> "grow-facets" [color=red] "grow-facets" -> multi [label="facets" color=blue] "grow-facets" -> words [label="custom\nfacets" color=red] words -> integer [label="logic!",color=red] multi -> texts [label="string!"] multi -> files [label="file!"] multi -> colors [label="tuple!"] multi -> pairs [label="pair!",color=blue] multi -> images [label="image!"] multi -> actions [label="block!"] files -> new [label="file!"] images -> new [label="image!"] pairs -> new [label="pair!",color=blue] colors -> new [label="tuple!"] texts -> new [label="string!"] actions -> new [label="block!"] integer -> new [label="logic!",color=red]
The blue path shows how the integer 200x20 is passed into the face instance new. The red path, which will be explained in the next section, shows how the integer keyword is passed into the words block and set for the new face.
Facets are collected and ready for you, prior to the execution of the init block.
...Building your own facet functions
Normally you don't need to alter the multi functions, as they cover a good amount of datatypes
+++Facets with Keywords
Facets with keywords allows for more flexibility and readability for special parameters. Basically, they are written as words optionally followed by any type.
The facets default, setup, align, fill and spring mentioned in the beginning of the VID Extension Kit manual are all of this type.
In the FIELD style for example, you can specify an integer keyword:
view make-window [
field integer
]
This will set the word integer to true in the face, which will in turn alter the behavior of its accessors set-face* and get-face* to provide numeric functionality instead of using strings.
There are two subsets of facets with keywords:
:Standard facets with keywords - The built in keywords, such as default, setup, font, edge, etc.
:Custom facets with keywords - These are keywords defined in the words block in the style as a series of words and blocks. They are created and processed in exactly the same way as standard facets with keywords.
...Building a Custom Facet without an Argument
To use custom facets, we can extend the style again with a words block. The format for this block is:
words: [
keyword1 [...code...]
keyword2 [...code...]
]
The words block has one caveat: The face specification is handled as a block of arguments args, which you must sometimes move the index for.
This is because, layout temporarily hands over control of the arguments block args to us, so the specification for the face can be parsed in one pass. When each entry in the words block has been processed, the arguments block args needs to be pointing at the right index. If not, the effect will be that facets in the specification might be ignored or their arguments are ignored.
The conditions are:
-
You need to move the args index one step, if the keyword has one argument. That is: next args.
-
You don't need to move the index, if the keyword has no argument. You just return args. For the uppercase keyword, we therefore don't need to move it.
-
You must always return the argument block args, so its index is kept up to date for the next function.
Furthermore, referencing the face happens using new. The block will look like this:
[new/uc: true args]
For our style, we specify that the text in the style, should only be uppercased, if the keyword uppercase occurs in the layout block.
As a means to store that information, we can specify a uc word in the face, for later usage in init.
The init block has been changed to take advantage of the uc word:
stylize/master [
s: face with [
text: "My Face"
size: 100x100
color: blue
uc: false
init: [
if uc [
text: uppercase text
]
size/x: 1000
size: size-text self
size: size + font/offset + para/origin
]
words: [
uppercase [new/uc: true args]
]
]
]
The result can be seen immediately:
view make-window [
s "lowercase"
s "uppercase" uppercase
]
=image images/stylefacet1.png
...Building a Custom Facet with an Argument
If we want a keyword with a value, we could create a section in init that specifies that the face text may only be displayed up to a certain length, although internally, we will store it at its full length.
This could be a max-length keyword along with an integer! as argument.
Like before with uc, we can store the max-length in the face itself.
The words block will now appear as this:
words: [
uppercase [new/uc: true args]
max-length [new/max-length: second args next args]
]
The use of second args indicates the location of the argument in the args block. The use of next args sets the right index for args. Note that we don't need to store the index for args, just return it.
If we want to avoid destroying the text internally, we need to store it in two places. Fortunately, faces usually have a place for this in data.
The init block is altered accordingly:
init: [
if uc [
text: uppercase text
]
if max-length > 0 [
data: text
text: copy/part text max-length
]
size/x: 1000
size: size-text self
size: size + font/offset + para/origin
]
We can now use it in the layout to produce layouts with maximum text lengths of 5 chars:
view make-window [
s "My custom text" uppercase max-length 5
]
=image images/stylefacet2.png
+++Accessors
To further allow manipulation of a style, we can use accessors. Accessors usually consist of an face/access object inside the style and a public function counterpart for end users.
=dot images/accessordiagram.png rankdir=LR ratio=0.3 dpi=72 node [fontname = "Helvetica" shape = rectangle fontsize = 11] edge [fontname = "Helvetica" fontsize = 10] "set-face" -> "face" [label = "value" color = blue] face -> access -> "set-face*" -> text [label = "value" color = blue] text -> face
If we visit the set-face function, it's revealed, that it uses access/set-face* to manipulate the text of the face:
probe :set-face
== func [
"Sets the primary value of a face. Returns face object (for show)."
face
value
/no-show "Do not show change yet"
/local access
][
if all [
access: get in face 'access
in access 'set-face*
][
access/set-face* face value
ctx-format/format face none 'on-set
]
if not no-show [show face]
face
]
If we visit the face/access object, we see how text really is manipulated for set-face*:
probe get in get in get-style 's 'access 'set-face*
== func [face value][face/data: value]
This looks rather simple, so why use accessors at all?
...Why use Accessors?
Accessors are simply a formalized way of accessing the internals of a face, and in object oriented programming circumstances, this would be similar to public functions. Since REBOL does not use the concept of public/private functions for objects, accessors are plain formal. You could just as well access the face directly like this:
view/new make-window [
t: s "My custom text"
]
forever [
t/text: form now/precise
show t
wait 0.2
]
But the reasons for formalizing access to the face are:
-
The case where you replace the style with a different one of similar capability, like CHECK-LINE versus TOGGLE. Outwardly, the use of set-face would be identical, but inside, set-face* might be different.
-
You use the face in a larger array of faces, and wish to have uniform access to every single one of them.
-
Complex internal handling of the data that is fed to the style.
-
Rules for how to handle a face that does not use a specific accessor. How do you use scroll-face on a button? You don't and it has to be properly handled.
We are, in this style, handling and manipulating text in ways that another developer may not be aware of. For example, what is the relationship between face/text and face/data in our style? Sometimes, they are the same and other times, they are copies of eachother. It may not be easily apparent to another developer, who would be forced to spend time reading the source code to your style.
The original VID has weaknesses in that some styles do not have properly set up access objects, forcing you to access the face instance directly. This is fixed as much as possible in the VID Extension Kit.
Using accessors, the above example might look like this:
view/new make-window [
t: s "My custom text"
]
forever [
set-face t now/precise
wait 0.2
]
Note the absence of show as well as form. The style would be able to handle form internally, and show is handled automatically in set-face. You no longer see the complexity that you had to not just see, but had to write up before.
A simple line like:
set-face p form-values
where 'p is a PANEL style, perhaps with many form elements of different kinds, can involve hundreds of accesses. If you are using many faces, the code can quickly grow much more complex.
There are a lot of reasons to use accessors!
...A Standard Accessor
VID comes with a lot of standard access objects in the ctx-access context, and one set is already set up in FACE, the style we are basing our style on.
Each object provide different schemes of accessor functions, so it can be wise to investigate the existing ones, before starting to write your own. The VID Extension Kit comes with even more accessor objects. You can explore them with:
? ctx-access
data object! [set-face* get-face* clear-face* reset-face*]
data-default object! [set-face* get-face* clear-face* reset-face*]
data-state object! [set-face* get-face* clear-face* reset-face*]
data-number object! [set-face* get-face* clear-face* reset-face*]
data-find object! [set-face* get-face* clear-face* reset-face*]
data-pick object! [set-face* get-face* clear-face* reset-face*]
text object! [set-face* get-face* clear-face* reset-face*]
field object! [set-face* get-face* clear-face* reset-face*]
image object! [set-face* get-face* clear-face* reset-face*]
panel object! [setup-face* set-find-var set-panel set-pane...
face-construct object! [set-face* get-face* clear-face* reset-face*...
selector object! [set-face* get-face* clear-face* reset-face*...
multi-selector object! [set-face* get-face* clear-face* reset-face*...
selector-nav object! [key-face*]
For this style, the most appropriate one is plain text.
It does a slight bit more than we need, but it covers our needs the best, so we simply assign it to our style:
access: ctx-access/text
Note that we don't need to copy the object. We simply use it directly from the ctx-access context.
You can extend or modify accessors in place, by redefining them, as they are always objects. When doing that, it's necessary to copy the object, to avoid distorting the original:
access: make ctx-access/data [
setup-face*: func [face value] [... some code ...]
]
...Accessor Functions
So, how do you name the accessor functions?
The existing public functions are a good indicator of what you can use, and they follow a simple scheme:
The scroll-face function, it will look for an access/scroll-face* function in the face instance. If it's not found, nothing happens. Similarly get-face will look for access/get-face* and so on.
Not all *-face functions cover this, as a function like back-face or center-face are not made for accessing faces, so it's more appropriate to look at which accessor functions can exist inside access objects:
:set-face* - Arguments: face, value. This sets the primary value of the face.
:get-face* - Arguments: face. This gets the primary value of the face.
:reset-face* - Arguments: face. This resets the primary value of the face to its default value.
:clear-face* - Arguments: face. This clears the primary value of the face. Different from reset.
:key-face* - Arguments: face, event. Is used to perform keyboard operations on the face. This function is run after primary keyboard operations like tab navigation has completed. This function only works when the face is focused.
:scroll-face* - Arguments: face, x, y. Is used to scroll a face horizontally or vertically. Each direction has separate methods, as for example used in DATA-LIST to scroll pixel wise horizontally, but entire rows vertically.
:get-offset* - Arguments: face. Returns the current offset for the pane inside the face. This is useful for the public scroll-face function.
:resize-face* - Arguments: face, new-size, x-only, y-only. Is used for custom resizing of a face, in case you need special weighting or other atypical resizing behavior.
:disable-face* - Arguments: face. This is used in case the standard effect for disabled face does not produce a usable result. This function mainly exists for cosmetic purposes.
:enable-face* - Arguments: face. This is the direct opposite of disable-face*.
:setup-face* - Arguments: face. For complex faces, this function is used to parse the setup keyword in the face specification.
All these functions have public counterparts.
...Building our own Accessor Function
You can also define your own accessors, whatever you deem necessary to formalize any particular kind of access to the face. They can have any name you want to give them.
Each one of the functions can have any arguments, where the first one should start with the face itself.
If we want to manipulate the text word in the face, we could build an access function that reads:
set-face*: func [face value] [face/text: value]
Note that we don't show the face here, as this is handled by set-face.
Since we have introduced a processing of the maximum length as well as uppercase for the text inside the init block, as well as calculating the size of the text, we will need to do exactly the same thing here.
To make this code appear only once in the style, the solution is to move the max-length, uppercase and text size calculation code inside the set-face* function, and use the set-face* function directly in the init block:
set-face*: func [face value] [
face/data: face/text
if face/max-length > 0 [
face/text: copy/part face/text face/max-length
]
if face/uc [
face/text: uppercase face/text
]
calc-text-size self
]
and:
init: [
access/set-face* self text
]
Note that we could call set-face/no-show, but using access/set-face* is a little bit faster.
In the code below, we'll add the set-face* function inside its access object and alter the init block to use that function.
stylize/master [
s: face with [
text: "My Face"
size: 100x100
color: blue
uc: false
max-length: 0
calc-text-size: func [face] [
face/size/x: 1000
face/size: size-text face
face/size: face/size + face/font/offset + face/para/origin
]
access: make object! [
set-face*: func [face value] [
face/data: face/text: value
if face/max-length > 0 [
face/text: copy/part face/text face/max-length
]
if face/uc [
face/text: uppercase face/text
]
calc-text-size self
]
]
init: [
access/set-face* self text
]
words: [
uppercase [new/uc: true args]
max-length [new/max-length: second args next args]
]
]
]
+++Feel
In this section, we want to delve into user input: Respond to a mouse click in the face, alter one of its properties and show the change.
Making the face react to mouse or keyboard input is the hardest part to understand of making a style, in that it involves the understanding of REBOL's event system, particularly caveats and limits.
Fortunately as in other sections, there are some standard feel objects available for common items like buttons in system/view/vid/vid-feel.
And so like accessors, they can be assigned to face/feel, conveniently using the svv shortcut for system/view/vid:
feel: svv/button
But unlike accessors, feels can have sections that rely on the appearance of the style. svv/button manipulates already existing colors, edges and gradients in order to produce alternate appearances for click, mouse over, etc. so there is motivation for creating your own.
...The Four Feel Functions
The feel object consists of four standard functions, engage, detect, over and redraw that each respond to both different events and to events in different ways. It's not possible to specify additional custom functions for processing events, as the feel object is specified by the View system.
If there is a specific function, you don't want to use, you should specify none, instead of the function, in order to avoid it being evaluated for speed reasons.
It's necessary to explain each one in depth before proceeding, but instead of doing that here, it's easier to refer to the excellent section in the REBOL/View Graphics Reference on the feel object here.
...Building your own Feel Functions
Now that we know the capabilities and limitations of each function
The whole style is now looking like this:
stylize/master [
s: face with [
text: "My Face"
size: 100x100
color: blue
uc: false
max-length: 0
calc-text-size: func [face] [
face/size/x: 1000
face/size: size-text face
face/size: face/size + face/font/offset + face/para/origin
]
access: make object! [
set-face*: func [face value] [
face/data: face/text: value
if face/max-length > 0 [
face/text: copy/part face/text face/max-length
]
if face/uc [
face/text: uppercase face/text
]
calc-text-size self
]
]
init: [
access/set-face* self text
]
words: [
uppercase [new/uc: true args]
max-length [new/max-length: second args next args]
]
]
]
+++Flags
Flags are very important to make your style a good "citizen" in the VID Extension Kit. They are used to determine how the face is perceived by tab navigation, whether the face is enabled or disabled and when get-face and set-face work on it, especially through panels. The source code to the VID Extension Kit stores all flags separately from the styles, to get a better overview of flags for related styles.
For your own styles you can settle for storing the flags directly in face/flags.
All flags are stored in face/flags.
...Flag list
:INPUT - The face adheres to get-face and set-face when in a panel.
:TEXT - contains text
:FIXED - used during layout for backdrop faces, removing them from the calculations of face sizes and offsets.
:DROP - Used during layout for setting the size of backdrop faces.
:FLAGS - ?
:FONT - The face contains a shared font object.
:CHECK - ?
:RADIO - ?
:TOGGLE - ?
:TABBED - Face will be tabbed to, during tab navigation, if the disabled flag is not set.
:AS-IS - Does not trim text in face/text, if this flag is set. This is used in text faces.
:PANEL - Style is a panel style which contains multiple other faces.
:RESIZE - Face is resizable.
:HIDE - Text is hidden (password style)
:CLOSE-FALSE - For buttons that close windows and return false.
:CLOSE-TRUE - For buttons that close windows and return true.
:ACTION - Manipulates its input directly via mouse and can be disabled.
:TEXT-EDIT - Face can be used for text editing.
:CHANGES - Produces dirty? flag to check whether the face has been changed by the user
:AUTO-TAB - Tabs away from a field solely based on cursor movement in text field
:INTEGER - Accepts only integers as keyboard input
:DECIMAL - Accepts only decimal as keyboard input
:TRANSPARENT - For panels, does not set-face and returns none for get-face when input faces inside it are disabled.
:SCROLLABLE - Indicates that this style can be scrolled using a SCROLLER or SLIDER.
:COMPOUND - Styles that contain specific other faces, built in a custom way, like CONSTRUCTs.
:DISABLED - Face is disabled. This is only controlled by disable-face and enable-face. When disabled, the tabbed flag has no effect.
---Optional Additions to Styles
What we've seen above is the minimum that we can do for a style from start to finish. What is described below can be considered additional fundamental parts that won't be directly applicable to the style we have produced above.
+++Panes
Using panes in your style can help making your style tremendously flexible: You can build styles from other styles as building blocks, rather than straight derivatives.
These styles are usually called compound styles. A typical example of a compound style is SCROLLER, which consists of two ARROW styles and a DRAGGER style:
=image images/compoundscroller.png
The method to create them is fairly straight forward, but there are at least three ways, depending on your needs:
-
You can define them directly in face/pane as a series of faces.
-
You can define them dynamically using init and store the result in face/pane.
-
You can define them dynamically and easily change them during runtime, by deriving from FACE-CONSTRUCT and let it work out how to store the result in face/pane.
If we keep in mind that layout works flatly on layout blocks, it's easy to see that each point requires either premade faces or the use of layout.
If we continue with our style, we can add a series of faces to its pane:
...Direct Definition of Pane
If you don't require any decisions on the layout of the pane, the simplest way is to just assign a layout to the pane. This is used in many compound styles, where the standard layout along with a fixed block, provides the necessary functions.
Again if we remember how layout works flatly, gradually on deeper levels of panes, layout runs init blocks in the same order. That means that any initialization of faces in the pane will occur before intializing the face itself.
This might limit the usability of this method, if you are intending to use styles that could require specific values to be set inside them, before they are initialized.
Some styles that are meant to exist inside another style.
...Using init
By using init, you can add some dynamics to creating the pane layout. This is often used in VID. As an example, the PANEL style, uses its init block to obtain a sub-layout block from the main layout block:
panel [h1 "My Panel" button "Click here"]
This sub-block is then processed with layout inside the init block and the resulting face tree is attached to face/pane.
Another example is the above mentioned SCROLLER, which forms its pane inside init by manually assembling faces from style parts and manually constructed faces. The appearance of the SCROLLER is determined by the size that is given to it during layout. In the picture above, the scroller had a specified size of 200x20. This is interpreted in the init block that the arrows and dragger must have a horizontal appearance and behavior.
For our style, it would be appropriate
You can then choose to either keep the face that makes the root of the tree. This makes face/pane an object.
You can also simply use its pane, which makes face/pane a block.
...Using FACE-CONSTRUCT
The powerful FACE-CONSTRUCT style produces layouts from a do-setup function. This is used for creating complex layouts from your own dialect or is somehow difficult to achieve using one of the other methods. It's particularly useful for building lists that internally has interactive components, like CHOICE fields, DATE fields and other items that changes the structure of the list on the fly. FACE-CONSTRUCT is only available through the VID Extension Kit.
This requires that instead of basing our style on FACE, we must use FACE-CONSTRUCT. It also means the game changes a little bit.
;figure out whether this is really the only thing that needs to be done. it strikes me as if face-construct does its own things with init and setup, so check the selectors on that.
Then you define face/do-setup, with these requirements:
-
The input must be face/setup to gather information about what needs to be set up. The reason for face/setup is to store information from the setup facet in the layout block.
-
Use the face/emit function to add parts to the layout stored in face/lo.
-
Return value is not required.
access: make ctx-access/face-construct [ setup-face*: func [face] [ ...figure out how to create a reasonable example for this... face/emit face/setup ] ]
For our style, we'll stick to an example, where we build a type of flexible form input through a dialect.
something about a dialect that forms a stack of faces like the search items in Finder.
+++Iterated Faces
If you are in the business of building lists, iterated faces might be a choice. Iterated faces are faces that are "painted" in sequence down a vertical or horizontal line. Thus they are not real faces, but using a feature of View in that when a region of a window is painted and not cleared, and then offsetting the face to a new location and showing it there, the effect can get the appearance of being a list. Correct use of an iterated face can save a large amount of memory.
As a sidenote, REBOL 3 doesn't use iterated faces, due to the face that memory savings would be neglible, thanks to its faces having a vastly smaller memory footprint than in REBOL 2. For REBOL 2 they can make sense.
However, this feature makes them a little hard to use:
-
It requires a function to describe its pane. With that comes various counters and requirements made to the function.
-
You are forced to repaint the entire list, every time a small change is done to the list.
-
Faces used in an iterated list may have special properties to work in an iterated face.
Therefore the VID Extension Kit comes with a range of standard styles to deal with iterated faces, to let you define a subface directly for use as a simple list view.
...Describing the Iterated Face Function
describe function something about subface subface faces have a position describe height skipping describe return values for the function describe when to stop rendering describe cell-func
Then inside the init block, the pane is set to the function:
pane: :iterated
You can read more about iterated faces in the REBOL/View View Graphic System Reference here.
+++Formatters
Using formatters is restricted to text only styles, but they can be used anywhere you like in the style code. If you noticed earlier, the set-face source code had a single line:
ctx-format/format face none 'on-set
This simply runs the formatter, whenever the keyword on-set is used in the layout block for a style.
But at this time, any on-* keyword is required to be stored in words. Hence you need an implementation for every single style. Currently this has only been done in FIELD and its derivatives. A redesign may be required to take fully advantage of formatters in all faces in a later build of the VID Extension Kit.
+++Image Stock
REBOL has a built in image stock, which you are free to use. It works by loading images from the stock using the load-stock and load-stock-block functions. The VID Extension Kit provides a few new bitmaps, but the basic functionality of how to use stock images has not changed.
All images are stored in system/view/vid/image-stock, and it's necessary to load them, in order to use them for a face. For speed and memory considerations, it's a good idea to load them once during definition of the style instead of during init.
Example:
image: load-stock 'help
If you don't want to use an image from the image stock, then what do we do? Consider the writing here.
+++Impossibilities
Some things are not possible, simply due to the sequence in which, things are done:
As init is performed during layout, you can't ask it to perform operations on adjacent faces, since they are not in place yet.
Also parent-face cannot be guaranteed to be set for the face until the entire layout is done.
---Deriving from Existing Styles
The process is very much the same as for creating new styles. What can be important, is to reduce the amount of baggage that comes with the definition of a new style, based on an existing complex style.