gitea | include_toc |
---|---|
none |
true |
VID/S is different from VID because it serves a completely different design.
Feature | VID, View, Faces | VID/S, Spaces |
---|---|---|
Organization | Tree of static doubly linked face! objects (children have a /parent facet, parents have /pane). Face can appear in only a single place. |
Tree of singlyΒΉ or doubly linked space! objects (parents have /content, /items and /map facets, children have /parent only after render). Same space can appear in only a single placeΒ². |
Geometry | All faces are boxes sized in (virtual) pixels. | Each space can rotate/scale/bend it's children if it can express that in Draw. For interactivity support, it should provide coordinate transformation. |
Rendering | Faces are rendered by the OS, triggering redraw when a facet changes | Spaces are rendered using Draw dialect, redrawn continuously over timeΒ³. |
Naming | name: can prefix a face definition. |
name: can prefix a space definition. |
VID styles | style keyword allows to define a new style with new defaultsβ΄. |
style keyword allows to define a new style with new defaults. |
Stylesheets | No support. Faces look is mostly cast in stone. | Whole classes of spaces can be styled using a single style block or function. Styling is affected by the hierarchy. Spaces look can be fully customized, animated, effects applied (no limits). |
Coloring | Faces support both foreground and background colors. | Spaces support only one color, which will be e.g. text color for text and fill color for box . |
Font styles | Faces support font facet as well as bold/italic/underline/font-size shortcuts. |
Text spaces support bold/italic/underline shortcuts, while font and size can only be set by stylesheet. |
Actors | Each individual face can have a set of actors. | Each individual space can have the same set of actors. |
Event handlers | Events are handled by the OS. Some little customization can be done using a stack of event functions. | Events are handled by standard event handlers. Each space can have a stack of event handlers defining different levels of behavior from basic to custom. |
Facets | Each face has exactly the same set of facets that cannot be extended from VID. Mapping of VID datatypes to facets is hardcoded. | Each space defines it's own set of facets (only a few are shared), which can be extended by user with the facet= notation. Mapping of datatypes to facets is defined by spaces/styles map which can be altered at any time. |
Sizing | Each face has a fixed size that has to be changed manually when required. | Most spaces automatically adjust their sizes, virtually eliminating the need for manual intervention. limits facet controls the range in which the size can vary. |
Positioning | VID allows for quite tricky static layouts with rows, columns, alignment. Every pane is pre-arranged using the same powerful algorithm. | VID/S uses layout functions to position spaces (some support alignment). They are more limitedβ΅ and predictable but able to adapt to size changes. Some spaces can only contain a single child. |
Reactivity | Faces are deeply reactive. | Spaces are not reactiveβΆ, but VID/S adds (shallow) reactivity to spaces with a name or reaction defined. |
Footnotes:
ΒΉ Space tree uses /content or /items facet to fetch children. /map and /parent facets get auto-populated during render and are only valid for the last frame. They should be considered changing with each new frame.
Β² It's illegal but techincally allowed (with a warning) to render the same space in multiple places on the tree (as do some self-containing grid tests). These spaces will not support timer events, invalidation (because they don't know all of their parents), and must have the same size and map everywhere.
Β³ Draw block and map are cached internally, so space's draw
function is only called when it's invalid. Invalidation is triggered by a change of it's facet (not a deep change!), and results in next timer event rendering changed parts of the tree.
β΄ VID/S remembers style definition literally and then re-inserts it every time style is instantiated. This is more similar to how macros work than how VID works. All data after style: name
and up to the end of the style definition is copied deeply before reinsertion, ensuring no pane or reactive relation sharing.
β΅ It's possible to write a VID-like layout function, but most likely it won't be able to react to resizes in a meaningful way.
βΆ Making all spaces reactive would immensely slow down their operation as there can easily be tens to hundreds of thousands present at the same time. Spaces however employ their own reactivity implementation to track facet changes.
The following VID/S styles are currently supported for truly high level experience (more to come later):
Style | Description | Based on template | Datatypes accepted | Flags supported |
---|---|---|---|---|
hlist |
horizontal list of spaces | list | block! as content | tight |
vlist |
vertical list of spaces | list | block! as content | tight |
row |
horizontal tube of spaces | tube | block! as content | tight left center right top middle bottom |
column |
vertical tube of spaces | tube | block! as content | tight left center right top middle bottom |
list-view |
scrollable vertical list of data | list-view | tight left center right top middle bottom selectable multi-selectable | |
box |
borderless aligning box | box | block! as content | left center right top middle bottom |
cell |
bordered aligning box | cell | block! as content | left center right top middle bottom |
grid |
grid of spaces | grid | pair! as bounds, block! as content |
tight |
text |
single-line text | text | string! as text | bold italic underline strike ellipsize |
paragraph |
multi-line wrapped text | paragraph | string! as text | bold italic underline strike ellipsize |
label |
1- to 3-line unwrapped text with sigil | label | string! as text, char! or image! as image |
bold italic underline |
field |
single-line editable text | field | string! as text | bold italic underline strike |
link |
multi-line clickable wrapped text | link | string! or url! as text, block! as command |
|
data-clickable |
borderless clickable area | button | string! or image! as data, block! as command |
bold italic underline strike ellipsize (for text data only) |
button |
bordered clickable area | button | string! or image! as data, block! as command |
bold italic underline strike ellipsize (for text data only) |
slider |
adjustable parameter | slider | float! or percent! as offset | |
timer |
invisible zero-sized time events receiver | timer | integer!, float! or time! as rate, block! as on-time actor body |
|
<-> aka stretch |
invisible elastic filler space | stretch |
All supported space templates can be used in VID/S, but without auto-facet magic, flags, or user-friendly default settings.
VID/S is handled by lay-out-vids
function which takes a VID/S block and returns a block of space objects. Example:
>> spaces: lay-out-vids [text "foo" vlist [] button "bar"]
>> ?? spaces
spaces: [text:0x0 list:0x0 button:0x0] ;) custom mold implementation shortens objects to their type and size signature
>> ? spaces/1
SPACES/1 is an object! with the following words and values:
on-change* function! [word [any-word!] old [any-type!] new [any-type!]]
type word! text
size pair! 0x0
parent none! none
draw function! [/on canvas [pair!] fill-x [logic!] fill-y [logic!]]
cache block! length: 2 [size layout]
cached block! length: 0 index: 3 []
limits none! none
text string! "foo"
flags block! length: 0 []
font none! none
color none! none
margin integer! 0
weight integer! 0
layout none! none
Upon host
initialization, this list is rendered into Draw code.
VID/S can contain the following kinds of expressions:
This results in a new space created and returned.
Syntax: <bound-name>: <style-name> <modifiers...>
style-name
can be any of:- styles previously defined using style definition
- styles predefined by VID/S (see
? spaces/VID/styles
output) - raw template names (see
? spaces/templates
output), but these do not support auto-facets
bound-name
is a set-word that will refer to the created space object, and it makes the space reactivemodifiers
is an optional unordered set of any number of parameters described below
Example:
view [host [
label #"βΊ" "some text" underline teal
]]
Important quote from Quickstart:
Standard VID starts after
view [
and is used to describe faces layout. Afterhost [
VID ends and specialized VID/S begins.
TIP | Examples in this document can be run:
|
---|
This creates new VID/S styles, that are valid until return of lay-out-vids
. VID/S style is a deep copy of a part of VID/S layout and is inserted literally on every instantiation, thus helping avoid repetition.
Syntax: style <new-style-name>: <style-name> <modifiers...>
style-instantiation
is described abovenew-style-name
is a set-word that will be used asstyle-name
in the subsequent style instantiationsmodifiers
is an optional set of any number of parameters described below
Example:
view [host [
style big-button: button 200
vlist [big-button "abc" big-button "def"]
]]
All set-words within the style definition are bound to an anonymous context, so complex styles can be defined with their own local widget names, and their instances will not interfere with each other.
A trickier example of a composite style with it's own actors:
hlist [
style roller: vlist [
hlist tight [
style button: button 30x30 ;) inside hlist, button is redefined
button "β΄" [c/i: max 1 c/i - 1]
c: scrollable 40 with [ ;) 'c' will be local to each roller, not shared
i: 1
hscroll/size: vscroll/size: 0x0 ;) hide scrollbars
] react [origin: 35x0 - (c/i * 30x0)] [
hlist tight [
style b: button ;) style as a shortcut for compactness
b "1" b "2" b "3" b "4"
b "5" b "6" b "7" b "8"
]
]
button "β΅" [c/i: min 8 c/i + 1]
]
button 100 "Roll" [c/i: random 8] ;) outside hlist, button is the default one again
]
roller roller roller
]
Global stylesheet can be accessed as spaces/VID/styles
. lay-out-vids
accepts a /styles sheet [map!]
parameter - a stylesheet that is used together with the global one.
Both are maps of style-name [word!] -> style-spec [block!]
.
Style specification is a block of key: value
format with the following keys currently supported:
template
(word, mandatory) - name of template (in spaces/templates map) used to construct a space object during style instantiationspec
(block) - initialization code evaluated during space object construction (bound to space)facets
(block) - block of the following pairs:datatype-name [word!] auto-facet-name [word!]
- maps specific datatypes to relevant facets of the space (auto-facets)datatype-name [word!] function [value] [...]
- more flexible, when encounters this datatype, calls the function with the value, appending it's result to thespec
(this form expects literalfunction!
value, not the wordfunction
)facet-name [word!] code [block!]
- given any word that is not a datatype name, appendscode
to thespec
(used e.g. for alignment facets)
layout
(word) - bound name of the layout function used in place oflay-out-vids
, must support/styles
refinementpayload
(block) - data to be literally inserted during style instantiation before any other processing (this is howstyle
keyword works)
NOTE | For a more radical change of look than facets allow, a new template style should be defined instead. See Styling chapter in the manual. Template style is more profound and affects every space of built upon that template, globally. |
---|
This allows one to evaluate arbitrary expression and discard it's result.
Syntax: do <red-expression>
red-expression
is a block! of Red code
Examples:
do [print "hello in the middle of layout creation!"]
do [some initialization of previously created spaces...]
Styles that don't use block value for an auto-facet will get it processed by lay-out-vids
and assigned to their content
facet (which contains inner spaces).
Only some spaces support it:
cell
andbox
may only contain a single spacehlist
,vlist
(alllist
derivatives),row
,column
(alltube
derivatives), andgrid
may contain zero or more spaces
Syntax: <content>
where content
is a block! value
Example:
view [host [
column [
row [field "a" field "b" field "c" field "d"]
cell [
grid 2x2 [
text "a" text "b" return
text "c" text "d"
]
]
]
]]
Serves as a spec for the newly created space object. Similarly to make object! spec
semantics, it may define new facets. This is the most basic and powerful way of defining facets (and the most verbose for sure ;)
Syntax: with <spec...>
spec
is a block! of Red code, in which set-words mark space's facets
Example:
view [host [
button with [data: "some text" font: make font! [size: 20] limits: none]
]]
Defines a reactive relation on a space and makes the space reactive. Reaction may refer to this space by self
or by bound name (if one is given).
Syntax:
react <relation>
react later <relation>
where:relation
is a block of arbitrary Red code (reactivity framework uses paths and get-paths as reactive sources, so these serve as triggers)later
will delay relation evaluation until next change in it's sources
Example (combining both VID and VID/S reactivity):
view/flags [
;) just `h: host` won't work since it's delayed by VID after reaction definition
host 100x100 with [set 'h self] [
cell [text react [text: form h/size]] ;) VID/S react keyword
] react [h/size: h/parent/size - 20] ;) VID react keyword
] 'resize
Defines an actor (i.e. a function that gets called when specific event happens in a space).
Syntax:
<on-event> <body>
<on-event> function <spec> <body>
<on-event> :<reference>
Where:
on-event
is any word that starts withon-
(see list of supported events)body
andspec
are blocks. In the first case, actor is defined implicitly asfunction [space path event] body
, sobody
should use these namesreference
is either a get-word or get-path referring to the actor function
In all cases, function body is bound to space object, so space/
prefix is not required to access it's facets (unless actor is shared across multiple spaces).
For on-time
event to fire, space's /rate facet should be a positive integer, float or time value.
Sets keyboard focus to the chosen space (and to host face).
Syntax: focus
Example: button "text" focus
Sets space's facet to the value of expression. Non-existing facets are silently added, so check your spelling. Facets supported by each space are listed in the Widget Reference.
Syntax: <name>= <expression>
name
is any word that ends in=
expression
is an arbitrary Red expression that is evaluated usingdo
. Tip: parens can be helpful for readability
Examples:
view [host [
label
image= "π¦"
text= rejoin [random "dinosaur was here" "^/in year " random 2000]
color= green - 20
]]
text: "no conflict between style names and their facets!"
view [host [
text text= text
]]
Why invent such an alien to Redbol world syntax?...
Short answer: for readability.
Set-words are the best candidate, but they are already being used for style and space names. One option is to replace facet= expr...
with facet: (expr)
since that won't clash with style/space naming. But it will require all expressions to be parenthesized, and extra parens are often unreadable (e.g. facet: (compose [(a + b) c (d * e)])
).
Using just words like VID does are a more of a problem than a solution in a system where each VID/S style defines its own set of supported 'keywords'. E.g. is text
a facet, or a style name to instantiate, or is it a value to fetch? Example above illustrates just that.
I have considered using refinement and issue datatypes for facets:
/text "text"
#text "text"
But ultimately I find it less readable than:
text="text"
text="text"
locks the meaning for the brain, while the second leaves it up to guessing, slowing down our code interpretation.
So while it's a weird variant of set-word, I still find it a lesser evil than the other options listed.
Some datatypes are automatically recognized by certain VID/S styles and are assigned to a corresponding facet.
Syntax: <value>
value
is a value of one of the recognized datatypes
Each style has it's own set of supported datatypes. Currently these are:
Style | Datatypes | Target facets |
---|---|---|
label | image! char! string! |
image image text |
link | string! url! block! |
text text command |
text | string! | text |
paragraph | string! | text |
field | string! | text |
button | string! block! |
data command |
timer | integer! float! time! block! |
rate rate rate actors/on-time |
grid | pair! | bounds |
Example:
view [host [
vlist [
label
#"π΄" ;) char! is auto assigned to /image facet (as sigil)
"Wild palms" ;) string! is auto assigned to /text facet
link
"Open in browser π" ;) string! is auto assigned to /text facet
[browse https://www.imdb.com/title/tt0106175] ;) block! is auto assigned to /command facet
]
]]
Flags are words that when present, modify the appearance of the space.
Syntax: <flag>
where flag
is a word
Each style has it's own set of supported flags. Currently these are:
left
,center
,right
- control horizontal alignment of containers (row, column, box, cell)top
,middle
,bottom
- control vertical alignment of containers (row, column, box, cell)tight
- sets container spacing and margin to zero (hlist, vlist, row, column, list-view, grid)bold
,italic
,underline
- control font properties of text (text, paragraph, link, field)ellipsize
- controls automatic ellipsization (text, paragraph, link)
Examples:
view [host [
column 200x200 bottom right [
cell [text "π"]
paragraph bold "Ocean is more ancient than the mountains, and freighted with the memories and the dreams of Time."
text italic "-- H. P. Lovecraft"
]
]]
view [host [
row [
box cyan [t: paragraph blue ellipsize "Maecenas ut nulla faucibus, ultrices lectus sed, ullamcorper neque. Integer consectetur eu ipsum quis aliquet. In hac habitasse platea dictumst. Maecenas sodales vel diam vitae hendrerit."]
box pink [text ellipsize text= t/text]
]
]]
Sets color
facet of the new space. Not all spaces support it: color
should be used by the stylesheet to have effect.
Syntax: <color>
where color
can be:
- a tuple! value
- a word! referring to a tuple! value
- an issue! of format accepted by
hex-to-rgb
function
Example:
view [host 100x100 [
box color= sky * 120% [
vlist [
text "green" 0.200.0
text "magenta" magenta
text "orange" #f80
]
]
]]
Each space supports /limits facet which may define it's minimum and maximum size. It can take one of the following values:
none
- no limit defined (0x0 to infinity), equivalent tonone .. none
- a
range!
object with /min and /max fields, each can be:none
- no limit defined, equivalent to0x0
for /min andinfxinf
for /max (infxinf
is a specialpair!
value defined by spaces)- a
pair!
value - defines 2D limit - an
integer!
value - defines width limit, height stays unconstrained
Syntax:
<limit-value>
or<range-expression>
Where:
limit-value
- aninteger!
orpair!
value, which sets both low and high limits, fixing the sizerange-expression
- is a Red expression of the form<limit1> .. <limit2>
that constructs a newrange!
object:limit1
can only be a single token (expressions should be wrapped in parentheses), otherwise VID/S cannot reliably detect the range expressionlimit2
can be any expression, parenthesized or not, but keep in mind that..
is an operator, so it will take precedence over possible following operators- both limits should evaluate to a value supported by the /limits facet
Examples:
view/flags [
host 200x40 [
row white [
text "left" red ;) text does not stretch by default
<-> 0 .. 200 ;) this area will stretch up to 200 px but no more (<-> is a template name)
text "right" green
]
] react [face/size: face/parent/size - 20]
] 'resize
view/flags [
host 200x70 [
row white [
cell red 50x50 ;) fixed size
cell yellow 50 .. 100 ;) will fill the row height defined by the highest cell
cell green none .. none ;) will fill the rest
]
] react [face/size: face/parent/size - 20]
] 'resize
Spaces can define their weight
facet to determine relative sizing in containers. Some templates set it to 1
(the default). row
& column
in particular make great use of it. 0
or none
values disable extension.
Example:
view/flags [
host 100x100 [
row [
cell blue ;) uses default weight = 1
cell green 40 .. 100 weight= 0.5 ;) also has size constraints, receives 1/2 extension
cell red weight= 3 ;) receives triple extension
]
] react [face/size: face/parent/size - 20]
] 'resize
grid
style extends it's pane block with more keywords: at
and return
.
Normally, each new instantiated space in grid's pane just increments column index by one:
grid [text "a" text "b" text "c"]
will place text
s into cells 1x1, 2x1 and 3x1 respectively, regardless of declared grid bounds.
The following additional syntax allows one to control the cell coordinate:
return
moves subsequent space into the first column on the next rowat <expression>
evaluates the expression that follows it and uses it's result:- if result is a
pair!
, it moves subsequent space into given cell coordinate - if result is a
range!
object, it works as above, but also tells this cell to span the given range
- if result is a
Example:
view [host [
grid 5x5 widths= #[default 50] heights= #[default 50] [
cell [text "a"] cell [text "b"] return
text "c" text "d"
at 4x4 text "e" text "f"
at 4x5 cell [text "g"] cell [text "h"]
at 2x3 .. 4x3 cell [text "i"]
]
]]
Every space can have a hint (tooltip) shown when it's hovered over.
To have a hint it should set it's /hint
facet to a string.
Example:
view [host [
row [
button "OK" hint= "Destroy all files on my HDD"
button "Cancel" hint= "I prefer to have my files kept"
]
]]
Warning: tablet users won't see it! Mind your audience.
Read this only if you want popup menus.
Only single-level popup menus are supported currently.
Menus are specified in the menu
facet (menu= [...]
in VID/S). Any space can have it. Triggered by right-click (so they require a pointing device to work).
Menu specification is a block: [opt any flag any menu-item]
It starts with optional flags. Currently supported are:
radial
- usesring
layout to arrange menu items, instead of the defaultvlist
round
- considers menu items round, not rectangular; only has effect together withradial
flag
Each menu item is one or more values to display, followed by a paren!
with code to evaluate when this item is chosen:
data data ... (code)
Item data can contain: char!
, string!
, logic!
and image!
values, as well as object!
s referring to existing spaces, or word!
s referring to template names. It is laid out using horizontal tube
layout (aka row
). When aligning separator (stretch
aka <->
) is missing in the row, one is automatically added after first textual space, so rows like "Find..." "Ctrl+F"
become "Find..." <-> "Ctrl+F"
and get aligned properly (name to the left, key to the right).
Example from popups-test.red
:
menu: reshape [
;) note usage of logic values for sigils:
"Approve the course" #(true) (print "On our way")
"Alter the course" #(false) (
rocket/angle: random 360
invalidate rocket
print "Adjusting..."
)
;) note that sigil will be right-aligned because `<->` will be inserted after first string:
"Beam me up" "π" (print "Zweeee..^/- Welcome onboard!")
;) note how `switch` space is created by `reshape` and inserted as word into the item block:
"Thrusters overload" !(r-switch: make-space 'switch [state: on]) (
r-switch/state: rocket/burn?: not rocket/burn?
print pick ["Thrusters at maximum" "Keeping quiet"] rocket/burn?
)
]