vty-ui-1.7: doc/ch2/focus_groups.tex
\section{Focus Groups and Focus Changes}
\label{sec:focus}
Graphical interfaces allow the user to change focus between all of the
primary interface input elements, usually with the Tab key. The same
is true in \vtyui, except that because any widget can accept events --
and because you decide which widgets are ``focusable'' -- the library
cannot automatically determine which widgets should get the focus, or
the order in which focus should be received. As a result,
\vtyui\ provides a type called a "focus group."
A focus group is just an ordered sequence of widgets that should get the
user's focus as the Tab key is pressed. Widgets receive focus in the
order in which they are added to the group, and the first widget to be
added automatically gets the focus when it is added.
Creating a focus group is simple:
\begin{haskellcode}
fg <- newFocusGroup
\end{haskellcode}
Adding widgets to focus groups is also straightforward:
\begin{haskellcode}
w <- someWidget
addToFocusGroup fg w
\end{haskellcode}
A widget's ``focused behavior'' depends entirely on the widget's
implementation. Some widgets, when focused, provide a text cursor;
others merely change foreground and background color. In any case,
the widgets that the user can interact with should be in the
interface's focus group.
Once widgets are added to the focus group, you won't have to manage
anything else; the Tab key event is intercepted by the \fw{FocusGroup}
itself, and user input events are passed to the focused widget until
the focus is changed.
If, for some reason, you would like to be notified when a widget
receives or loses focus, you may register event handlers for these
events on any widget:
\begin{haskellcode}
w <- someWidget
w `onGainFocus` \this -> ...
w `onLoseFocus` \this -> ...
\end{haskellcode}
In both cases above, the \fw{this} parameter to each event handler is
just the widget to which the event handler is being attached (in this
case, \fw{w}). Many event handlers follow this pattern.
\subsection{Top-Level Key Event Handlers}
All user input is handled via a \fw{FocusGroup}; the focus state of
the group indicates which widget will receive user input events.
However, \fw{FocusGroup}s are widgets, too! Although they cannot be
rendered, they support the same key handler interface as other
widgets. This is how we create "top-level" key event handlers for the
entire interface. For example, if you want to register a handler for
a "quit" key such as \fw{'q'}, the focus group itself is where this
key event handler belongs. This is because focus groups always try to
handle key events first, and only pass those events onto the focused
widget if the \fw{FocusGroup} has no matching handler.
\begin{haskellcode}
fg <- newFocusGroup
fg `onKeyPressed` \_ key _ ->
if key == KASCII 'q' then
exitSuccess else return False
\end{haskellcode}
\subsection{Container Widgets and Input Events}
\label{sec:containers_and_input}
Most of the time you will probably end up adding key event handlers
directly to interactive widgets, but it may be convenient to wrap
those widgets in containers that affect their behavior. For example,
in the demonstration in Section \ref{sec:gettingStarted}, we used then
\fw{centered} function to center an edit widget. The result was a
\fw{Centered} widget, which is one of the many built-in container
widget types. This type of widget ``relays'' user input events and
focus events to the widget it contains. This means you can add key
and focus event handlers to the \fw{Centered} widget and they will be
passed on to the child widget for handling. Most container widgets
are implemented this way; when in doubt about event relaying behavior,
consult the API documentation. Relaying of events is accomplished
with the following functions, defined in the \fw{Core} module:
\begin{itemize}
\item \fw{relayFocusEvents} -- relays focus events from one widget to
another. For example: \fw{wRef `relayFocusEvents` someWidget}.
When \fw{wRef} becomes focused, it will focus \fw{someWidget}.
\item \fw{relayKeyEvents} -- relays keyboard input events from one
widget to another. For example: \fw{wRef `relayKeyEvents`
someWidget}. When \fw{wRef} becomes unfocused, it will unfocus
\fw{someWidget}.
\end{itemize}
As we saw above, only focused widgets will ever be asked to process
input events; this means that if you add event handlers to a container
such as \fw{Centered}, you'll need to add that widget -- not its child
-- to the \fw{FocusGroup}.
You might wonder why this is useful. Consider a situation in which
you want to add some padding to an input widget, such as an \fw{Edit}
widget, but when the \fw{Edit} widget is focused you want to highlight
the padding too, to make them appear as a single widget. Since
padding widgets (see Section \ref{sec:padding}) relay events to their
children, you could focus the padding widget, and the edit widget would
automatically receive the focus as well as user input events. This
kind of focus and event ``inheritance'' makes it possible to create
new, composite widgets in a flexible way, while getting the desired
visual results.
\subsection{Merging Focus Groups}
\label{sec:merging_focus_groups}
Some widgets, such as the ``dialog'' widget (\fw{Dialog}, see Section
\ref{sec:dialogs}), are composed of a number of input widgets already;
widgets like \fw{Dialog} must create their own \fw{Focus\-Group}s to
provide coherent focus behavior, and they will return them to you when
they are created. In order to integrate these focus groups into your
application, you must merge them with your own focus group.
For example, consider the ``directory browser'' widget
(\fw{DirBrowser}, see Section \ref{sec:dirbrowser}). You might want
to place this alongside other widgets that should also accept input.
When you create the \fw{DirBrowser} widget, you will get a reference
to the widget and a reference to its \fw{FocusGroup}:
\begin{haskellcode}
(browser, fg1) <- newDirBrowser defaultBrowserSkin
fg2 <- newFocusGroup
-- Add my own widgets to fg2
merged <- mergeFocusGroups fg1 fg2
\end{haskellcode}
The \fw{mergeFocusGroups} function will merge the two focus groups and
preserve the order of the widgets, such that widgets in the first
group will come before widgets in the second group in the new group's
focus ordering. The merged group should then be passed to the rest of
the setup process that we introduced in Section
\ref{sec:gettingStarted}; we'll go into more detail on that in the
next section.