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

jopiquet@yahoo.fr

 

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)

 

Introduction_ 2

Création de composants personnalisés 2

Styles et feuilles de styles 2

Evolution : de style vers composant 4

Rapport entre VID et les objets face 5

Le champ « pane » d’une face 5

Créer un nouveau composant 6

Permet l’initialisation de son composant 7

Associer des raffinements à son composant 7

Des composants réactifs 8

Awake et surveillance de port 8

Propriété « awake » d’un port 8

Surveillance automatique de ports 9

Un composant réagissant au réseau_ 9

Exemples de composants 10

Simple : un composant affichant du texte formaté_ 10

Légèrement plus complexe … un conteneur d’icônes 13

Le prochain tutorial 16


Introduction


 

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 !

 

 

Création de composants personnalisés


 

Styles et feuilles de styles

 

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"

]

 

 

Evolution : de style vers composant

 

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

Rapport entre VID et les objets face

 

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.

 

Le champ « pane » d’une face

 

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]

]

 

 

Créer un nouveau composant

 

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 !"

]

Permet l’initialisation de son composant

 

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

 

Associer des raffinements à son composant

 

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.

 

 

Des composants réactifs


 

Awake et surveillance de port

 

Propriété « awake » d’un port

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.

 

Surveillance automatique de ports

 

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]

 

 

Un composant réagissant au réseau

 

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

            ]

]

 

 

Exemples de composants


 

Simple : un composant affichant du texte formaté

 

 

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.

 

Légèrement plus complexe … un conteneur d’icônes

 

 

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 !

 

Retour