1.1 A purely functional widget set
A LUI widget has a very simple pure semantic model. It is a function from a model that it is editing to an image, size, and mapping of event groups to documentation and new models.
type Widget model = model -> (Image, Size, Map EventGroup (Documentation, Event -> model))
An Image is semantically an "infinite mapping of coordinates to colors", Size is the actual part of the infinite Image that the widget resides in.
The Map documents what each EventGroup does, additionally providing a function to map specific Event values to their handling, which is limited to returning new states of the model.
LUI only has keyboard support as of yet.
1.2 Widgets currently exported by LUI
- Grid -- places child widgets in a grid, and allows moving the keyboard focus (selected child).
- Box -- Horizontal/Vertical boxes are just specializations of Grids to a size of 1 in one of the dimensions.
- TextEdit -- A standard line text editor
- TextView -- A simple label, cannot be edited
- Space -- A simple spacer widget
- FocusDelegator -- Wrap your widgets with this widget, and the user will be able to select the widget itself (in which state the widget does not have focus, but is selected).
- Adapter -- Allows adapting an existing widget's model (with a Data.Accessor), image or size.
- KeysTable -- Given a map of event handlers, can display the documentation of the event groups to the user.
- Scroll -- A sized "scrolling window" into a larger widget. No scroll bars yet...
- Unfocusable -- Prevent focus from going into the child widget, but still display the child widget.
2 Focus delegation
The top-level widget always has focus. It may choose to pass its focus to a child widget, if it has any. For example, a Grid widget will pass its focus to the selected child. A FocusDelegator will pass its focus to its selected child if it is in child mode, and stop doing so when going to self-mode.
To implement focus delegation, widgets simply merge the child widget's event map with their own event map however they see fit.
A navigational widget (one that implements some notion of a "cursor") will typically prefer its children key bindings over its own. Unless overridden by a child widget's key mapping, it will merge its event map into the child's. When its cursor "hits the end", the widget stops mapping the keys that no longer have meaning, and thereby exposes any parent widget mapping.
The above trick allows a hierarchy of navigational widgets to exist, allowing the user to use the same keys to go in a general direction. In practice, the user is sending navigation keys to different widgets at different times, but the experience the user gets is that of moving a global cursor.