From HaskellWiki
Jump to navigation Jump to search


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.

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.

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.

Use of accessors

Widgets only edit a model, and have no additional state of their own. This means that the entire widget state, including its GUI state is stored within the model. A widget is typically created with an accessor (See Data.Accessor) to the model, that can pull the exact information it needs from the entire model.

This model makes no distinction between the importance of a cursor, and of the text that a TextEdit edits, for example. The accessor given to the TextEdit widget, however, allows complete control of exactly where the text and cursor are stored within the model. One can store important text in an important part of the data structure, while placing the cursor in some GUI-state part of the model.

Transactional updates

This model is *transactional*, because a widget can only return a whole new model at once. For example, a TextEdit must update both the text and cursor at the same time. The widget user can of course reject any such update which would reject the entire transaction, without accidentally rejecting a text edit but letting the cursor move outside valid range.