Réelle widget objets est quelque chose qui est très orientée objet. Une technique couramment utilisée dans le monde fonctionnelle est d'utiliser à la place Fonctionnel Réactif de Programmation (PRF). Je vais décrire brièvement ce que d'une bibliothèque de widgets dans le plus pur Haskell ressemblerait lors de l'utilisation de PRF.
tl/dr: Vous n'avez pas à traiter "Widget objets", vous permet de gérer des collections de "flux d'événements" à la place, et ne se soucient pas de ce qui les widgets ou lorsque ces flux proviennent.
En fibre de verre, il y a la notion de base d'un Event a
, qui peut être vu comme une liste infinie [(Time, a)]
. Donc, si vous voulez le modèle d'un compteur qui compte, on l'écrit en tant que [(00:01, 1), (00:02, 4), (00.03, 7), ...]
, qui associe un compteur spécifique de la valeur à un moment donné. Si vous souhaitez modéliser un bouton est enfoncé, vous produisez un [(00:01, ButtonPressed), (00:02, ButtonReleased), ...]
Il y a aussi communément ce qu'on appelle un Signal a
, qui est comme un Event a
, sauf que la valeur modélisée est continue. Vous n'avez pas un ensemble discret de valeurs à des moments précis, mais vous pouvez demander à l' Signal
de sa valeur, disons, 00:02:231
et il vous donnera la valeur 4.754
ou quelque chose. Pensez à un signal comme un signal analogique comme sur un cœur jauge de charge (électrocardiographiques de l'appareil/le moniteur de Holter) à l'hôpital: c'est une ligne continue qui saute haut et en bas, mais ne fait jamais un "écart". Une fenêtre ne toujours avoir un titre, par exemple (mais c'est peut-être la chaîne vide), de sorte que vous pouvez toujours demander à sa valeur.
Dans une bibliothèque d'interface graphique, sur un niveau bas, il y avait un mouseMovement :: Event (Int, Int)
et mouseAction :: Event (MouseButton, MouseAction)
ou quelque chose. L' mouseMovement
est le réel USB/souris PS2 sortie, de sorte que vous obtenez seulement des différences de positions comme des événements (par exemple, lorsque l'utilisateur déplace la souris vers le haut, vous obtiendrez cas (12:35:235, (0, -5))
. Vous seriez alors en mesure de "s'intégrer" ou plutôt à "accumuler" le mouvement des événements pour obtenir un mousePosition :: Signal (Int, Int)
qui vous a donné absolue les coordonnées de la souris. mousePosition
pourrait également prendre en considération absolue des périphériques de pointage, tels que des écrans tactiles, ou d'événements du système d'exploitation qui repositionner le curseur de la souris, etc.
De même pour un clavier, il y avait un keyboardAction :: Event (Key, Action)
, et l'on pourrait aussi "intégrer" que les flux d'événements en keyboardState :: Signal (Key -> KeyState)
qui vous permet de lire une clé de l'état à n'importe quel point dans le temps.
Les choses se compliquent quand vous voulez dessiner des trucs sur l'écran et interagir avec les widgets.
Pour créer un guichet unique, on aurait une "fonction magique" appelé:
window :: Event DrawCommand -> Signal WindowIcon -> Signal WindowTitle -> ...
-> FRP (Event (Int, Int) {- mouse events -},
Event (Key, Action) {- key events -},
...)
La fonction est magique parce qu'il faudrait appeler le système d'exploitation des fonctions spécifiques et de créer une fenêtre (à moins que l'OS lui-même est le fibre de verre, mais je doute que). C'est aussi pourquoi il est dans l' FRP
monade, car il s'agirait createWindow
et setTitle
et registerKeyCallback
etc en IO
monade derrière les coulisses.
On pourrait bien sûr le groupe de l'ensemble de ces valeurs dans des structures de données de sorte qu'il y aurait:
window :: WindowProperties -> ReactiveWidget
-> FRP (ReactiveWindow, ReactiveWidget)
L' WindowProperties
sont des signaux et des événements qui déterminent l'aspect et le comportement de la fenêtre (par exemple, si il devrait y avoir les boutons fermer, ce que le titre devrait être, etc.).
L' ReactiveWidget
représente S et Es qui sont des évènements souris et clavier, dans le cas où vous souhaitez émuler les clics de souris à partir de votre application, et un Event DrawCommand
que représente le flux des choses que vous voulez dessiner sur la fenêtre. Cette structure de données est commune à tous les widgets.
L' ReactiveWindow
représente des événements comme la fenêtre réduite, etc, et la sortie ReactiveWidget
représente la souris et le clavier des événements provenant de l'extérieur/à l'utilisateur.
Puis on va créer une réelle widget, disons un bouton-poussoir. Il aurait la signature:
button :: ButtonProperties -> ReactiveWidget -> (ReactiveButton, ReactiveWidget)
L' ButtonProperties
aurait à déterminer la couleur/texte/etc sur le bouton, et l' ReactiveButton
contiendrait par exemple, un Event ButtonAction
et Signal ButtonState
lire l'état du bouton.
Notez que l' button
de la fonction est une fonction pure, puisqu'elle ne dépend que de la pure PRF valeurs, comme les événements et les signaux.
Si l'on veut groupe de widgets (par exemple, les empiler horizontalement), on aurait pu créer par exemple un:
horizontalLayout :: HLayoutProperties -> ReactiveWidget
-> (ReactiveLayout, ReactiveWidget)
L' HLayoutProperties
devrait contenir des informations sur la frontière de tailles et de l' ReactiveWidget
s pour le contenu des widgets. L' ReactiveLayout
serait alors contenir un [ReactiveWidget]
avec un élément pour chaque enfant widget.
Ce que la disposition n'est qu'il aurait une interne Signal [Int]
qui a déterminé la hauteur de chaque widget dans la mise en page. Il serait alors bénéficier de tous les événements à partir de l'entrée ReactiveWidget
, alors basée sur la structure de la partition, sélectionnez une sortie ReactiveWidget
pour envoyer l'événement à, aussi en attendant la transformation de l'origine, par exemple les événements de la souris par le décalage de la partition.
Pour démontrer comment cette API permettrait de travail, estime que ce programme est:
main = runFRP $ do rec -- Recursive do, lets us use winInp lazily before it is defined
-- Create window:
(win, winOut) <- window winProps winInp
-- Create some arbitrary layout with our 2 widgets:
let (lay, layOut) = layout (def { widgets = [butOut, labOut] }) layInp
-- Create a button:
(but, butOut) = button butProps butInp
-- Create a label:
(lab, labOut) = label labProps labInp
-- Connect the layout input to the window output
layInp = winOut
-- Connect the layout output to the window input
winInp = layOut
-- Get the spliced input from the layout
[butInp, layInp] = layoutWidgets lay
-- "pure" is of course from Applicative Functors and indicates a constant Signal
winProps = def { title = pure "Hello, World!", size = pure (800, 600) }
butProps = def { title = pure "Click me!" }
labProps = def { text = reactiveIf
(buttonPressed but)
(pure "Button pressed") (pure "Button not pressed") }
return ()
(def
est de Data.Default
en data-default
)
Cela crée un événement graphique, comme suit:
Input events -> Input events ->
win ---------------------- lay ---------------------- but \
<- Draw commands etc. \ <- Draw commands etc. | | Button press ev.
\ Input events -> | V
\---------------------- lab /
<- Draw commands etc.
Notez qu'il ne doit pas y avoir de "widget objets" n'importe où. Une mise en page est tout simplement une fonction qui transforme l'entrée et la sortie des événements selon un système de partitionnement, vous pouvez utiliser le flux d'événements, vous avez accès à des widgets, ou vous pouvez laisser un autre sous-système de générer le flux entièrement. Il en va de même pour les boutons et les étiquettes: ils sont simplement des fonctions de conversion sur les événements en tirer des commandes, ou des choses semblables. C'est une représentation de découplage total, et très flexible dans la nature.