FFP10: Graphische Benutzeroberflächen mit GTK

Werbung
Grundlagen GUI GTK+ Glade Beispiele
Fortgeschrittene Funktionale
Programmierung
mit Haskell
Graphische Benutzeroberflächen mit GTK
Steffen Jost
LFE Theoretische Informatik, Institut für Informatik,
Ludwig-Maximilians Universität, München
21. Januar 2016
Steffen Jost
FFP
10-1
Grundlagen GUI GTK+ Glade Beispiele
Existentielle Quantifikation IORef Spracherweiterungen
Existentielle Datentypen
{-# LANGUAGE ExistentialQuantification #-}
data Obj = forall a. (Show a) => Obj a
list = [Obj 1, Obj True, Obj 3, Obj "Foo"] :: [Obj]
Spezialfall eines Typen höheren Ranges
Erlauben es, z.B. heterogene Listen über Klassen zu erstellen
Benennung
Konstruktor Obj hat ja den Typ Show a => a -> Obj
∃-Quantor taucht in einer negativer Position auf, nach den
logischen Regeln entspricht dies Quantifikation über allen:
∀x.(P(x) → Q) ≡ (∃x.P(x)) → Q
data Obj = forall a. Show a => Obj a entspricht also
data Obj = Obj (exists a. Show a => a)
kein exists in Haskell
Steffen Jost
FFP
10-2
Grundlagen GUI GTK+ Glade Beispiele
Existentielle Quantifikation IORef Spracherweiterungen
Fallstricke
data Foo = forall a
. Eq a
=> FooA a a
| forall b c . Show c => FooB b (b -> c)
good :: Foo -> String
good (FooA x y) | x==y
= "equal"
| otherwise = "not"
good (FooB z f) = show $ f z
bad (FooA x _, FooA z _) = x==z
bad (FooA x _, _
) = x
where (FooA _ y) = f
-- Problem 1
-- Problem 2
-- Problem 3
Probleme
1 Verschiedene Eq-Instanzen sind nicht miteinander vergleichbar!
2 Rückgabetyp ist nicht feststellbar – dieser ist ja durch das
Argument bestimmt im Gegensatz etwa zu undefined :: t
3 Kein Pattern-Matching solcher Datentypen in let/where,
kein deriving, kein newtype
technische Einschränkung
Steffen Jost
FFP
10-3
Grundlagen GUI GTK+ Glade Beispiele
Existentielle Quantifikation IORef Spracherweiterungen
Unterschied
-- Klasseneinschränkung (Veraltet)
data Old a = Show a => Old a
-- Klasseneinschränkung mit GADTs
data Better a where
Better :: Show a => a -> Better a
-- Existentielle Quantifikation
data Obj = forall a. (Show a) => Obj a
okay :: Show a => Old a -> a
okay (Old a) = a
better :: Show a => Better a -> a
better (Better a) = a
wrong :: Show a => Obj -> a
wrong (Obj a) = a
Steffen Jost
-- typing impossible
FFP
10-4
Grundlagen GUI GTK+ Glade Beispiele
Existentielle Quantifikation IORef Spracherweiterungen
Veränderliche Variablen
IO-Monade erlaubt echt-veränderliche Variablen
Module Data.IORef definiert:
newIORef
:: a -> IO (IORef a)
Referenz erzeugen
readIORef
:: IORef a -> IO a
Referenz auslesen
writeIORef
:: IORef a -> a -> IO ()
Referenz schreiben
modifyIORef :: IORef a -> (a -> a) -> IO ()
Referenz bearbeiten, strikte Variante: modifyIORef’
Einfachste Art von Variablen, funktioniert wie MVar, TVar, etc.
Operationen sind nicht atomar!
⇒ Entweder nur in einem Thread verwenden, oder
Race-Conditions durch TVar vermeiden.
Operationen können in anderer Reihenfolge ablaufen!
⇒ Nicht für Locks einsetzen, sondern MVar einsetzen.
Steffen Jost
FFP
10-5
Grundlagen GUI GTK+ Glade Beispiele
Existentielle Quantifikation IORef Spracherweiterungen
Functional Dependencies
MultiParamTypeClasses
Erlaupt Typklassen mit mehreren Parametern:
Wiederholung 8-11
class Monad m => VarMonad m v where
new :: a -> m (v a)
get :: v a -> m a
put :: v a -> a -> m ()
instance VarMonad STM TVar where ...
instance VarMonad IO IORef where ...
FunctionalDependencies
Erlaubt es, Abhängigkeiten zwischen Parametern auszudrücken:
class Monad m => VarMonad m v | m -> v where ...
d.h. Typ m legt auch schon v eindeutig fest. Unsinnige Instanz
instance VarMonad IO TVar nicht mehr zusätzlich möglich.
Hilft Typinferenz und liefert bessere Fehlermeldungen.
Steffen Jost
FFP
10-6
Grundlagen GUI GTK+ Glade Beispiele
Existentielle Quantifikation IORef Spracherweiterungen
Functional Dependencies vs. Typ Familien
Functional Dependencies wurden inzwischen von Erweiterung
Typ-Familien nahezu abgelöst, siehe Folie 8-9ff.:
class Monad m => VarMonad m where
type Ref m :: * -> *
new :: a -> m (Ref m a)
get :: Ref m a -> m a
put :: Ref m a -> a -> m ()
Typ-Familien sind flexibler, aber etwas mehr Schreibarbeit, da in
jeder Instanzdeklaration der Typ definiert werden muss.
Steffen Jost
FFP
10-7
Grundlagen GUI GTK+ Glade Beispiele
Übersicht
Viele verschiedene Ansätze für grafische Benutzeroberfläche/
Graphical User-Interface (GUI) in Haskell.
High-Level Ansätze, primär basierend auf Functional Reactive
Programming, momentan experimentell oder Plattform-gebunden.
threepenny-gui
FRP, stabil, unabhängig, benutzt Browser als Display
Keera Hails FRP, recht neu, primär für Spiele gedacht,
Backends für Qt, Gtk & Android Widgets
Mid-Level Ansätze benutzen etablierte, imperative,
Plattform-übergreifende Frameworks:
Qt qtHaskell oder HsQML für Qt Quick
wxWidgets wxHaskell wohl etabliert
GTK+ Gtk2Hs wohl etabliert
Steffen Jost
FFP
10-8
Grundlagen GUI GTK+ Glade Beispiele
Beispiel: threepenny-gui
main = startGUI defaultConfig setup
setup :: Window -> UI ()
setup window = void $ do
return window # set title "Currency Converter"
dollar <- UI.input
euro
<- UI.input
getBody window #+ [
column [
grid [[string "Dollar:", element dollar]
,[string "Euro:" , element euro ]]
, string "Amounts update while typing."
]]
euroIn
<- stepper "0" $ UI.valueChange euro
dollarIn <- stepper "0" $ UI.valueChange dollar
let rate = 0.7 :: Double
withString f = maybe "-" (printf "%.2f") . fmap f . readMay
dollarOut = withString (/ rate) <$> euroIn
euroOut
= withString (* rate) <$> dollarIn
element euro
# sink value euroOut
element dollar # sink value dollarOut
Steffen Jost
FFP
10-9
Grundlagen GUI GTK+ Glade Beispiele
Übersicht
Viele verschiedene Ansätze für grafische Benutzeroberfläche/
Graphical User-Interface (GUI) in Haskell.
High-Level Ansätze, primär basierend auf Functional Reactive
Programming, momentan experimentell oder Plattform-gebunden.
threepenny-gui
FRP, stabil, unabhängig, benutzt Browser als Display
Keera Hails FRP, recht neu, primär für Spiele gedacht,
Backends für Qt, Gtk & Android Widgets
Mid-Level Ansätze benutzen etablierte, imperative,
Plattform-übergreifende Frameworks:
Qt qtHaskell oder HsQML für Qt Quick
wxWidgets wxHaskell wohl etabliert
GTK+ Gtk2Hs wohl etabliert
Steffen Jost
FFP
10-10
Grundlagen GUI GTK+ Glade Beispiele
Ereignisschleife Widgets Container
GTK+
GTK+ steht für GIMP ToolKit, ein Plattform-übergreifendes
Framework für Graphische Benutzeroberflächen, in C geschrieben
Plattformen: Linux, Mac OS X, Windows,. . .
Desktops: GNOME, Unity, Xfce, . . .
Apps: GIMP, Geany, Chromium <V35,. . .
Komponenten
GLib:
Kernbibliothek, Kompatibilitätsschicht,Thread-Handling
Cairo: Bibliothek für 2D Vektor-Grafik
GDK: Rendern, Bitmaps
Pango: Textdarstellung, Internationalisierung
ATK:
Zugänglichkeit, z.B. Vergrößern, Vorlesen
Glade: Grafisches GUI Design Tool
Steffen Jost
FFP
10-11
Grundlagen GUI GTK+ Glade Beispiele
Ereignisschleife Widgets Container
Gtk2Hs
Gtk2Hs bietet Anbindung von Gtk+ 2.x an Haskell
Benutzung rein durch Haskell möglich
Plattform-unabhängig Unterstützung
Unicode unterstützt, Typklasse GlibString string hat
Instanzen für Data.Text oder String
Unterstützt auch Gtk+ Tools:
GUI-Beschreibungen mit Glade V3 für Gtk+ 2.x
im Format GtkBuilder
früher auch noch Libglade
Achtung: Gtk+ 3.x Unterstützung noch immer sehr experimentell
Dokumentation
Tutorials & API auf https://wiki.haskell.org/Gtk2Hs
Am einfachsten: Hooglen mit Suchstring +gtk,
oder auch direkt mit Hayoo suchen
Steffen Jost
FFP
10-12
Grundlagen GUI GTK+ Glade Beispiele
Ereignisschleife Widgets Container
Installation
Installation etwas schwierig; Entwicklerpakete von GTK+ 2.x
müssen vorhanden sein! Mit Stack einfacher, siehe Alex’ Info-Seite.
Benutzung über CIP-Pool der Informatik ohne Stack
local> ssh -Y remote.cip.ifi.lmu.de
cip> export PATH=~/.cabal/bin/:$PATH
cip> cabal update
cip> cabal install cabal-install
cip> cabal update
cip> cabal install gtk2hs-buildtools
cip> cabal install gtk
Damit sollte es funktionieren. 3 ersten Befehle ggf. nicht notwendig
Falls es nicht klappt:
anderen CIP-Rechner ausprobieren
Versionsunterschiede
Lokal installierte GHC Pakete löschen, d.h Verzeichnisse
~/.ghc und ~/.cabal einfach komplett löschen
Steffen Jost
FFP
10-13
Grundlagen GUI GTK+ Glade Beispiele
Ereignisschleife Widgets Container
Imperative GUI-Programmierung
Benutzerinteraktion durch imperatives I/O:
Eingabe: Ereignisse (Clicks, Tastendrücke, interne
Nachrichten, Nachrichten von anderen Applikationen).
Ausgabe: Graphische Darstellung von Inhalten und Steuerung
⇒ GUI-Programmierung mit Gtk2Hs erfolgt in IO-Monade /
Gtk+ ist Ereignis-gesteuert:
Hauptthread durchläuft eine Schleife in Gtk+
Ereignisse werden mit callback-Funktion behandelt
Probleme/Konsequenzen:
Ereignisbehandler bennötigen Zugriff auf einen gemeinsamen
Zustand
Steffen Jost
FFP
10-14
Grundlagen GUI GTK+ Glade Beispiele
Ereignisschleife Widgets Container
Beispiel: Hello world!
import Graphics.UI.Gtk
main :: IO ()
main = do
initGUI
window <- windowNew
button <- buttonNew
set window [ containerBorderWidth := 10,
containerChild
:= button ]
set button [ buttonLabel := "Hello World"
]
onClicked button callback1
onDestroy window mainQuit
widgetShowAll window
mainGUI
callback1 :: IO ()
callback1 = putStrLn "Hello World"
Steffen Jost
FFP
10-15
Grundlagen GUI GTK+ Glade Beispiele
Ereignisschleife Widgets Container
Initialisierung und Ereignisschleife
Module Graphics.UI.Gtk.General.General definiert:
initGUI :: IO [String]
Initialisierung von Gtk+ gemäß Kommandozeilenparameter
Rückgabe ungenutzter Kommandozeilenparameter
mainGUI :: IO ()
Übergibt die Kontrolle des Hauptthreads an die Gtk+
Ereignisschleife
mainQuit :: IO ()
Beendet die Gtk+ Ereignisschleife
Wird ein Ereignis ausgelöst, so wird die Ereignisschleife
unterbrochen, um die entsprechende callback-Funktion auszuführen.
Steffen Jost
FFP
10-16
Grundlagen GUI GTK+ Glade Beispiele
Ereignisschleife Widgets Container
Beispiel: Hello world! (Teil 2)
main = do
initGUI
window <- windowNew
button <- buttonNew
set window [ containerBorderWidth := 10,
containerChild
:= button ]
set button [ buttonLabel := "Hello World"
]
onClicked button $ callback2 button
onDestroy window mainQuit
widgetShowAll window
mainGUI
callback2 :: ButtonClass o => o -> IO ()
callback2 b = do
l <- get b buttonLabel
-- GUI Call
set b [ buttonLabel := (l ++ "!") ] -- GUI Call
Steffen Jost
FFP
10-17
Grundlagen GUI GTK+ Glade Beispiele
Ereignisschleife Widgets Container
Beispiel: Eingefrorene GUI (Teil 1)
callback3 :: ButtonClass o => o -> IO ()
callback3 b = do
l1 <- get b buttonLabel
let n
= read $ (words l1) !! 1
let fin = show $ fib n
let l2 = "Fib " ++ (show $ n+1) ++ " = " ++ fin
set b [ buttonLabel := l2 ]
GUI friert hier bei der Berechnung größerer Fibonacci-Zahlen ein,
d.h. reagiert nicht mehr auf Knöpfe, Größenänderung, etc.
Steffen Jost
FFP
10-18
Grundlagen GUI GTK+ Glade Beispiele
Ereignisschleife Widgets Container
Thread-Safety
Wird ein Ereignis ausgelöst, so wird die Ereignisschleife
unterbrochen, um die entsprechende callback-Funktion im
Hauptthread auszuführen.
Problem
Berechnung der callback-Funktion friert GUI ein
⇒ aufwändige Berechnung im callback mit forkIO abhandeln
Gtk+ ist jedoch nicht Thread-Safe, d.h. GUI-Funktionen dürfen
nur aus dem Hauptthread heraus aufgerufen werden, sonst drohen
unvorhersehbare Abstürze!
postGUISync :: IO a -> IO a
Führt Aktion im Hauptthread aus und blockiert aktuellen
Thread bis Ergebnis zurückgegeben wird
postGUIAsync :: IO () -> IO ()
Führt Aktion im Hauptthread aus, aktueller Thread läuft weiter
Steffen Jost
FFP
10-19
Grundlagen GUI GTK+ Glade Beispiele
Ereignisschleife Widgets Container
Thread-Safety
timeoutAdd :: IO Bool -> Int -> IO HandlerId
timeoutAddFull :: IO Bool -> Priority -> Int -> IO HandlerId
IO-Aktion wird regelmäßig im Hauptthread ausgeführt, bis
False zurückgegeben wird;
Optionen:
1 Aufrufe von GUI-Funktion im Hauptthread mit MVar
anfordern, welche regelmäßig abgefragt werden
2 Falls nicht mit -threaded kompiliert wird, reicht es,
Ereignisschleife regelmäßig mit yield zu unterbrechen:
timeoutAddFull (yield >> return True)
priorityDefaultIdle 100 -- ms
Fallstrick Diese Funktionen aus Modul
Graphics.UI.Gtk.General.General nicht mit gleichnamigen
Modul System.Glib.MainLoop verwechseln, da letztere nicht im
Gtk+ Hauptthread ausgeführt werden
Steffen Jost
FFP
10-20
Grundlagen GUI GTK+ Glade Beispiele
Ereignisschleife Widgets Container
Beispiel: Eingefrorene GUI (Teil 1)
callback3 :: ButtonClass o => o -> IO ()
callback3 b = do
l1 <- get b buttonLabel
let n
= read $ (words l1) !! 1
let fin = show $ fib n
let l2 = "Fib " ++ (show $ n+1) ++ " = " ++ fin
set b [ buttonLabel := l2 ]
GUI friert hier bei der Berechnung größerer Fibonacci-Zahlen ein,
d.h. reagiert nicht mehr auf Knöpfe, Größenänderung, etc.
Steffen Jost
FFP
10-21
Grundlagen GUI GTK+ Glade Beispiele
Ereignisschleife Widgets Container
Beispiel: Eingefrorene GUI (Teil 2)
callback4 :: ButtonClass o => o -> IO ()
callback4 b = do
l1 <- get b buttonLabel
void $ forkIO $ do
let n
= read $ (words l1) !! 1
let fin = show $ fib n
let l2 = "Fib " ++ (show $ n+1) ++ " = " ++ fin
seq l2 $ postGUIAsync $ set b [ buttonLabel := l2 ]
Einfrieren verhindert: Berechnung auslagert in eigenen Thread
seq notwendig um Berechnung zu erzwingen
postGUIAsync um zu Gtk+-Thread zurückzukehren
Wichtig, geht nut falls mit -threaded kompiliert; oder
Ereignisschleife mit yield regelmäßig unterbrechen:
timeoutAddFull (yield >> return True)
priorityDefaultIdle 100
Steffen Jost
FFP
10-22
Grundlagen GUI GTK+ Glade Beispiele
Ereignisschleife Widgets Container
Widgets
Element einer GUI wird oft als Widget bezeichnet
= Window & Gadget
Widgets haben verschiedene Attribute, welche zur Laufzeit
verändert werden können
Beispiel: Text von einem Label oder einem Knopf
Widgets können andere Widgets enthalten
Beispiele: Window, Dialog, etc.
Die Anordnung enthaltener Widgets wird durch das übergeordnete
Widget bestimmt; die Attribute der Widgets können dies
beeinflussen
Beispiel: Padding Attribut von Container-Widgets
Steffen Jost
FFP
10-23
Grundlagen GUI GTK+ Glade Beispiele
Ereignisschleife Widgets Container
Attribute lesen
Modul System.Glib.Attributes definiert:
data ReadWriteAttr o a b
Attribut eines Objekts o mit Lesetyp a und Schreibtyp b
type Attr o a = ReadWriteAttr o a a
Gewöhnliches Attribut mit identischem Typ für beide Zugriffe
type ReadAttr o a = ReadWriteAttr o a ()
Attribut kann nur gelesen werden
type WriteAttr o b = ReadWriteAttr o () b
Attribut kann nur geschrieben werden
get :: o -> ReadWriteAttr o a b -> IO a
Attribut auslesen
Beispiel
value <- get button buttonLabel
Steffen Jost
FFP
10-24
Grundlagen GUI GTK+ Glade Beispiele
Ereignisschleife Widgets Container
Attribute schreiben
Modul System.Glib.Attributes definiert:
set
::
o -> [AttrOp o] -> IO ()
data AttrOp o =
= forall a b. (ReadWriteAttr o a b) := b
| forall a b. (ReadWriteAttr o a b) :~ (a -> b)
| forall a b. (ReadWriteAttr o a b) :=> (IO b)
|
...
Attribute werden mit speziellen Schreipoperationen geändert
AttrOp o ist ein existentieller Datentyp, d.h.
z.B. in einer Liste des Typs [AttrOp o] können die Elemente
verschiedene Typen für a und b besitzen
Beispiel
set button [ buttonLabel
:= "OK",
buttonFocusOnClick := False ]
Steffen Jost
FFP
10-25
Grundlagen GUI GTK+ Glade Beispiele
Ereignisschleife Widgets Container
Attribute schreiben
Modul System.Glib.Attributes definiert:
:=
Zuweisung eines Wertes
:~
Anwendung einer Funktion auf aktuellen Wert
:=>
Zuweisung des Wertes einer IO Aktion
:~>
Anwendung einer IO-Funktion auf aktuellen Wert
::=
Anwendung einer Funktion, welche als Argument
das aktuelle Objekt bekommt
::~
Anwendung einer Funktion, welche als Argumente
aktuelles Objekt und aktuellen Wert bekommt
Alle Attribut-Operationen sind als infixr 0 deklariert
Steffen Jost
FFP
10-26
Grundlagen GUI GTK+ Glade Beispiele
Ereignisschleife Widgets Container
Widgets Allgemein
Modul Graphics.UI.Gtk.Abstract.Widget definiert:
widgetShowAll :: WidgetClass self => self -> IO ()
Zeigt vorbereitetes Widget auf dem Bildschirm an
widgetHideAll :: WidgetClass self => self -> IO ()
Versteckt Widget vorübergehend
widgetDestroy :: WidgetClass self => self -> IO ()
Entfernt Widget dauerhaft
onDestroy :: WidgetClass w => w -> IO () -> IO (ConnectId w)
Wird beim Schliessen des Widgets ausgeführt
Typ data ConnectId o stammt aus Modul
System.Glib.Signals
Steffen Jost
FFP
10-27
Grundlagen GUI GTK+ Glade Beispiele
Ereignisschleife Widgets Container
Buttons
Zu jeder wichtigen Art von Widgets gibt es entsprechende Module,
welche spezialisierte Funktionen zur Verfügung stellen.
Modul Graphics.UI.Gtk.Buttons.Button definiert:
onClicked :: ButtonClass b => b -> IO () -> IO (ConnectId b)
Registriert Behandler für Ereignis, Knopf gedrückt
onPressed :: ButtonClass b => b -> IO () -> IO (ConnectId b)
Bereits wenn der Knopf gedrückt wird
onReleased :: ButtonClass b => b -> IO () -> IO (ConnectId b)
Wenn der Knopf losgelassen wird
onEnter :: ButtonClass b => b -> IO () -> IO (ConnectId b)
Wenn der Cursor auf den Knopf kommt
Steffen Jost
FFP
10-28
Grundlagen GUI GTK+ Glade Beispiele
Ereignisschleife Widgets Container
Container
Container-Widgets können Kinder/Children-Widgets enthalten.
Typklasse Container: Widgets mit ein oder mehreren Kindern
containerAdd :: ConatinerClass self, WidgetClass widget =>
self -> widget -> IO ()
Kind hinzufügen
containerRemove :: ConatinerClass self, WidgetClass widget =>
self -> widget -> IO ()
Kind entfernen
containerGetChildren :: ConatinerClass self => self -> IO [Widget]
Kinder auslesen
containerForeach :: ConatinerClass self =>
self -> (Widget -> IO ()) -> IO () Funktion auf Kinder anwenden
Geordnetes Hinzufügen über spezielle Unterklassen-Funktion
Hinzufügen auch über Attribute wie im “Hello Gtk”-Beispiel
Spezielle Kind-Attribute , z.B. zur Anordnung, vorhanden
set container [ attr child := value ]
Steffen Jost
FFP
10-29
Grundlagen GUI GTK+ Glade Beispiele
Ereignisschleife Widgets Container
Windows
Beispiel: Window ist einer Unterklasse von Container und Bin
Typklasse Bin: Widgets mit genau einem Kind
binGetChild :: BinClass self => self -> IO (Maybe Widget)
Kind auslesen
Ein Fenster beinhaltet also nur genau ein Kind-Objekt.
Modul Graphics.UI.Gtk.Windows.Window definiert:
windowNew :: IO Window
erstellt neues Fenster
windowDefaultWidth :: WindowClass self => Attr self Int
Breite bei Initialisierung
windowTitle :: (WindowClass self) => Attr self string
Fenstertitel setzen
windowPresent :: WindowClass self => self -> IO ()
Fenster nach vorne schieben
Steffen Jost
FFP
10-30
Grundlagen GUI GTK+ Glade Beispiele
Ereignisschleife Widgets Container
Elemente anordnen
Container zur horizontalen Ausrichtung aus Modul
Graphics.UI.Gtk.Layout.HBox
Vertikale Ausrichtung analog mit VBox
hBoxNew :: Bool -> Int -> IO HBox
Container erstellen; 1.Arg: Kinder mit gleichmäßigem Platz
anordnen, 2.Arg: Freie Pixel zwischen Kindern
Festlegen wie Elemente sich anpassen mit data Packing aus
Modul Graphics.UI.Gtk.Abstract.Box:
PackGrow Kinder wachsen mit
Eingabefeld
PackNatural Kinder bestimmen eigene Größe
PackRepel Rand um Kind wächst
Menu Leiste
Knöpfe
Einfügen der Kinder mit boxPackEnd oder
boxPackStart :: (BoxClass self, WidgetClass child) =>
self -> child -> Packing -> Int -> IO ()
Int-Wert legt zusätzlichen Abstand fest
Steffen Jost
FFP
10-31
Grundlagen GUI GTK+ Glade Beispiele
Ereignisschleife Widgets Container
Beispiel: Horizontale Packung
main = do
initGUI
window
<- windowNew
hbox
<- hBoxNew True 10
button1 <- buttonNewWithLabel "Button 1"
button2 <- buttonNewWithLabel "Button 2"
set window [ windowDefaultWidth
:= 200,
windowDefaultHeight
:= 200,
containerBorderWidth := 10,
containerChild
:= hbox ]
boxPackStart hbox button1 PackGrow 0
boxPackStart hbox button2 PackGrow 0
...
Steffen Jost
FFP
10-32
Grundlagen GUI GTK+ Glade Beispiele
Ereignisschleife Widgets Container
Ausrichtung
Box erlaubt keine Festlegung der Ausrichtung. Alignment-Widgets
aus Modul Graphics.UI.Gtk.Layout.Alignment machen das.
Alignment-Widgets sind in der BinClass, d.h. haben genau ein
Kind, dessen Ausrichtung diese bestimmen.
alignmentNew :: Float -> Float -> Float -> Float -> IO Alignment
.. <- alignmentNew xalign yalign xscale yscale
xalign, yalign
Ausrichtung: 0=Links/Oben bis 1=Rechts/Unten; 0.5=Mitte
xscale, yscale
Skalierung des Kindes: 0=wächst nicht; 1=wächst voll
⇒ Skalierung=1 impliziert, das Ausrichtung keine Auswirkung hat,
da das Kind sowieso den vollen Raum einnimmt!
Steffen Jost
FFP
10-33
Grundlagen GUI GTK+ Glade Beispiele
Ereignisschleife Widgets Container
Tabellen
Tabellen erlauben detaillierte Platzierung; Container-Unterklasse
Aus Modul Graphics.UI.Gtk.Layout.Table:
tableNew ::
->
->
->
Int
-- Anzahl Zeilen
Int
-- Abzahl Spalten
Bool
-- Alle Felder gleich groß?
IO Table
tableAttachDefaults ::(TableClass self,WidgetClass widget)
=> self -> widget -- Tablle, einzufügendes Kind
-> Int -> Int
-- Linke und Rechte Spalte
-> Int -> Int
-- Obere und Untere Zeile
-> IO ()
Kind-Widgets können mehrere Zellen einer Tabelle belegen
tableAttach erlaubt zusätzliche Angaben zu Rand und
Verhalten bei Größenänderung. Defaults: [Expand,Fill]
Steffen Jost
FFP
10-34
Grundlagen GUI GTK+ Glade Beispiele
Ereignisschleife Widgets Container
Beispiel für Tabellenlayout
createButton :: Table -> (Int, Int) -> IO Button
createButton table (x,y) = do
b <- buttonNew
onClicked b (myButtonPressAction (x,y))
tableAttachDefaults table b x (x+1) y (y+1)
return b
main = do
...
table <- tableNew ysize xsize True
let rows = [ [ (x,y) | x <- [0..xsize-1] ]
| y <- [0..ysize-1] ]
buttons <- mapM (mapM (createButton table)) rows
...
myButtonPressAction :: (Int, Int) -> IO ()
Steffen Jost
FFP
10-35
Grundlagen GUI GTK+ Glade Beispiele
Menüs und Werkzeugleisten
Glade
Glade ist ein grafisches Werkzeug zum Erstellen statischer GUIs
Glade V3 für Gtk+ 2.x, GtkBuilder Format
Man legt damit fest:
Welche Widgets gibt es in der Anwendung
Welches Widget ist in welchem anderen wo enthalten
Welche Attribute anfangs eingestellt sind,
z.B. Ausrichtung, Größe, Beschriftung, etc.
Das Ergebnis wird sofort auf dem Bildschirm angezeigt
Die GUI wird durch eine XML Datei beschrieben, welche erst zur
Laufzeit eingelesen wird und mitgeliefert werden muss.
Alternativ kann die XML Beschreibung als auch String im Haskell
Code untergebracht werden.
Steffen Jost
FFP
10-36
Grundlagen GUI GTK+ Glade Beispiele
Menüs und Werkzeugleisten
Builder
Modul Graphics.UI.Gtk.Builder:
explizit importieren
builderNew :: IO Builder Leeres Builder Objekt erzeugen
builderAddFromFile :: GlibFilePath fp => Builder -> fp -> IO ()
Definition aus Datei zu Builder hinzufügen
builderAddFromString :: Builder -> string -> IO ()
Definition aus Datei zu Builder hinzufügen
builderGetObject :: (GObjectClass cls, GlibString string) =>
Builder -> (GObject -> cls) -> string -> IO cls
2.Arg.: Type-cast, z.B. castToWindow, castToButton, . . .
3.Arg.: Namen-Attribut des gesuchten Objektes in XML
Einlesen des XML wird erst zur Laufzeit durchgeführt.
Hier können Laufzeitfehler entstehen!
falsch geschriebene Namen, falsche Casts, etc.
Steffen Jost
FFP
10-37
Grundlagen GUI GTK+ Glade Beispiele
Menüs und Werkzeugleisten
Zentraler GUI Datentyp
Problem
Einlesen des XML wird erst zur Laufzeit durchgeführt.
Hier können Laufzeitfehler entstehen!
falsch geschriebene Namen, falsche Casts, etc.
Empfehlung
Bei Initialisierung der GUI alle Elemente auf einmal anlegen und in
zentralen Datentypen ablegen.
Passt XML Beschreibung nicht zum Code, werden Fehler am
Programmstart erkannt; danach garantiert Typsystem alles weitere.
Es müssen nur die Widgets eingelesen werden, auf die auch
Zugriffe notwendig sind
Alignment, Box,. . .
Dynamische Elemente wie bisher später hinzufügen
GUI Datentyp kann an Callback-Funktionen übergeben werden
Evtl. sinnvoller, einen Datentyp pro Fenster-Klasse anzulegen
siehe Aufgabe A14-1
Steffen Jost
FFP
10-38
Grundlagen GUI GTK+ Glade Beispiele
Menüs und Werkzeugleisten
Beispiel: Builder
data MainGUI = MainGUI { mainWindow
:: Window
, mainVbox
:: VBox
, mainLabel
:: Label
, startButton :: Button
}
main = do initGUI
gui <- loadGUI
-- selbstdefiniert fkt
onDestroy (mainWindow gui)
mainQuit
onClicked (startButton gui) $ fooAction gui
widgetShowAll (mainWindow gui) -- zeigt alle Kinder, nicht
mainGUI
-- nur die gui enthaltenen
loadGUI = do -- all XML loading errors must occur here
builder <- builderNew
builderAddFromFile builder "myGUI.glade"
mainWindow <- builderGetObject builder castToWindow
mainVbox
<- builderGetObject builder castToVBox
mainLabel
<- builderGetObject builder castToLabel
startButton <- builderGetObject builder castToButton
return $ MainGUI {..}
Steffen Jost
FFP
"window1"
"vbox1"
"label1"
"button1"
10-39
Grundlagen GUI GTK+ Glade Beispiele
Menüs und Werkzeugleisten
Menü und Werkzeugleiste
Menü, Werkzeugleisten und Tastenkombinationen starten
Aktionen, welche durch den Typ Action repräsentiert werden.
Aktionen können aktiviert und deaktiviert werden.
actionNew ::
String
-> String
-> Maybe String
-> Maybe String
-> IO Action
onActionActivate
-----
name
: unique name for the action
label : displayed in items & btns
tooltip
stockId: icon to be displayed
:: ActionClass self => self -> IO () ->
IO (ConnectId self)
actionSetVisible
:: ActionClass self => self->Bool->IO()
actionSetSensitive :: ActionClass self => self->Bool->IO()
Steffen Jost
FFP
10-40
Grundlagen GUI GTK+ Glade Beispiele
Menüs und Werkzeugleisten
XML Menübeschreibung
uiDecl = "\
\<ui>\
\ <menubar>\
\
<menu action=\"FILE_MENU\">\
\
<menuitem action=\"QUIT\"/>\
\
</menu>\
\ </menubar>\
\ <toolbar>\
\
<toolitem action=\"QUIT\"/>\
\ </toolbar>\
\</ui>"
Steffen Jost
FFP
10-41
Grundlagen GUI GTK+ Glade Beispiele
Menüs und Werkzeugleisten
Menu erstellen
createMenu :: VBox -> IO ()
createMenu box = do
actFileMenu <- actionNew "FILE_MENU" "File" Nothing Nothing
actQuit <- actionNew "QUIT" "Quit"
(Just "Exit Tic Tac Toe") (Just stockQuit)
onActionActivate actQuit mainQuit
actGroup <- actionGroupNew "ACTION_GROUP"
mapM (actionGroupAddAction actGroup) [actFileMenu,actQuit]
ui <- uiManagerNew
uiManagerAddUiFromString ui uiDecl
uiManagerInsertActionGroup ui actGroup 0
Just menubar <- uiManagerGetWidget ui "/ui/menubar"
boxPackStart box menubar PackNatural 0
Just toolbar <- uiManagerGetWidget ui "/ui/toolbar"
boxPackStart box toolbar PackNatural 0
Steffen Jost
FFP
10-42
Grundlagen GUI GTK+ Glade Beispiele
Tic Tac Toe Solitaire
Beispielapplikation: Tic Tac Toe
Idee für sehr simple GUI:
Erstelle 9 Buttons, blank, d.h. ohne Label.
Klick auf Button ist Zug des menschlichen Spielers
Ereignisbehandlung eines Buttons:
Label des Buttons setzen
Antwort des Computerspielers berechnen
Globales Label für Statusanzeige
⇒ Ereignisbehandler eines Buttons benötigt Zugriff auf
Status-Label
Spielzustand
alle anderen Button-Labels
Steffen Jost
FFP
10-43
Grundlagen GUI GTK+ Glade Beispiele
Tic Tac Toe Solitaire
API für Tic Tac Toe Modell
Logik des Spieles in seperatem Modul, welches u.a. bereitstellt:
data State = State { turn, winner :: Maybe Player
, board
:: Board (Maybe Player)
, lastMove
:: Move, size :: Int }
data Player = X | O
newtype Pos = Pos (Int,Int)
type Move
= (Player, Pos)
newGame
showStatus
makeMove
computerMove
::
::
::
::
deriving (Eq, Ord, Show)
deriving (Eq, Ord, Show)
Int -> Int -> Player -> State
State -> String
State -> Move -> Maybe State
Int -> State -> Maybe Move
Pos beginnt bei (1,1) und endet bei (size state, size state)
computerMove bekommt Spielstärke als Argument
Steffen Jost
FFP
10-44
Grundlagen GUI GTK+ Glade Beispiele
Tic Tac Toe Solitaire
Versuch einer zirkulären Konstruktion
data App = App { stateRef :: IORef State -- Zustand Spiel
, buttons :: Map.Map Pos Button }
buttonPress
:: App -> Pos -> IO () -- btn event handler
createButton :: App -> Table -> Pos -> IO Button
createButton app table (Pos (x,y)) = do
b <- buttonNew
onClicked b (buttonPress app (x,y))
tableAttachDefaults table b x (x+1) y (y+1)
return b
let app = App
{ stateRef = newIORef initialState
, buttons = fold (createButton app table) Map.empty
[Pos (x,y)| x <- [1..gameSize], y <- [1..gameSize]]
} -- DOES NOT WORK: createButton is monadic!
Alternative: Event-Handler mit 2. Schleife installieren
Steffen Jost
FFP
10-45
Grundlagen GUI GTK+ Glade Beispiele
Tic Tac Toe Solitaire
Zirkuläre monadische Programme
Monadische Rekursion:
module Control.Monad.Fix where
class Monad m => MonadFix m where
mfix :: (a -> m a) -> m a
instance MonadFix IO
Den Knoten zuziehen (tying the knot):
do app <- mfix $ \ app' ->
do buttons <- foldM
(createButton app' table)
Map.empty
[Pos (x,y)| x<-[1..gameSize], y<-[1..gameSize]]
return $ App
{ stateRef = newIORef initialState
, buttons
= buttons
}
Steffen Jost
FFP
10-46
Grundlagen GUI GTK+ Glade Beispiele
Tic Tac Toe Solitaire
Rekursive Do-Notation
Let-Definition in Haskell sind immer (wechselseitig) rekursiv:
> let { foo x =x:bar(x+1); bar y =y:foo(-y) } in take 9 $ foo 0
[0,1,-1,0,0,1,-1,0,0]
Mit GHC-Spracherweiterung DoRec auch monadisch erlaubt:
{-# LANGUAGE DoRec #-}
do
-- normale monadische Aktionen
rec -- rekursiver Abschnitt beginnt
buttons <- foldM (createButton app table) Map.empty
[Pos (x,y)| x<-[1..gameSize], y<-[1..gameSize]]
let app = App { stateRef = newIORef initialState
, buttons = buttons }
Steffen Jost
FFP
10-47
Grundlagen GUI GTK+ Glade Beispiele
Tic Tac Toe Solitaire
Solitaire
Code-Beispiel aus Blog-Eintrag vom 19. März 2013
“Creating board games in Haskell in 100 lines of code”
im Keera Studios Blog http://keera.co.uk/blog
> git clone https://github.com/keera-studios/gtk-helpers.git
100 spezielle Zeilen
+ Framework für generische Brettspiele von Keera Studios
+ Grafiken
Steffen Jost
FFP
10-48
Grundlagen GUI GTK+ Glade Beispiele
Tic Tac Toe Solitaire
API: Game.Board.BasicTurnGame
class PlayableGame a index tile player piece
| a -> index, a -> tile, a -> player, a -> piece where
curPlayer :: a -> player
allPieces :: a -> [(index, index, player, piece)]
allPos
:: a -> [(index, index, tile)]
moveEnabled
:: a -> Bool
canMove
:: a -> player -> (index, index) -> Bool
canMoveTo
:: a -> player -> (index, index) -> (index, index) -> Bool
move
:: a -> player -> (index, index) -> (index, index) -> [GameCha
activateEnabled :: a -> Bool
canActivate
:: a -> player -> (index, index) -> Bool
activate
:: a -> player -> (index, index) -> [GameChange index player p
applyChange
:: a -> GameChange index player piece -> a
applyChanges
:: a -> [GameChange index player piece] -> a
data GameChange index player piece = AddPiece (index, index) player piece
| RemovePiece (index, index)
| MovePiece (index, index) (index, index)
data GameState index tile player piece = GameState
{ curPlayer'
:: player
, boardPos
:: [(index, index, tile)]
, boardPieces' :: [(index, index, player, piece)] }
Steffen Jost
FFP
10-49
Grundlagen GUI GTK+ Glade Beispiele
Quellen
GTK+ Projekt: http://www.gtk.org/
gtk2hs Projekt-Homepage und Dokumentation:
http://projects.haskell.org/gtk2hs/
Glade Tutorials
auf Gtk2Hs Projektseite
“Reflections on programming” Blogpost von Rickard Lindberg
Real World Haskell, Kapitel 23 “GUI programming”
von Bryan O’Sullivan, Don Stewart, John Goerzen
Keera Studios, http://keera.co.uk/blog
Blog über Spieleentwicklung mit Haskell für Gtk und Android
threepenny-gui:
http://github.com/HeinrichApfelmus/threepenny-gui
Steffen Jost
FFP
10-50
Herunterladen