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 Dynamics 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.