0.0.0

About Alloy

Alloy is a user interface toolkit. It is defined through a set of protocols that allow for a clear interface, as well as a standardised way to integrate Alloy into a target backend.

The project is currently still in its design phase and large parts of it may change. Please wait warmly.

Examples

A set of simple examples for Alloy can be found in the examples directory of the source tree.

Helping Out

If you are looking for tasks to help Alloy along, please see the various todo comments in the code base, open issue tickets on GitHub, and the TODO file.

Alloy Protocols

Alloy is structured as a family of protocols. This allows it to be very flexible, and allows you, the user, to put together the system in a way that fits your needs. The Alloy project (henceforth "the project"), consists of the following set of protocols:

The documentation in this document will focus only on the Core protocol. The Core protocol defines the fundamentals of Alloy, while the other protocols focus on extensions built around it. Note that the project also contains implementations of these protocols, not just the protocol definitions themselves.

The order of the protocols as follows is intended to give a clear understanding of each, only introducing further protocols if the ones they depend on have already been explained. If you need to jump to a specific section, please use the index above.

Units

Whenever we deal with real-world measurements we need to talk about units. In the case of a UI toolkit we are concerned with distances. Alloy offers a unit type that encapsulates a numeric value and allows us to reason about various measurements. In the base protocol there are two absolute units and five relative units. Absolute in this context means that the unit can be translated to device units (typically "pixels") no matter the context it is used in. Relative in turn means that the device unit size is dependent on the context in which the unit is used in. The available units are as follows:

  • px
    A direct representation of a number of device pixels. Note that this may still be subject to reinterpretation by the underlying rendering backend. However, as this is the base unit in Alloy, all other units will be subject to the same backend scaling factor in the end.

  • cm
    A representation of real-world centimetres. This should allow measuring things that correspond to an actual real-world extent. However, this translation depends on user-supplied data, see dots-per-cm.

  • un
    The standard unit in Alloy. uns are scaled relative to the user interface's target resolution and current, actual resolution. This allows the interface to scale up and down dynamically depending on the current resolution and preserve the layout. It is recommended that you use uns wherever possible. For the scaling factors involved in computing pixels from a un, see base-scale and resolution-scale.

  • vw & vh
    A fraction of the total view width or height. The view is the total visible area in which the renderer can operate, which typically either corresponds to the virtual screen size, the monitor resolution, or a single window.

  • pw & ph
    A fraction of the parent width or height. The "parent" is a dynamically determined layout element with a logical extent. See Layout.

You can convert between units by simply passing the unit to the constructor of another, or compute directly with units by using one of the many math functions defined for units: u+ u* u- u/ umax umin u= u/= u< u> u<= u>=.

Often when needing to compute with units, the unit needs to be converted into some numerical value. To do so, use to-px, which will return an absolute pixel representation of the unit. Beware however that this is subject to the current parent, and conversion without an active parent will signal an error. The unit parent should be bound dynamically with with-unit-parent.

Units are immutable and are cached or constructed at compile time wherever possible. It is safe to dump them to FASLs, too. While two units of the same type and with the same value may be eq, this is not guaranteed. To ensure unit size equality, use u=.

Geometry

While units give us the tool to denote measurements, the geometry protocol gives access to a set of tools to describe and operate on geometric data. All of the geometry in Alloy is based on a two dimensional Cartesian coordinate system. All of the measurements in the geometrical structures are expressed in terms of unit instances. Specifically, the following structures are available:

  • point
    A singular position in space, denoted by x and y.

  • size
    The span of a construct in space, denoted by w and h.

  • extent
    A delimited extent in space, denoted by x, y, w and h.

  • margins
    The offset from the borders of a surrounding extent, denoted by l, u, r, and b. Positive measurements decrease the extent, negative measurements increase the extent.

The default constructors of these structures take unit instances, or a real number that is interpreted as a un unit. There are alternate convenience constructors that create the structures from device pixel units. These constructors are simply prefixed by px- (px-extent, etc). In the other direction, convenience accessors for device pixels of all the coordinates are also available with a px prefix (pxx, etc).

Just like units, geometrical constructs are immutable and may be constructed at compile time, or emitted into FASLs. To compare them, you should use the respective type's comparison function (extent=, etc).

A couple of extra functions exist for convenience purposes, such as destructure-margins and destructure-extent to easily deal with all of the fields, contained-p to check for inclusion, overlapping-p to check for intersection, extent-intersection to compute the intersection, and ensure-extent to coerce any structure into an extent.

Elements and Containers

In order to abstract away a number of traversal operations, Alloy offers the basic element and container classes. A container contains a number of elements and acts similar to a sequence. Whenever a hierarchy is composed in Alloy, it is made up of elements and containers. The following operations are defined on containers:

  • enter
    Enters a new element into the container. Where and how the element is inserted is up to the container. A container may specify additional keyword arguments that influence the element's positioning or other metadata the container might have.

  • leave
    Removes the element from the container.

  • update
    Changes the metadata of the element and possibly its position within the container. A container may specify keyword arguments that let the user change the element's positioning or other metadata.

  • element-count
    Returns the number of elements currently contained in the container.

  • elements
    Returns a sequence of all the elements contained in the container. You may not modify this sequence.

  • element-index
    Returns the current index of the element within the container. This index may change if elements are entered, left, or updated within the container. Note that the index is not required to be numeric.

  • call-with-elements / do-elements
    Repeatedly calls the supplied function with successive elements. The start and end indices may influence the region of iterated elements. A container may ignore the start and end indices if they are not applicable.

  • clear
    Leaves all elements from the container. This requires leave to be called on every element in the container, though the order is unspecified.

Renderer

Alloy is a graphical user interface, and so rendering of the interface plays an important role. In the Core protocol, rendering is extremely simplified, in order to allow the backend the greatest amount of flexibility and control. In fact, it is so simple that components (See Components) could be just representations of widgets or controls in another UI toolkit.

The renderer protocol is based around a set of generic functions and two classes:

  • renderer
    Any rendering backend must provide a subclass of this that is responsible for visually presenting elements in some way.

  • renderable
    Any element that should be drawn must be a subclass of this. It is illegal to attempt to render objects that are not renderables unless backend explicitly allows it.

The protocol is split into two sections, the first dealing with resource allocation, and the second with the control of visualisations.

Rendering Resource Management

Before a renderable can be visualised with a renderer, the register function must be called to inform the renderer of the renderable. The user must add methods to this function for both renderables and renderers, as appropriate. Specifically, any renderable that contains child elements that should be renderable, must also call register on its child elements when it itself is registered. The register function may be called at any point.

Before any visualisation at all can be done, the allocate function must be called with the renderer. Calling this function multiple times should have no further effect. The renderer is encouraged to defer allocation of resources that pop up during register calls until this point, unless the renderer has already been allocated before. The user is encouraged to call allocate at a strategic point where it is permissible for loading pauses to occur. A renderer may signal an error of type allocation-failed if it is currently impossible for the renderer to perform rendering actions for whatever reason.

When deallocate is called the renderer should free all resources it can. This returns the renderer to the state before allocate was called for the first time, but does not influence the elements known to the renderer via register. A renderer may deallocate itself in case a critical failure occurs that prevents it from operating further.

Renderer Visualisation Control

Visualisation of elements is done via render. When render is called, the renderer should perform whatever steps necessary to render the given renderable. The behaviour is undefined if allocate was not successfully called prior to this, or the renderer was not notified of the renderable via register. The user is allowed to provide non-primary methods to further customise the rendering behaviour. The user is not allowed to provide primary methods unless the renderer protocol used specifically permits it.

During rendering the renderer must only visualise things if the region to visualise is within the visible-bounds of the renderer. These bounds can be dynamically constrained via call-with-constrained-visibility/with-constrained-visibility. Whether an extent is visible or not can be checked via extent-visible-p. Specifically, if an extent is partially visible, the renderer must only render the part of the extent that is fully within the visible bounds.

If the renderer supports partial updates, the user is encouraged to call into the rendering machinery via maybe-render instead. Unlike render, maybe-render will silently traverse the hierarchy and only invoke render on an element if the element was previously marked with mark-for-render. After render has been called on a renderable, render-needed-p will always be NIL. The user should always call mark-for-render if any property of a renderable was changed that would change its visual representation.

Focus

In Alloy there is a notion of a "focus tree" – a hierarchy of elements that designates how the focus flows between elements. Focus in this case refers to how important an element currently is. A focus-element in Alloy can have three states; NIL for no focus at all, :strong for when it is fully focused, and :weak for when it should be considered for strong focus.

Within a focus-tree there must always be exactly one element with strong focus, but there may be many elements with weak or no focus. Every element in a focus tree has exactly one parent element. For the element at the root of the focus tree, this is the element itself. Focus may flow inwards and outwards, meaning that a strongly focused element may pass the strong focus to a child element (activate), or it may pass the focus to its parent (exit).

An element may also be strongly focused directly, referred to as "focus stealing".

Within a focus-chain – an element that can contain child elements – only one of its direct children may have strong or weak focus. This is used when the focus-chain is activated, to determine which of the child elements to give strong focus to. Each child also has a direct successor and predecessor. There may be additional ties between child elements that offer more intuitive navigation, but this basic connection is always present.

Note that there is no necessary visual correspondence to the way focus moves between elements. This is important as elements may have a certain visual grouping, but the ideal way focus travels between the elements may not be directly encoded in this grouping.

An element may only be contained in one focus chain at a time. Attempting to enter an element into multiply focus chain before leaveing it will signal an error.

More specifically, within a focus-tree the following invariants must be upheld at any time:

  • There must always be exactly one strongly focused element

  • All predecessors of the strongly focused element must be weakly focused

  • An element may only be weakly focused if:

    • Its immediate predecessor is strongly focused

    • Or one of its successors is strongly focused

  • If an element is an immediate successor of another, the other element must be its focus-parent.

Events

Alloy is a retained mode toolkit where you construct an interface, which then reacts to changes in the environment. These changes are communicated via events. When an element handles an event, it can either decide to handle it and perform whatever action necessary to do so, or call decline in order to allow the event to propagate to an element that might want to handle it instead. The behaviour of this propagation is distinguished between the following two types of events:

  • direct-event
    Direct events are events without geometric information and are handled by being directed to the element that currently has strong focus, and then bubble outwards in the focus hierarchy if the handling is declined.

  • pointer-event
    Pointer events are events that have a specific associated location. They are first directed to the element with strong focus similar to direct-events, but if declined will bubble inwards from the root element until the last element that geometrically contains the point is found.

Alloy contains a variety of event classes to describe general user interface changes. These events are loosely grouped into either being specific or descriptive. Specific means that the event describes a particular hardware action directly, such as a key press. Descriptive events on the other hand may be translated from a variety of hardware actions and are used to describe a particular action in the interface, such as focusing the next element.

Descriptive events allow you to write the user interface interaction in a more action-oriented way, which allows the end-user to decide how to map physical buttons and gestures to the interactions they want. This is important for accessibility, internationalisation, and customisation.

Layout

Layouting in Alloy refers to the decisions made to determine where elements are positioned in space and how large they are. In other words, it's the mechanism to determine the extent of each element that should be rendered. Similar to focus trees, there are "layout trees" in Alloy – hierarchies of elements that govern the layouting decisions.

Every element in a layout tree has exactly one parent, with the element at the root having itself as its own parent. Every element also has a bounds that determines its axis aligned bounding box. When rendered, the visual representation of the element should not exceed this extent.

Layout decisions are primarily made by layout instances, for which Alloy specifies a protocol to communicate an agreeable layouting between elements. The actual layout decisions are left up completely to the layout implementation.

When an element's bounds change, it must call notice-size in order to notify its parent of the updated bounds. If an element decides that it needs more space, it should call notice-size to ensure a consistent layout. Typically this will result in a standard layout update being run, same as when the layout instance itself changes bounds.

When a layout's bounds change it may recompute the bounds of its direct children. It must do so in the following steps for each of its child elements:

  1. Compute the new bounds for one element.

  2. Call suggest-size with the new bounds for the element. The element must then extend, contract, or otherwise change the suggested event and return a new extent that it finds more agreeable than the last.

  3. Adjust the layouting decisions to account for the element's preferred extent.

  4. Force the final computed extent on the element by calling (setf bounds).

A layout may perform steps 2 and 3 multiple times before settling on a final extent, though it must guarantee to reach step 4 eventually. suggest-size is primarily used to handle the case of nested layouts or other kinds of elements that may need to shrink or expand to fit their contents. Whether the element's preferred extent is used at all or not however is still up to the layout.

A layout must deal in px units. The extent supplied to suggest-size and (setf bounds) must only contain px units. An element is however allowed to use other units for the extent returned from suggest-size. However, be aware that the absolute size of units depends on the currently bound unit parent (See Units). The layout must set this parent to itself when resolving units.

Some layouts may temporarily hide elements or regions from view. In order to force a region to be visible, the function ensure-visible can be used. This function will traverse upwards to ensure that every layout along the way makes the desired region visible as best possible.

An element may only be contained in one layout at a time. Attempting to enter an element into multiply layouts before leaveing it will signal an error.

UI

Since layout trees and focus trees are disjoint, there needs to be a way to tie them together, including any other global information necessary. For this, Alloy has the UI object, the main entry point once the interface has been constructed. It has a layout-tree and focus-tree, as well as provides access to the global unit scaling factors, dots-per-cm, target-resolution, resolution-scale, and base-scale.

Once you've constructed a UI instance, you should be able to add elements to its focus tree and layout tree, set the desired "native" resolution, and finally render it, handle events, or change the effective resolution with suggest-size.

If you would like to switch out the layout or focus hierarchies on the fly, you can set the root of either tree instance.

Observables

In order to allow parts of the system to react to changes that happen elsewhere, Alloy implements an observation protocol. Any object that can be observed for changes must be an observable. Observations happen based on functions to observe. When an observable function is called with an observable instance, a set of functions that observe this combination is called with the same arguments as the original function call.

An observable may either have observations fired automatically on generic functions that have been defined with define-observable or made observable with make-observable, or it may manually fire observations with notify-observers.

New observers can be added with on or observe, and managed with remove-observers and list-observers.

Data

Alloy is created around the idea that the data you present in your interface should be decoupled from the elements that present it. However, to provide standardised interfaces to the data, and to express the requirements for data structure and metadata an element might have, Alloy provides a Data protocol.

The base protocol is very light, though it is expected that elements add further constraints to the protocol in order to express their needs. The basis involves a data class, from which any data representation should inherit. Every data representation object is observable, to allow the interface to respond to changes.

In order to obtain the most appropriate data representation instance for a place, use place-data. The user is encouraged to provide additional methods on expand-place-data and especially exand-compound-place-data if they add new data representation types.

Note that as long as an object is observable, and the generic functions and observable places as required by the element's data protocol are implemented for the object, the object may be used as a data representation object directly.

Component

Representing user interactions happens through Components. Components are "leaf elements" and should not contain any further elements. Instead, if something should be made up of different interactions, it should be modelled as a combination of layouts, focus chains, and components. Every component is tied to a data instance that provides the data to visualise and the metadata to determine the interaction constraints.

Being an interactable leaf element, a component is both a layout-element, a focus-element, a renderable, and an observable. Particularly, it is possible to observe any component's focus and size changes, and react to them remotely. Specific components may offer additional interactions, though typically it is more apt to observe the changes on its data object instead.

Multiple components may share the same data instance and changes between them will update automatically. This allows representing the same information in multiple places, potentially in different ways simultaneously.

Components are typically created for a place or data instance through represent and represent-with. Alloy can try to pick the component type for a data type automatically by using T for the component type. In this case the actual component type to use is resolved via component-class-for-object.

Standard Implementations

Aside from the protocols, Core provides a set of standard implementations of the protocols that should fill a lot of the needs for an interface.

  • Components

    • Button

    • Combo

    • Icon

    • Text Input

    • Label

    • Plot

    • Progress

    • Radio

    • Scroll

    • Slider

    • Switch

  • Data Representations

    • Place Data

    • Slot Data

    • Aref Data

    • Computed Data

  • Focus Chains

    • Focus List

    • Focus Grid

  • Layouts

    • Border Layout

    • Clip View

    • Fixed Layout

    • Grid Layout

    • Grid Bag Layout

    • Linear Layouts

  • Observables

    • Observable Object

    • Observable Table

  • Structures

    • Query

    • Scroll View

    • Tab View

    • Window

Note that Core does not provide any standard implementations for renderers. Rendering is a very involved and complex process, and as such is left up to secondary systems and protocol extensions.

Observables

Data Representations

Focus Chains

Layouts

Components

Button

Combo

Icon

Text Input

Label

Plot

Progress

Radio

Scroll

Slider

Switch

Structures

Query

Scroll View

Tab View

Window

Project Systems

Aside from this Core, the project also includes several other systems that fill or extend parts of Alloy.

Support

If you'd like to support the continued development of Alloy, please consider becoming a backer on Patreon:

https://filebox.tymoon.eu//file/TWpjeU9RPT0=

System Information

0.0.0
Yukari Hafner
zlib

Definition Index