vty-ui-1.1: doc/ch3/implementing_event_handlers.tex
\section{Handling Events}
\label{sec:event_handlers}
An interface is truly interactive only if we can express the
relationship between various events in the interface. User input and
network events may affect the user interface, but we also need to be
define how the interface components interact with each other.
\vtyui\ provides a mechanism to address this called the \fw{Handlers}
type, defined in the \fw{Events} module.
For any given widget type, we must decide what events can occur as a
result of the widget's state change. For each type of event, we must
decide what sort of data we should pass to handlers of this event so
they can take an appropriate action.
Imagine that you've implemented a ``temperature monitor'' widget, and
you want to be notified whenever the temperature changes so you can
update other parts of your interface. In that case, the event data is
a type containing the new temperature:
\begin{haskellcode}
data TemperatureEvent = Temp Int
\end{haskellcode}
In your widget type definition, you'll need a place to store the event
handlers for this temperature change event:
\begin{haskellcode}
data TempMonitor =
TempMonitor { tempChangeHandlers :: Handlers TemperatureEvent
}
\end{haskellcode}
Notice that we use the event type as the type parameter to
\fw{Handlers}; this indicates that we want to store a collection of
handler functions which take an argument of type
\fw{TemperatureEvent}. The \fw{Handlers a} type is just an alias for
\fw{IORef [a -> IO ()]}.
Once we've defined our storage type, we need to update our widget
constructor to construct a \fw{Handlers} list:
\begin{haskellcode}
newTempMonitor :: IO (Widget TempMonitor)
newTempMonitor = do
handlers <- newHandlers
wRef <- newWidget $ \w ->
w { state = TempMonitor { tempChangeHandlers = handlers
}
}
return wRef
\end{haskellcode}
Now we have a place to store the handlers, a model for the event data
itself, and an updated constructor. Next, we need a nice API to
register new event handlers. The \fw{vty-ui} convention is to use
functions prefixed with ``on'', such as \fw{onGainFocus} and
\fw{onActivate}. This convention makes it easy to write readable
infix event handler registration functions. In the temperature
monitor case, we might write something like this:
\begin{haskellcode}
onTemperatureChange :: Widget TempMonitor
-> (TemperatureEvent -> IO ())
-> IO ()
onTemperatureChange wRef handler =
addHandler (tempChangeHandlers <~~) wRef handler
\end{haskellcode}
We've introduced a new operator here, \fw{<\string~\string~}. This
operator takes any \fw{Widget a} and a function on its state type,
\fw{a -> b}, and runs the function and returns the value, \fw{b},
inside calling monad. \fw{addHandler} needs a value of type
\fw{Handlers TemperatureEvent}, and to get that we must use
\fw{<\string~\string~}.
The \fw{addHandler} function takes a \fw{Handlers a} and a handler of
type \fw{a -> IO ()} and adds it to the \fw{Handlers} list.
Here is a bogus but valid demonstration of this new function:
\begin{haskellcode}
let maxTemp = 100
t <- newTempMonitor
t `onTemperatureChange` \(Temp newTemp) ->
when (newTemp > maxTemp) $ error "It's too hot!"
\end{haskellcode}
The last thing it do is to actually ``fire'' the event that these
handlers will handle; assuming the monitor widget has a
\fw{setTemperature} function and some internal state to store the
temperature, that function would create the \fw{TemperatureEvent} and
invoke the handlers as follows:
\begin{haskellcode}
setTemperature :: Widget TempMonitor -> Int -> IO ()
setTemperature wRef newTemp = do
-- Set the internal widget state.
-- ...
-- Then invoke the handlers:
fireEvent wRef (tempChangeHandlers <~~) (TemperatureEvent newTemp)
\end{haskellcode}
Just as with \fw{addHandler}, we pass a handler list lookup function
to \fw{fireEvent}. We also pass it an event value which will be
passed to all of the registered handler functions.
The functions \fw{newHandlers}, \fw{addHandler}, and \fw{fireEvent}
are defined along with the \fw{Handlers} type in the \fw{Events}
module. The widget state projection function \fw{<\string~\string~}
is defined in the \fw{Core} module along with its \fw{WidgetImpl}
state projection counterpart, \fw{<\string~}.