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.
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.
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:
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.
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
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
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.
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:
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
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
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:
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-extent, etc). In the other direction, convenience accessors for device pixels of all the coordinates are also available with a
px prefix (
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 (
A couple of extra functions exist for convenience purposes, such as
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
Elements and Containers
In order to abstract away a number of traversal operations, Alloy offers the basic
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:
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.
Removes the element from the container.
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.
Returns the number of elements currently contained in the container.
Returns a sequence of all the elements contained in the container. You may not modify this sequence.
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.
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.
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:
Any rendering backend must provide a subclass of this that is responsible for visually presenting elements in some way.
The protocol is split into two sections, the first dealing with resource allocation, and the second with the control of visualisations.
Rendering Resource Management
renderable can be visualised with a
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.
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 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
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
maybe-render will silently traverse the hierarchy and only invoke
render on an element if the element was previously marked with
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.
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.
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 (
An element may also be strongly focused directly, referred to as "focus stealing". This will cause the previously strongly focused element to become weakly focused.
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.
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 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 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.
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-bounds in order to notify its parent of the updated bounds. If an element decides that it needs more space, it should call
notice-bounds 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:
Compute the new bounds for one element.
suggest-boundswith 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.
Adjust the layouting decisions to account for the element's preferred extent.
Force the final computed extent on the element by calling
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-bounds 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
(setf bounds) must only contain
px units. An element is however allowed to use other units for the extent returned from
suggest-bounds. 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.
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
focus-tree, as well as provides access to the global unit scaling factors,
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
handle events, or change the effective resolution with
If you would like to switch out the layout or focus hierarchies on the fly, you can set the
root of either tree instance.
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
New observers can be added with
observe, and managed with
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.
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
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-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
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.
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.
Aside from this Core, the project also includes several other systems that fill or extend parts of Alloy.