reflex-gi-gtk
This package provides the necessary plumbing to write reactive GUI
applications using GTK3+ (based on
gi-gtk) and
reflex.
While this packes should make working with gi-gtk and reflex together
much easier than using them together without any kind of glue code, it
doesn't provide a lot of abstraction on top of either GTK or
reflex. People familiar with both gi-gtk and reflex separately should
have no trouble using reflex-gi-gtk successfully to build awesome
applications.
Installation
The package can be compiled with stack
using the shipped stack.yaml
as follows:
$ stack install
The build process has mostly been tested using stack, but it should
also work using cabal-install or
other standard Cabal
-based package managers.
Quick start
To start writing your reactive GUI application using reflex-gi-gtk, you will farst want to start the reactive part of your application using runReflexGtk
, something like this:
{-# LANGUAGE OverloadedStrings, OverloadedLabels, FlexibleContexts #-}
import Control.Applicative (liftA2)
import GI.Gtk hiding (main)
import Reflex.GI.Gtk
import System.Environment
import System.Exit
main :: IO ()
main = do
argv <- (:) <$> getProgName <*> getArgs
Just gtkApplication <- applicationNew (Just "org.example.MyAwesomeApp") []
rc <- runReflexGtk gtkApplication (Just argv) $ myReactiveCode gtkApplication
case rc of
0 -> exitSuccess
n -> exitWith $ ExitFailure $ fromIntegral n
Inside the call to runReflexGtk
you can assume that GTK has been
initialized and start creating GTK widgets:
myReactiveCode :: (MonadReflexGtk t m) => Application -> m ()
myReactiveCode gtkApplication = do
window <- runGtk $ applicationWindowNew gtkApplication
box <- runGtk $ boxNew OrientationVertical 0
containerAdd window box
input1 <- runGtk entryNew
input2 <- runGtk entryNew
output <- runGtk $ labelNew Nothing
runGtk $ boxPackStart box input1 False False 0
runGtk $ boxPackStart box input2 False False 0
runGtk $ boxPackStart box output False False 0
Note that we use runGtk
to execute GTK functions instead of relying
on liftIO
. This is because GTK functions are required to be run with
the correct thread local context. reflex-gi-gtk internally uses
multiple threads in a way that makes it hard to predict in which
thread a given piece of code will run. runGtk
will ensure that the
lifted IO
action is always run in the appropriate threading context
for GTK functions.
You can also start obtaining reactive inputs from widget signals:
text1 <- dynamicOnSignal "" input1 #changed $ \fire -> labelGetText input1 >>= fire
text2 <- dynamicOnAttribute input2 #text
This creates to Dynamic
s containing texts. text1
is constructed
based on the #changed
event of the first input Entry
while text2
is bound to the attribute #text
of input2
, but overall the effect
will be the same. Both text1
and text2
will always contain the
text entered into their respective input Entry
.
Once we have obtained reactive inputs, we can of course manipulate
them just as any other reactive values:
let combinedText = liftA2 (<>) text1 text2
In the end, we can also render dynamic values to attributes of GTK
widgets:
sink output [#label :== combinedText]
Don't forget to call widgetShowAll
on your window at an appropriate
time:
_ <- gtkApplication `on` #activate $ widgetShowAll window
pure ()
When you compile and run your program above, you should see your two
input boxes and the appropriate, dynamically updated output label.
This is of course a very basic example. For more information you can
look at a more elaborate example in the example
directory or at the
haddock documentation on hackage.