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 16. Januar 2015 Steffen Jost FFP 10-1 Grundlagen GUI GTK+ Glade Beispiele Abschlußprojekt Anmeldung bis spätestens Mittag, 23.01.15 per UniworX Informationen zur Anmeldung als “Übungsblatt” dort Termine 28. Januar, 25. März, 26. März Abschlußpräsentation Je nach Teilnehmerzahl ca. 15-25 Minuten Präsentation inkl. Demonstration der Software inkl. Diskussion des Codes: Wo wurden welche Techniken aus der Vorlesung eingesetzt? Welche interessanten Probleme traten auf? Welche Bibliotheken/Vorlagen wurden benutzt? bei 2–3 Teilnehmern: Wer hat was gemacht? Anschließend Befragung aller Teilnehmer zu Projekt und Code. Bewertet wird neben Umfang und eingesetzten Techniken auch Eleganz und Klarheit des Codes selbst. Steffen Jost FFP 10-2 Grundlagen GUI GTK+ Glade Beispiele Existentielle Quantifikation Veränderliche Variablen Sprach 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-3 Grundlagen GUI GTK+ Glade Beispiele Existentielle Quantifikation Veränderliche Variablen Sprach 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 atomisch! ⇒ Entweder nur in einem Thread verwenden, oder Race-Conditions mit TVar vermeiden. Operationen können in anderer Reihenfolge ablaufen! ⇒ Nicht für Locks einsetzen, sondern MVar einsetzen. Steffen Jost FFP 10-4 Grundlagen GUI GTK+ Glade Beispiele Existentielle Quantifikation Veränderliche Variablen Sprach Functional Dependencies MultiParamTypeClasses Erlaupt Typklassen mit mehreren Parametern: Wiederholung 8-10 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-5 Grundlagen GUI GTK+ Glade Beispiele Existentielle Quantifikation Veränderliche Variablen Sprach Functional Dependencies vs. Typ Familien Functional Dependencies wurden inzwischen von Erweiterung Typ-Familien nahezu abgelöst, siehe Folie 8-11: 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, dafür etwas mehr Schreibarbeit, da in jeder Instanzdeklaration der Typ definiert werden muss. Steffen Jost FFP 10-6 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. Bemerkenswert: threepenny-gui: FRP Unterstützung, stabil, unabhängig, benutzt Browser als Display Mid-Level Ansätze benutzen etablierte, imperative, Plattform-übergreifende Frameworks: Qt qtHaskell; nun Qt Quick durch HsQML wxWidgets wxHaskell wohl etabliert GTK+ Gtk2Hs wohl etabliert Steffen Jost FFP 10-7 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-8 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. Bemerkenswert: threepenny-gui: FRP Unterstützung, stabil, unabhängig, benutzt Browser als Display Mid-Level Ansätze benutzen etablierte, imperative, Plattform-übergreifende Frameworks: Qt qtHaskell; nun Qt Quick durch HsQML wxWidgets wxHaskell wohl etabliert GTK+ Gtk2Hs wohl etabliert Steffen Jost FFP 10-9 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: GUI Design Tool Steffen Jost FFP 10-10 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 derzeit nicht unterstützt sehr experimentell Dokumentation Tutorials & API auf http://projects.haskell.org/gtk2hs/ Am einfachsten: Hooglen mit Suchstring +gtk, oder auch direkt mit Hayoo suchen Steffen Jost FFP 10-11 Grundlagen GUI GTK+ Glade Beispiele Ereignisschleife Widgets Container Installation Installation kann schwierig sein. Am besten über OS Paketmanager, da Entwicklerpakete von GTK+ 2.x vorhanden sein müssen! Benutzung über CIP-Pool der Informatik 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 funktioneren. 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-12 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-13 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-14 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-15 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 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-16 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 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-17 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-18 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-19 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, falls mit -threaded kompiliert! Ansonsten Ereignisschleife mit yield regelmäßig unterbrechen: timeoutAddFull (yield >> return True) priorityDefaultIdle 100 Steffen Jost FFP 10-20 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-21 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-22 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-23 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-24 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-25 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-26 Grundlagen GUI GTK+ Glade Beispiele Ereignisschleife Widgets Container 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 23.01.2015 Steffen Jost FFP 10-27 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-27 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 kann also nur genau ein Child haben. 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 windowPresent :: WindowClass self => self -> IO () Fenster nach vorne schieben Steffen Jost FFP 10-28 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-29 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-30 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-31 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-32 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-33 Grundlagen GUI GTK+ Glade Beispiele 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-34 Grundlagen GUI GTK+ Glade Beispiele 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-35 Grundlagen GUI GTK+ Glade Beispiele 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. sinnvoll ein Datentyp pro Fenster anlegen siehe Aufgabe A12-2 Steffen Jost FFP 10-36 Grundlagen GUI GTK+ Glade Beispiele Beispiel: Builder main = do initGUI gui <- loadGUI onDestroy (mainWindow gui) mainQuit onClicked (startButton gui) $ fooAction gui widgetShowAll (mainWindow hui) -- zeigt auch ungebaute Kinder mainGUI data MainGUI = MainGUI { , , , } mainWindow mainVbox mainLabel startButton :: :: :: :: Window VBox Label Button 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-37 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-38 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 makeMove computerMove showStatus :: :: :: :: deriving (Eq, Ord, Show) deriving (Eq, Ord, Show) Int -> Player -> State State -> Move -> Maybe State Int -> State -> Maybe Move State -> String Pos beginnt bei (1,1) und endet bei (size state, size state) computerMove bekommt Spielstärke als Argument Steffen Jost FFP 10-39 Grundlagen GUI GTK+ Glade Beispiele Tic Tac Toe Solitaire Solitaire Bildquelle: wikimedia.org 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-40 Grundlagen GUI GTK+ Glade Beispiele Tic Tac Toe Solitaire Screenshot Steffen Jost FFP 10-41 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-42 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-43