AboutProjects

VID Extension Kit - Lists

Introduction

Lists are a big topic, and deserve their own document. The VID Extension Kit offers several building blocks for lists.

All tests for lists are available in the tests/list/ directory in this distribution.

List Styles

The list styles are built from several basic components as compound styles. Some are not obvious list styles, but are based on them, like the CHOICE style.

Common of all of these lists are that they are based on iterated faces. This means that one face is used to generate the whole list, usually rendering from top to bottom in one operation. It also means that the rows must be of equal height.

Components

LISTThis is the basic list style, which essentially is a list of iterated faces with predefined access possibilities for changing rendering, column/row layout, row manipulation, keyboard navigation and scrolling accessor.
CARET-LISTDerivative of LIST, which uses an alternative keyboard selection scheme.
SCROLLERDepending on the style, it may require a scroller to scroll the list horizontally or vertically.
LIST-CELLA text cell optimized for lists. If you want clickable rows and custom render functions in the list, this or one of its derivatives mentioned below should be used.
LIST-TEXT-CELLA derivative of LIST-CELL that is optimized for plain text.
LIST-IMAGE-CELLA derivative of LIST-CELL for images.
SORT-BUTTONThis is the standard sort button, which is used by DATA-LIST and is automatically placed in the header face. It is derived from BUTTON. Clicking this will begin sorting for the column below it. Any other sorted column will be unsorted.
SORT-RESET-BUTTONThis is the standard sort reset button, which presents a diamond shaped icon in the top right hand corner of the list. Clicking this button resets to use no sorting.

In the future, there will be more styles to provide direct manipulation of rows, such as buttons and inline edit fields.

Compound Styles

NAV-LISTThis is a PANEL that holds the basic words for the elements that a data-list consists of.
DATA-LISTThis uses a LIST, a vertical SCROLLER and a PANEL with SORT-BUTTON and SORT-RESET-BUTTON in it to form a regular list. It is derived from NAV-LIST.
PARAMETER-LISTThis is derives from DATA-LIST and shows a predefined setup for displaying objects in parameter or key/value form easily.
TEXT-LISTThis is a simple 1-dimensional DATA-LIST set up for MUTEX selection mode and has a scroller, but no header. It can be used for very quick display of strings or numbers in a block.
CHOICEThis uses a CARET-LIST to show the options list.

Specifying a List

There are multiple methods for specifying the structure and content of a list. Generally there are two ways:

  1. By specifying an object to SETUP, which gives you minimum flexibility, but minimum code.
  2. A Specification dialect for SETUP, if you want to specify with greater flexibility.

Specifying a list works the same across DATA-LIST and PARAMETER-LIST. If you want to work with the LIST style, no SETUP is available and you must use the face words directly.

You can also specify a data source. It can be the list itself, in which case the data is stored at FACE/DATA, or you can assign one like so:

data-block: []

And then in layout:

data-list data data-block

DATA-BLOCK is not copied.

Setup

When using SETUP, you can either use an object or a dialect block (the specification dialect) to specify the structure of the list. Every time SETUP-FACE is called, the list face is entirely re-done.

Using an Object

The object can typically be the same as the object used to specify each row in the list.

my-list-row: make object! [
    name:
    age:
    weight:
        none
]

You can then pass the object to the data list like this:

data-list setup my-list-row

The column display order will match that of the object.

Specification Dialect

The dialect is formed as a word and a value as argument, usually a block or a word. Each word will typically describe what occurs in all columns for a particular facet of the list, such as the column names or the widths.

The words are:

inputThis is a block of words, describing the columns as structured for the raw data. If this value is not stated, but output is, then INPUT will be the same as OUTPUT.
outputThis is a block of words, describing the columns as structured in the output display. If this value is not stated, but input is, then INPUT will be used as OUTPUT. If used with SUB-FACE, the output is mapped to each face in the order they appear in the SUB-FACE. Other keywords, like | (pipe char) can also be used to describe a 1 pixel gray spacer between columns.
namesThis is a block of strings, which describe the shown names in the header of the list. It must have the same order and length as OUTPUT. If this value is not stated, the words will be FORMed from OUTPUT, uppercasing the first character in the word. It is overridden, when using HEADER-FACE or SUB-FACE directly.
resize-columnThis is a single word which describes which column will be resizable. It is overridden, when using HEADER-FACE or SUB-FACE directly.
widthsThis is a block of integers describing the width of each column in pixels. The resizable column ignores this value. Note also that if you specify a table width that is much narrower than the combined width of the header, some columns will be perfectly hidden. It is overridden, when using HEADER-FACE or SUB-FACE directly.
modesThis is a block of words describing the mode of the button that sits in the header face above the column. SORT will use plain ascending/descending sorting. NO-SORT will create an inert sort button, that will do nothing when clicked. Default for all columns is SORT.
typesThis is a block of words or datatypes describing the datatype for the column. This is only used to determine which style will be used to render the column. For now string! is supported. Later image! will be used to directly render images that exist in the raw data.
adjustWhether the header and content are LEFT, CENTER or RIGHT adjusted.
select-modeThis is either MUTEX, which allows only selecting one row, PERSISTENT, which allows selecting multiple rows without using a qualifier key or MULTI (default), which allows selecting multiple rows using CTRL or SHIFT as qualifier keys. This option is not reset when SETUP-FACE is performed several times.
header-faceThis is a layout block. When used, it will replace the standard header generated by DATA-LIST. You can use any standard layout
sub-faceThis is a layout block for each row used in the layout. When using this, you can create an entirely custom layout, even with multiple lines per data row. Words in OUTPUT are distributed in the same order that the faces are described in the layout. Note that the layout cannot change height, so you cannot use variable height layouts in these types of lists.
renderThis is a function body that uses FACE and CELL as argument. The FACE is the list face inside the data list. The CELL is the cell that is currently being rendered.
follow-sizeWhether to follow scrolling of a selected row line-by-line or page-by-page. The options are LINE (default) or PAGE. This option is not reset when SETUP-FACE is performed several times.
Examples

You can specify input:

data-list setup [input [name age weight]]

You can specify output, which is useful, if you want to hide or rearrange columns:

data-list setup [input [name age weight] output [weight age]]

In OUTPUT, you can add spacers as a | (pipe char), which will produce a one pixel space between columns:

data-list setup [input [name age weight] output [weight | age]]

You can add specific titles. In cases like these, if you don't need column rearrangement or need to hide columns, you don't have to add the OUTPUT keyword, as INPUT is automatically used as OUTPUT, if nothing else is stated:

data-list setup [input [name age weight] names ["Name" Age [years]" "Weight [kg]"]]

You can specify the width of each column in pixels with the condition that the first column is resizable and therefore is ignored:

data-list setup [input [name age weight] widths [100 150 50]]

You can specify which column is resizable, when the list is resized when resizing the window in which it sits. The condition is that only one column is resizable, as the list resizing model follows that of the VID Extension Kit.

data-list setup [input [name age weight] widths [100 150 50] resize-column 'age]

If you use a plain word for age as input for resize-column, the style will try to see if the word 'age is set with a value. If not, an error is created. This counts for any keyword that requires a single value as argument.

Set a selection mode, where only one row can be selected at a time:

data-list setup [input [name age weight] select-mode 'mutex]

Set follow size to a full page, to jump one whole page in the list, when navigating with cursor keys:

data-list setup [follow-size 'page]

Perform adjustment of the text in a column:

data-list setup [input [name age weight] adjust [left center right]]

Set a column mode for a column, so that sorting is disabled for this column:

data-list setup [input [name age weight] modes [sort no-sort]]

See the file tests/lists/spec-dialect.r for an example.

You can also use words, as long as they are set and for objects, their words are used. This allows the use of object prototypes:

my-obj: make object! [a: b: c: none]

And in the layout:

data-list setup [input my-obj]

See the file tests/lists/spec-dialect-2.r for an example of this.

Content of a List

The contents of a list is usually one or several columns and rows, where each column has a title. DATA-LIST is designed to accept an input directly to display contents, so you don't have to spend time generating a specification of the content.

There are generally 3 types of data content allowed:

1-dimensional blocks

A simple 1-dimensional block of elements that are not objects are allowed, such as:

[1 2 3 4]

The list will automatically detect that there is only one column, if the first element is not a block.

You cannot use EDIT-FACE to edit this kind of block. For this, you will need to provide your own custom functions.

2-dimensional blocks

2-dimensional blocks are allowed and may contain various types, including objects:

[[1 2] [3 4] [5 6] [7 8]]

The list will automatically figure out the number of columns from the length of the first block.

You cannot use EDIT-FACE to edit this kind of block. For this, you will need to provide your own custom functions.

Object blocks

This is the recommended method for maximum flexibility as the same object can be used both for specifying the list structure and for specifying rows in the list. You can also use EDIT-FACE to edit the list contents this way. Each word in the object is designated as a column and will be used as the column name. So if you have a prototype object:

my-list-row: make object! [
    name:
    age:
    weight:
        none
]

The columns shown in the list will be:

Name Age Weight

Sorting

A list with a header can be sorted by the user, if the header face is specified. The header face usually contains SORT-BUTTONS that handle sorting for you.

See the file tests/lists/sorting.r for an example.

Caveats

Internally, the sorting mechanism uses SORT/COMPARE and is thus susceptible to the limitations it poses, such as trying to compare two different datatypes. Therefore it's usually not wise to use more than one datatype per column, if you want to sort rows.

You cannot sort multiple columns at the same time. If you select a new column to sort, the original data is resorted by that column.

You cannot yet programmatically sort.

Filter Function

The QUERY-FACE accessor can filter rows, based on an input function. The input function takes a row as an input argument and when returning TRUE, the row will be included in the list. If FALSE, it will be left out.

This is useful for filtering out data that you don't want to see, such as with a real-time string filtering function.

See the file tests/lists/filtering.r for an example.

Example

Example filtering function, which leaves out a line, if the string in F-FILTER cannot be found in any cell:

filter-row: func [row /local value] [
    if empty? value: get-face f-filter [return true]
    foreach cell row [if find cell value [return true]]
    false
]

And in the list specification the QUERY-FACE accessor uses the filter function to filter in the list per key-press:

f-filter: field on-key [query-face l-data :filter-row]
l-data: data-list data list-data

Render Function

When the list uses a RENDER-FUNC function, each cell can be evaluated for content and adjusted in appearance accordingly. Any facet of the face can be adjusted. This is used for alternate line coloring in the standard DATA-LIST.

But by setting any facet, including DRAW blocks, you can include images or complex indicators in a cell. The RENDER-FUNC function is run, every time a single cell is rendered, so speed is of importance.

See the file tests/lists/rendering.r for an example.

Cell Information

Each cell contains several bits of information to help you determine rendering:

POSThis is the physical position of the data in the output. If you are rendering the fourth cell in the third row, the value will be 3x4. Using POS/X, you can determine which column you are in. Using POS/Y, you can determine which row you are in, in the data, if you use it to pick the raw value from FACE/DATA-SORTED.
DATAThis is the raw data input to the cell. Even if there are no more rows with data to render, such as in a table with fewer rows of data than visible rows in the list, the function will still be called, and in that case, the value is NONE.

Example

A basic example here uses this method to use bold text for a column with numbers between 0 and 100 that go over the value 90:

data-list with [
    render-func: func [face cell] [
        cell/font/style: if all [cell/data cell/data > 90] ['bold]
    ]
]

Notice here that a check of CELL/DATA is done to ensure that the check can only be done on rows that contain output.

Random Colors

Another handy use of the render function is to assign a random color to a cell. Since lists are iterated faces, the entire list is always generated with all faces in one single draw operation, when calling SHOW.

If you are experiencing slowdowns, due to excessive redraws of the list, you can apply a random background color per cell to detect such excessive redraws.

data-list with [
    render-func: func [face cell] [
        cell/color: random 255.255.255
    ]
]

Data Processing

Internally, the LIST does not destroy or modify the original data, when sorting or filtering. It only does so, when using the editing functions with EDIT-FACE.

What occurs on every update, is:

  1. The original data is fed to the list
  2. The data is passed through the filter function. If none is specified, all rows pass through and an index is generated for the rows that are allowed to pass through.
  3. Then the indexes of those filtered data passes through the sorting function and the indexes are sorted appropriately to the currently set column for sorting and the direction of sorting.
  4. Then, the sorted indexes are used to generate the order of output of the data in the original input.
  5. When showing the list, this output is then used to place the correct values in the correct cells. Also the output block is moved to the current scroll position.
  6. And finally during this operation, the render function is called to make any modifications necessary to the cell, before it's displayed.
  7. Finally the face is shown and all cells are displayed.

Selection

The selected rows can be accessed through FACE/SELECTED and will be the indexes of the items in the original unsorted and unfiltered data block.

To get the content itself, use the GET-FACE function.

Selecting Rows

To select rows, use the SELECT-FACE function, which can take a number of different inputs.

Selection can take place, both on filtered and sorted data as well as the unfiltered and unsorted data. The division works like this:

Unfiltered and unsorted data happen with:

  • Numeric selection, blocks of numbers.

Filtered and sorted selection happens with:

  • Word based selection (FIRST, PREVIOUS, NEXT, LAST) * TRUE/FALSE/NONE to select all or no rows.

Examples

Selecting the sixth row; numeric selection of rows always occur on the unsorted and unfiltered data. When sorting, it will then not be the sixth row displayed, and if the row is filtered, the row is not displayed:

select-face my-list 6

Selecting a number of rows:

select-face my-list [3 4 5]

Selecting rows by function; the function must return TRUE for the row to be selected. The function input value is a row, which then usually is a block or an object:

select-face my-list func [row] [row/height > 90]

Select first filtered and sorted row:

select-face my-list 'first

Select last filtered and sorted row:

select-face my-list 'last

Select next filtered and sorted row:

select-face my-list 'next

Select previous filtered and sorted row:

select-face my-list 'previous

Select nothing:

select-face my-list none

or:

select-face my-list false

Select everything:

select-face my-list true

See the file tests/lists/selection.r for examples of selection using SELECT-FACE.

Selection Mode

When setting the selection mode in the list, you can determine the behavior of selection:

MUTEXThis selects only one row at a time, regardless of whether holding down qualifier keys or not. When using MUTEX, using GET-FACE will return the selected item directly, not wrapped in a block.
MULTIWhen clicking a row, it will be highlighted. When clicking other rows using SHIFT or CONTROL keys, those rows will be highlighted, either in sequence (SHIFT) or appended to the current selection (CTRL).
PERSISTENTWhen clicking a row it will be highlighted. When clicking another row without holding down any qualifier keys, that row will also be highlighted. Clicking a row again will remove the highlight.

When using MULTI or PERSISTENT, selecting multiple rows will be returned in a block in the order they are selected, when using GET-FACE on the list.

Example

Using the specification dialect, you can change the selection mode:

my-list: data-list setup [select-mode 'mutex]

Keyboard Navigation

When using a DATA-LIST, it is registered as a compound style and internal components cannot be navigated to directly. To use keyboard navigation, the list must be focused, i.e. be covered with the focus ring.

Any usage of the keyboard results in FACE/SELECTED being manipulated and the ON-KEY actor being run.

The LIST style offers a KEY-FACE accessor function with the following functionality:

upSelects the previous row. If no items are selected, the first row is selected. If the previous row is out of view, the list scrolls it into view. The use of the FOLLOW-SIZE keyword during setup sets whether to jump by one line or a whole page, when the list scrolls the selected row into view.
downSelects the next row. If no items are selected, the first row is selected. If the next row is out of view, the list scrolls it into view.
ctrl-upSelects a row one page up and deselects the previously selected row. One page is the number of visible rows in the list.
ctrl-downSelects a row one page down and deselects the previously selected row.
shift-upSelects the previous row and does not deselect any previous rows.
shift-downSelects the next row and does not deselect any previous rows.
ctrl-shift-upSelects the entire previous page and does not deselect any previous rows.
ctrl-shift-downSelects the entire next page and does not deselect any previous rows.
ctrl-ASelects all rows.
ctrl-shift-ADeselects all rows.

See the file tests/lists/selection.r for an example use of ON-KEY on a DATA-LIST.

Caveats

It is currently not possible to sort by keyboard.

Scrolling

When scrolling, you can use the scroll-wheel on any area inside the list, including the header. When the data-list is resized, the scroller is redragged.

Following

The internal function FOLLOW, which exists inside the list face, allows the list to scroll to a specific physical row. This is used, when needing to scroll a particular row into view, and thus is used during keyboard navigation, to automatically scroll the list, whenever the selected row moves beyond the visible part of the list.

FOLLOW can use two modes; LINE, which is default and PAGE. This can be set during setup, using the FOLLOW-SIZE keyword.

linewill simply scroll the chosen row into view near the top or bottom edge of the list, whichever is nearest.
pagewill scroll an entire page size, every time the face is moved out of view. This results in page-by-page style scrolling, when moving by keyboard.

See the tests/lists/follow-size.r example.

Example

l-data setup [follow-size 'page]

Caveats

There is currently no horizontal scroller.

Editing

Using the EDIT-FACE accessor, you can do some editing of the data block in the list. If the data block is not copied, it can be used to modify an external block.

For simplicity, editing is only available, when the data is formed as a block of objects.

The EDIT-FACE accessor takes three arguments:

FACEThe face to edit
OPThe operation to carry out, in this case ADD, EDIT or DELETE as a word
VALUEThe value to pass to the face accessor for the operation.

Edits are carried out on the selected rows in the list.

Example

Given a data-list L-DATA, we can add a row like this:

edit-face l-data 'add

Adding always happen at the end of the list.

Edit a row:

edit-face l-data 'edit

Delete a row:

edit-face l-data 'delete

Caveats

  1. Interactive inline editing is not yet possible. This will be possible, once a text-cell face for this type of editing is made available.
  2. Single-cell editing using EDIT-FACE is not yet possible.