Programmation de composants
actifs pour VID
Auteur : M. PIQUET Johan
Janvier 2004
Tout droits d’exploitation réservés.
Diffusion autorisée après permission de l’auteur
Pour toutes questions, suggestions, ou corrections, merci
de contacter l’auteur afin de faire évoluer le tutorial.
Diffusion accordée
sur le site RebolFrance
(www.rebolfrance.org)
Création de composants personnalisés
Evolution : de style vers composant
Rapport entre VID et les objets face
Permet l’initialisation de son
composant
Associer des raffinements à son
composant
Surveillance automatique de ports
Un composant réagissant au réseau
Simple : un composant affichant
du texte formaté
Légèrement plus complexe … un
conteneur d’icônes
REBOL est pourvu d’un
puissant dialecte permettant de créer des interfaces graphiques.
Ce dialecte est appelé VID, pour Visual Interface Dialect, et permet
l’utilisation d’un nombre appréciable de composants par défaut.
Ce tutorial se consacre dans une première partie à l’extension du dialecte VID par l’ajout de nouveaux composants. Dans cette partie vous apprendrez à créer de réels composants : en opposition aux styles de composant, lesquels ne constituent qu’un raccourci d’écriture.
Dans une seconde partie la création de composants actifs sera exposée. Par composant actif il faut entendre des composants réagissant à des événements : clavier, souris, temporisation, … mais aussi réseau, fichier, et bien d’autres !
VID permet la création de styles de composant. Cela signifie qu’il est possible d’enregistrer sous un nom un composant et ses options d’affichage et ainsi de diminuer l’effort d’écriture d’une interface graphique ou de créer des feuilles de style.
Exemple : un
bouton personnalisé sans utilisation des styles
REBOL []
view layout [
box
"hello" %button-up.png
effect
[extend]
100x20
font
[size: 12 color: black shadow: 0]
]
Exemple : le
même bouton personnalisé, avec l’utilisation des styles
REBOL []
view layout [
style
mon-bouton
box "hello" %button-up.png
effect [extend] font [size: 12 color: black
shadow: 0]
mon-bouton "Bonjour" 100x20
mon-bouton "programmeurs REBOL" 200x20
]
Dans cet exemple un style de nom mon-bouton a été créé. Ce style correspond a un composant box avec pour texte « hello », pour image « button-up.png » et une fonte de taille 12 de couleur noire.
Ce style une fois définie peut être utilisé autant de fois que désiré: mais uniquement dans le même bloque. Ainsi l’exemple suivant ne fonctionnerait pas :
REBOL []
view layout [
style
mon-bouton
box "hello" %button-up.png
effect [extend] font [size: 12 color: black
shadow: 0]
mon-bouton "Bonjour" 100x20
]
view layout [ mon-bouton "programmeurs REBOL" 200x20 ]
Les styles peuvent être regroupés en feuilles de style : il s’agit d’objets regroupant la définition de plusieurs styles.
REBOL []
mes-styles: stylize [
bouton-rouge:
button red
bouton-bleu:
button blue
]
view layout [
styles mes-styles
bouton-rouge "Rouge"
bouton-bleu "Bleu"
]
Dans cet exemple une feuille de style de nom « mes-styles » est définie. Le mot-clé VID « styles » permet d’inclure une feuille de style et de rendre utilisable ses styles dans le bloque VID où elle est incluse.
Notre feuille de style « mes-styles » est donc incluse et permet l’utilisation des styles « bouton-rouge » et « bouton-bleu ».
La fonction stylize possède deux raffinements
particulièrement utiles : master et styles.
Le raffinement /master permet d’ajouter les styles de la feuille de style aux styles par défaut : ce qui évite d’avoir à recharger la feuille de style à chaque utilisation de l’un des styles et éventuellement de surcharger un style existant en le redéfinissant.
REBOL []
mes-styles: stylize/master [
bouton-rouge:
button red
bouton-bleu:
button blue
]
view layout
[
;
Plus besoin de charger le style ici …
bouton-rouge "Rouge"
bouton-bleu "Bleu"
]
Le raffinement /styles permet de créer une feuille de style en complétant une feuille de style existante.
REBOL []
mon-style: stylize [ bouton-rouge: button red ]
mes-styles: stylize/styles [bouton-bleu: button blue ] mon-style
view layout [
styles mes-styles
bouton-rouge "Rouge"
bouton-bleu "Bleu"
]
Nous venons de voir comment créer un style : c’est à dire comment réunir sous un nom un composant et une pré définition de ses paramètres. Nous allons maintenant voir comment créer des composants.
Un composant VID comporte :
- un identifiant - une visualisation
- une initialisation - des options
- des événements - des données propres
Avant de débuter ce chapitre il est nécessaire d’exposer le rapport entre VID et les objets de type face, ainsi que de préciser quelques points.
VID est un dialecte REBOL, c’est à dire un langage dans le langage REBOL. Ce langage est interprété par la fonction « layout », laquelle génère un objet de type face (plus exactement un objet dérivé de l’objet system/view/vid/vid-face). Cet objet peut ensuit être affiché grâce à la fonction « view ».
Un objet de type face est une description formelle du composant sous forme d’objet: c’est à dire un objet contenant l’ensemble des paramètres nécessaires à l’affichage du composant (position, dimensions, texte associé, police de caractère, couleur de fond, …) et l’ensemble des paramètres nécessaires à son exploitation (gestion des événements utilisateur et système, données membres spécifiques).
Ainsi « composant VID », « face », et « objet de type face », sont trois termes représentant tous un objet dérivé de l’objet system/view/vid/vid-face et contenant toutes les informations nécessaires au composant.
Les composants VID sont
organisés sous la forme d’un arbre conteneurs / contenus.
Un composant est soit de type simple, soit de type conteneur.
Si un composant est de type simple le champ « pane » de sa face associée est à none : ce qui signifie qu’il ne contient pas de composant.
Cependant si ce composant est un conteneur sont champ « pane » est une série d’objets de type face. Ces faces correspondent aux composants contenus et sont affichés relativement au bord supérieur gauche du conteneur.
REBOL []
panneau-1: layout/offset [H1 "Panneau 1"] 0x0
panneau-2: layout/offset [H1 "Panneau 2"] 0x50
view layout [
conteneur:
box 300x300 white
button "Panneau 1" [conteneur/pane: [panneau-1] show conteneur]
button "Panneau 2" [conteneur/pane: [panneau-2] show conteneur]
button "Panneau 1 et 2" [conteneur/pane: [panneau-1 panneau-2] show conteneur]
]
La méthode la plus simple pour créer un composant (et la seul exposé ici) est de dériver ce composant d’un composant existant. Cette technique se base sur les styles : dont le principe vous a été rappelé précédemment.
Pour comprendre le principe il est nécessaire d’examiner l’objet retourné par la fonction stylize :
REBOL []
mes-styles: stylize [
bouton-rouge:
button red
bouton-bleu:
button blue
]
view layout [un-boutton : button "le boutton"]
probe mes-styles
Etonnement : l’objet « mes-styles » n’est pas une liste du style [composant-de-base options] mais une série composée
- du mot « bouton-rouge »
- d’un objet face
- du mot « bouton-bleu »
- d’un objet face
Les objets face sont associés aux mots les précédant et leur examen montre qu’ils sont dérivés du composant VID button. Les styles sont donc en réalité de nouveaux composants créés à partir d’un composant modèle. L’exemple suivant permet de le constater :
REBOL []
mes-styles: stylize [
bouton-rouge:
button red
bouton-bleu:
button blue
]
mes-styles/bouton-rouge/color: pink
view layout [
styles mes-styles
bouton-rouge "Rose ??!"
bouton-bleu "Toujours bleu !"
]
L’initialisation d’un composant se fait grâce au membre « init » de son objet face. Le contenu de ce bloque de code est exécuté lorsque l’objet est visualisé.
mon-style: stylize [
mon-texte: text "hello"
]
probe mon-style/mon-texte/init
append mon-style/mon-texte/init [print "initialisé"]
Cet exemple affiche le contenu du membre « init » d’un composant texte, puis ajoute un morceau de code affichant le message « initialisé ». L’exécution du bloque « init » peut être testé en affichant le composant :
view layout [
styles mon-style
mon-texte
]
A la suite de cette instruction le texte « initialisé » apparaît dans la console, preuve que le bloque « init » a été exécuté.
Des raffinements
peuvent être associés aux identifiant de composant dans le dialecte VID.
Le composant « text-list » est un bon exemple de composant usant des
raffinements :
REBOL []
view layout [
text-list data ["choix 1" "choix 2" "choix 3"]
]
Les raffinements d’un composant
sont décris dans le membre « words » du composant.
Le membre « words » est une série de la forme [nom-raffinement
fonction-de-traitement …].
REBOL []
mon-style: stylize [
mon-texte: text "hello"
]
mon-style/mon-texte/words: reduce [
'raffinement1 func[composant args] [ prin "Raffinement1, args = " probe args next args]
'raffinement2
func[composant args] [ prin "Raffinement2, args = " probe args next
args]
]
Cet exemple crée un composant de nom mon-texte possédant deux raffinements : rafinement1 et rafinement2. Ces raffinements sont chacun associés à une fonction de traitement prenant deux arguments : le composant sur lequel le raffinement s’applique, et un bloque de donnée. Dans l’exemple les fonctions associées aux raffinements affichent leur bloque de donnée.
view layout [
styles mon-style
mon-texte raffinement1 ["donnée1" "donnée2"] 100x20 raffinement2 "donnée3"
]
L’exécution de ces lignes de code affiche dans la console :
Raffinement1, args = [raffinement1 ["
donnée1" "donnée2"] 100x20 rafinement2 "donnée3"]
Raffinement2, args = [raffinement2
"donnée3"]
Le champ args est donc une liste contenant en première position le nom du raffinement, puis les données pouvant être appliquées au raffinement.
Une question vous vient certainement à l’esprit : pourquoi les fonctions associées aux raffinements se terminent-elles par « next args » ? Cela permet de dire à l’interpréteur de continuer à analyser la suite, tout simplement. Rebol continuera alors jusqu’à la découverte d’un mot.
Chaque objet port possède une fonction méthode « awake ». Cette fonction est automatiquement appelée lorsqu’un événement arrive sur le port et que ce port est surveillé par un « wait ».
REBOL []
port90: open tcp://:90
;
une valeur de retour true indique que l’événement est capturé
port90/awake: func[port] [print "Evénement détecté sur le port 90 !" true]
wait [port90 60]
Cet exemple crée un
objet port écoutant le port TCP 90. A cet objet est associée une fonction
« awake » très simple affichant un message lorsqu’un événement se
produit sur le port.
Un « wait » permet ensuit de surveiller ce port pendant au plus une
minute (60 secondes donc).
Pour déclencher un événement sur le port 90 le plus simple est d’ouvrir votre navigateur Internet et de saisir en adresse « localhost:90 ». Le navigateur essaye alors de contacter le serveur web associé au port 90 de la machine locale et provoque ainsi un événement.
Une autre façon de
surveiller un port est de l’inclure dans la liste des ports à surveiller.
Il s’agit du membre system/ports/wait-list. Lors d’un wait les ports présents
dans cette liste sont surveillés, en plus des ports précisés dans l’argument du
wait.
REBOL []
port90: open tcp://:90
port90/awake: func[port] [print "Evénement détecté sur le port 90 !" true]
append system/ports/wait-list port90
wait [60]
Il est possible de créer des composants VID réagissant au réseau : c’est à dire que ceux-ci sont automatiquement réveillés lors d’un événement réseau. Quel intérêt ? Par exemple pour exploiter un flux, vidéo ou audio. Imaginons un module C capable de capturer un flux vidéo MPEG4 et de l’émettre sur un port réseau. Notre composant une fois inséré afficherait le flux vidéo le tout en restant indépendant du reste du programme.
REBOL []
view layout [
text "En attente de connection ..."
with [
data:
make object!
[
; Ouvre un port sur le canal 90
port: open tcp://:90
; Contiendra une référence au
composant parent
parent: none
; Appelé lorsqu’un client se
présente sur le port 90
wake-up: func [/local client]
[
close port
parent/text: "Client présent"
show parent
]
; Ajout le port d’écoute en
surveillance
append system/ports/wait-list port
; Si un événement se produit sur
le port d’écoute alors wake-up
port/awake: func[port][wake-up do-events]
]
data/parent:
:self
]
]

Ce composant permet d’afficher du texte formaté. Il permet deux types de formatages : un formatage sur la taille avec la balise « size », et un formatage sur la couleur avec la balise « color ».
Ce composant n’exploite ici que la capacité d’initialisation d’un composant. De plus comme vous le verrez, il utilise le mot clé VID « with » pour ajouter des variables et des fonctions à l’objet face du composant.
REBOL [ Author: "PIQUET Johan" Title: "Composant ft : Afficheur de texte formaté"]
stylize/master [
ft:
text with [
D’abord déclare un
nouveau composant de nom « ft » grâce à la fonction
« stylize ».
Le raffinement « master » permet de l’ajouter aux composants par
défaut. Grâce au mot « with » du dialecte VID nous allons rajouter
les variables et les fonctions dont nous aurons besoin dans l’objet face du
composant.
morceaux: copy "across space
-3x-5 "
layer: none
L’idée est ici de
construire la requête VID correspondant au texte formaté, et de stocker le
layer créé. « morceaux » contient alors la requête, laquelle est
construite par morceaux, et layer contiendra la face générée.
interpréter: func[texte /local elt cpt size color résultat]
[
replace/all
texte "^/" " "
replace/all
texte "^-" " "
replace/all
texte ">" "<"
texte: parse/all texte "<"
size:
copy ""
color:
copy ""
résultat: copy ""
cpt: 1
foreach elt
texte [
elt:
trim elt
either
even? cpt
[
replace
elt " " ": "
if
not none? find elt "size" [size: copy elt]
if
not none? find elt "color" [color: copy elt]
if not none? find elt "br"
[append résultat "return "]
]
[
if
not empty? elt
[append
résultat join "text ^"" [elt "^" font [" size
" " color "]
bottom
"]]
]
cpt:
cpt + 1
]
append
morceaux résultat
]
Cette fonction
traduit la description du texte formaté en requête VID. Aucune vérification de
sa syntaxe n’est effectuée, de plus les caractères « < »
et « > » ne peuvent
pas apparaître dans le texte à afficher.
générer-layer: func[/local maximum
elt st] [
layer:
layout to-block morceaux
maximums:
copy []
maximum:
-1
pos-y:
-1
foreach
elt layer/pane [
either
elt/offset/y = pos-y
[st:
second size-text elt if st > maximum [maximum: st]]
[pos-y:
elt/offset/y maximum: second size-text elt append maximums
maximum]
]
insert maximums 0
décalage: 0
pos-y:
-1
foreach
elt layer/pane [
if
elt/offset/y > pos-y [
décalage: décalage + maximums/1
pos-y: elt/offset/y
maximums: next maximums
]
;
Applique un facteur de correction
elt/size/y: maximums/1 + 2 + (0.2 *
(second size-text elt))
elt/offset/y: décalage
]
]
Cette fonction
génère le layer, puis corrige les dimensions et positions des objets contenus
dans ce layer en accédant directement à leurs propriétés. Chacun des morceaux
de texte est englobé par une boîte invisible, et aligné verticalement dans le
bas de ces boîtes. Cette fonction donne à toutes les boîtes d’une même ligne la
même hauteur ce qui a effet d’aligner visuellement tout les morceaux de texte.
init: [
interpréter text
générer-layer
text:
none
pane:
:layer/pane
size:
:layer/size
]
] ; ferme le with
] ; ferme le stylize
L’initialisation du
composant : le contenu du champ « text » est interprété, le
layer est construit et corrigé, puis son contenu pointé par « pane »
mon-texte: { <size 12>Exemple de <size 20><color 200.0.0>composant<br>
<size 32><color 23.5.200>affichant du texte<br><size 12>formaté }
view layout [ft mon-texte]
Un petit exemple d’utilisation. Notre composant héritant du composant « text » la chaîne précisée ici est automatiquement copiée dans le champ « text » du composant.

Ce composant affiche un ensemble d’icônes associées à un ou deux textes. Chacun des icônes réagit aux cliques de souris et exécute une fonction, définissable pour chacun des icônes.
stylize/master [icon-lay: box 400x400 with [
words: reduce ['data func[face args]
[face/data: first next args next args]]
]
Déclare un nouveau composant de nom “icon-lay”. Modifie son
objet associé grâce au mot clé « with ». Le raffinement
« data » est associé au
composant. Celui-ci permet d’inclure le bloque suivant data dans le champ
« data » de l’objet.
créer-icone: func[pos img texte1 texte2 action /local pos1 pos2] [
either empty? texte2
[
pos1:
pos + 32x8
return
reduce [
'at
pos 'image :img
'at
pos1 'h4 texte1 black action 'font [colors: [black black]]
]
]
[
pos1: pos + 32x0
pos2: pos + 32x14
return reduce [
'at
pos 'image :img
'at
pos1 'h4 texte1 black action 'font [colors: [black black]]
'at
pos2 'text texte2 black action 'font [colors: [black black]]
]
]
]
Cette fonction construit le morceau de requête VID
correspondant à un icône. Ici deux cas se distinguent : un seul texte est
associé à l’icône, dans ce cas celui-ci est centré verticalement à coté de
l’image. Ou deux textes sont précisés : dans ce cas les deux textes sont
affichés l’un en dessous de l’autre, le tout à coté de l’icône.
nbr-col: 2
espace: 200x40
data: []
« nbr-col » indique sur combien de colonnes les
icônes sont affichées. « espace » précise la largeur des colonnes
(espace/x) et la hauteur des lignes (espace/y).
init: [
nbr-lgn: 1 + to-integer ((length? data) / nbr-col)
lyr: []
pos: 0x0
for j 1
nbr-lgn 1 [
for
j 1 nbr-col 1 [
if
tail? data [break]
append
lyr créer-icone pos data/1/1 data/1/2 data/1/3 data/1/4
pos: pos + as-pair espace/x 0
data: next data
]
if
tail? data [break]
pos: pos + espace
pos/x: 0
]
pane:
layout lyr
size:
pane/size
pane: pane/pane
]
] ; fin du with
] ; fin du stylize
La fonction
« Init ». Sa fonction consiste ici pour chaque élément de la série
data, de créer et placer l’icône correspondant, grâce à des appels à
« créer-icône ». Les éléments de la série data sont sous la
forme : [image texte-1 texte-2 action-si-clique].
retour.png: load %retour.png
copier.png: load %copier.png
coller.png: load %coller.png
barre: [
[retour.png "Retour" "Page précédante" []]
[copier.png "Copier" "" []]
[coller.png "Coller" "" []]
]
view layout [at
0x0 icon-lay data barre with [ nbr-col: 3 espace: 135x40 ]]
Un exemple d’utilisation. Ici « nbr-col » prend la valeur 3, pour obliger les icônes à être tous alignés horizontalement.
Conclusion
Voilà voilà. Un tutorial de plus dans le monde Rebol. Désormais vous en savez un peu plus sur la création de composants VID, et si vous avez tout compris (je l’espère) vous êtes maintenant capable de programmer vos propres composants !