Events
To react to user events in the graphical user interface, GTK+ lets you connect callbacks to signals. This package takes a more declarative approach, and talks about events rather than signals. Widgets emit event values, and these values can be mapped and transformed into other values as the event propagates up the tree of widgets.
Pure Event Handlers
When working with GTK+ widgets, we transform signals into event values
using event handlers. Much like properties, event
handlers for signals are declared in the attributes list, using the
on
function.
data ButtonEvent = ButtonClicked counterButton :: Widget ButtonEvent counterButton = widget Button [ #label := "Click me!" , on #clicked ButtonClicked ]
Some signals in GTK+ carry extra information, supplied as extra parameters to signal callbacks. This is supported in gi-gtk-declarative as well, by passing those parameters to the event handler function.
Event Handlers with Arguments
Looking at WidgetDirectionChangedCallback
in gi-gtk, the callback
type for the direction-changed
signal, we see that it's a type
alias:
type WidgetDirectionChangedCallback = TextDirection -> IO Bool
In gi-gtk-declarative, when using a pure event handler, the type of
the event handler will be TextDirection -> event
, where event
is
the event data type of the widget. In the following example we declare
a button and emit an event when it's text direction changes. We map
the GTK-specific text direction value to our own type.
-- A custom type for text direction data Dir = LTR | RTL -- Our event data type data MyEvent = TextDirectionSet Dir | TextDirectionCleared textDirAwareEntry :: Widget MyEvent textDirAwareEntry = widget Entry [on #directionChanged toEvent] where -- Map GTK 'TextDirection' values to our event type: toEvent TextDirectionLtr = TextDirectionSet LTR toEvent TextDirectionRtl = TextDirectionSet RTL toEvent _ = TextDirectionCleared
Impure Event Handlers
When attaching an event handler to a widget signal, it's often
necessary to query the widget for its current property values.
Getting properties is not pure, so we need event handlers in IO
.
Using the onM
function instead of on
, our event handler returns
the event as IO event
. Also, the handler will take the widget itself
as an extra argument.
In the following example we attach an event handler to the color-set
signal. The event handler uses getColorButtonRgba
to get the current
RGBA
value from the ColorButton
widget, and maps the
ColorChanged
constructor over the IO action.
data ColorEvent = ColorChanged (Maybe RGBA) colorButton :: RGBA -> Widget ColorEvent colorButton color = widget ColorButton [ #title := "Selected color" , #rgba := color , onM #colorSet toColorEvent ] where toColorEvent :: ColorButton -> IO ColorEvent toColorEvent w = ColorChanged <$> getColorButtonRgba w
In case the underlying signal callback type has extra arguments, the impure event handler will be a function of those arguments and the widget as the last argument. The translation can be described like this:
type SignalCallback = arg1 -> arg2 -> ... -> argN -> IO () type ImpureEventHandler = arg1 -> arg2 -> ... -> argN -> widget -> IO event
Signal Handler Return Values
Some signal callbacks in GTK+ have return values other than ()
. It's
common that callbacks return a Bool
value, determining whether to
propagate the event further or not. One example is
WidgetFocusCallback
, the callback for the focus
signal:
type WidgetFocusCallback = DirectionType -> IO Bool
In gi-gtk-declarative, the return value type of the event handler will
be a tuple of the callback return value and the event to emit. In the
case of the focus
signal, a pure event handler type would be
DirectionType -> (Bool, event)
. As described above, impure event
handlers return IO actions, and in the case of the focus
signal the
type would be DirectionType -> IO (Bool, event)
.
The translation to pure and impure event handlers can be described like this:
type SignalCallback = arg1 -> arg2 -> ... -> argN -> IO a -- where 'a' is not '()' type PureEventHandler = arg1 -> arg2 -> ... -> argN -> (a, event) type ImpureEventHandler = arg1 -> arg2 -> ... -> argN -> widget -- also including the widget! -> IO (a, event)
Functors
Widgets that emit events have Functor
instances, meaning that you
can use regular fmap
and friends to map event values using pure
functions.
As an example, say that we have another library provide a
clickyButton
that emits ButtonEvent
s.
data ButtonEvent = ButtonClicked clickyButton :: Text -> Widget ButtonEvent
In our own widget declaration, we can use the clicky button to
increment or decrement some counter, by mapping the ButtonClicked
events to values of type MyEvent
.
import Data.Functor (($>)) data MyEvent = Incr | Decr incrDecrButtons :: Widget MyEvent incrDecrButtons = container Box [#orientation := OrientationHorizontal] [ clickyButton "-1" $> Decr , clickyButton "+1" $> Incr ]
Note
(a $> b)
is equivalent to (const b <$> a)
.