uiex • dessiner_a_la_souris

TUTORIEL 4 - Dessiner à la souris

Objectif :

Utilisation de l'API "2d graphic" de Scol

Moyens :

  • création de types par utilisation de structure ('struct')
  • fenêtre 2d transparente
  • déplacement d'une fenêtre sans attribut système pour le faire
  • introduction à l'API Scol "2d graphic" (UI Ex)
  • utilisation de différents types de containers (ObjContainer, CompText, CompSlideBar et CompBitmap)
  • fonctions réflexes sur les containers
  • modification de ressources d'un container bitmap
  • précision sur les AlphaBitmaps
  • utilisation de curseur non système
  • couleur au pixel près

STATUT : INCOMPLET

Version : 1.0
Date : octobre 2007 pour le code source, mai 2009 pour le tutoriel
Licence du tutoriel : GNU FDL v1.3 (gnu_fdl)
Licence du code source : GNU/GPL v3 (GPL)
Téléchargement du tutoriel complet : tutoriel 4
Décompressez l'archive et copiez le dossier "4" dans "%scol%/tutoriels/codes/" où %scol% est le dossier répertoire utilisateur de Scol. Ok pour toute version de Scol supérieure à la 4.5.

Ne pas oublier de consulter la doc de référence (liste des fonctions Scol au format html) pour toutes les fonctions Scol croisées dans ce package et dans les suivants ! Cette doc devrait être toujours à portée de clic lorsque vous codez en Scol ;-)



Ce package Scol est chargé grâce au script "launch.scol" présent dans le même dossier. La fonction 'main', située en fin de fichier, est la fonction primaire appelée par ce script.

Cette application comporte deux zones réactives : un écran permettant de dessiner et un espace d'outils pour choisir la taille et la couleur du pinceau. L'interface n'utilise pas les API du système sur lequel elle est exécutée mais les contrôles Scol de l'API dite "2d graphic".



LES CONTAINERS

Les containers sont des objets Scol contenant soit d'autres containers (ce sont les ObjContainer) soit des ressources graphiques (ce sont tous les CompXxx). C'est une approche résolument orientée objet de Scol.

Il existe des librairies écrites en Scol telle que GRAPHICdesign qui poussent plus loin l'approche objet de ces éléments.

Ne seront traités ici que les containers dits réels par opposition aux containers dits virtuels.
Les premiers apportent des éléments graphiques manipulables par l'utilisateur, les seconds apportent une simulation d'éléments manipulables. Ces containers virtuels feront certainement l'objet d'un tutoriel futur :).

De façon générale, il faut un élément père : ce sera toujours un ObjContainer. Celui-ci pourra contenir alors soit d'autres ObjContainer fils, soit des éléments graphiques (CompXxx), soit un mix des deux. On pourra les lier entre eux grâce à des noeuds (ObjNode) et chainer ou non leur propriétés ou certaines de leurs propriétés.
Le container père est souvent associé à une fenêtre classique (ObjWin) mais ceci ne revêt pas un caractère obligatoire.

Une fois au moins un contenant ObjContainer créé, on peut y placer autant d'éléments graphiques qu'on souhaite, les faire apparaître ou disparaître, activer ou désactiver, changer certaines de leurs caractéristiques, modifier leur ressource graphique, etc.

Pour afficher un bitmap simple, il faut choisir un objet CompBitmap et le créer avec la fonction _CRcompBitmap.
Pour un bouton à plusieurs états, ce sera un objet CompRollOver et on le créera avc la fonction _CRcompRollOver
Si on désire insérer un texte, éditable ou non, on se tournera vers un objet CompText créé avec la fonction _CRcompText etc ...
On peut également les superposer pour construire visuellement son interface. Une chose à garder en tête est que le premier élément inséré seera par défaut tout en-dessous et le dernier élément tout au-dessus.

Toutes ces fonctions de création de contenants graphiques ont des points communs :

  • le canal où ils doivent être créés;
  • l'ObjContainer père duquel ils vont dépendre;
  • le noeud (ObjNode) éventuel qui va les chainer entre eux;
  • la position sous la forme d'un tuple d'entiers ([ I I ]) de ces objets dans le référentiel de l'ObjContainer;
  • des flags, notamment pour les montrer/cacher (CO_VISIBLE / CO_HIDE), les activer / désactiver (CO_ENABLE / CO_DISABLE), les comportement de l'élément lorsque l'ObjContainer est redimensionné, ..., plus des flags spécifiques (par exemple la justification du texte pour un CompText, ...).
    Les flags de redimensionnement sont les suivants :
    • OBJ_LW_FLEX : la marge entre le bord gauche de l'ObjContainer et le bord gauche de l'élément est variable (flexible);
    • OBJ_MW_FLEX : la largeur de l'élément est variable;
    • OBJ_RW_FLEX : la marge entre le bord droit de l'ObjContainer et le bord droit de l'élément est variable;
    • OBJ_LH_FLEX : la marge entre le bord haut de l'ObjContainer et le bord haut de l'élément est variable (flexible);
    • OBJ_MH_FLEX : la hauteur de l'élément est variable;
    • OBJ_RH_FLEX : la marge entre le bord bas de l'ObjContainer et le bord bas de l'élément est variable.
  • des flags de "filtres" indiquant si les évènements utilisateurs doivent être passés aux éléments de plans inférieurs ou non. Par exemple, l'utlisateur clique sur un CompBitmap : le clic est enregistré par cet objet mais doit-il être passé aussi à l'ObjContainer auquel il dépend ? Si oui, il faudra l'indiquer ici, sinon on l'omettra.
    Ces filtres sont :
    • OBJ_CONTAINER_CLICK : l'appui sur un bouton de la souris est passé au noeud supérieur;
    • OBJ_CONTAINER_UNCLICK : le relâchement de la souris est passé au noeud supérieur;
    • OBJ_CONTAINER_DBLCLICK : le double-clic est passé au noeud supérieur;
    • OBJ_CONTAINER_KEYUP : le relâchement d'une touche du clavier est passé au noeud supérieur;
    • OBJ_CONTAINER_KEYDOWN : l'appui d'une touche du clavier est passé au noeud supérieur;
    • OBJ_CONTAINER_MOUSEWHEEL : la rotation de la molette de la souris est passé au noeud supérieur;
    • OBJ_CONTAINER_MOVE : le déplacement du curseur de la souris est passé au noeud supérieur;
    • OBJ_CONTAINER_ALLEVENTS : applique tous les filtres en une seule fois.

Une fois les éléments crées mais également lorsqu'ils sont modifiés, il faut rafraichir le container père afin que les changements soient appliqués visuellement. Pour cela, il est impératif d'appeler la fonction _PAINTcontainer : _PAINTcontainer MonContainer;

Exemple :

set MonContainer = _CRcontainerFromObjWin _channel MaFenetre 0 0 480 360 CO_CHILDINSIDE 0x22DD55 "test";
set MonAlphaBitmap = _LDalphaBitmap _channel _checkpack "images/image1.png";
let _GETalphaBitmapSize MonAlphaBitmap = [w h] in
set MonCompBitmap = _CRcompBitmap _channel MonContainer nil [5 10] 
			OBJ_ENABLE|OBJ_VISIBLE|OBJ_LW_FLEX|OBJ_LH_FLEX 
			OBJ_CONTAINER_CLICK|OBJ_CONTAINER_UNCLICK 
			MonAlphaBitmap 10 20 w-10 h-20;
_PAINTcontainer MonContainer;
0;;


CRÉATION DE TYPE

Il existe plusieurs façon de créer un nouveau type Scol. Ici, nous voyons la méthode via la fonction struct. L'autre méthode consiste à employer la fonction typedef.

struct crée un type "multiple" possédant des analogies avec un tableau. Plus précisément, il crée un nouveau type d'après des types déjà existants (ou d'autres types créés via struct ou typedef).

strcut NOM_DU_NOUVEAU_TYPE [ définition ] mkNOM_DU_NOUVEAU_TYPE;;

struct NOUVEAUTYPE [ element1 : Type1, element2 : Type2, element3 : type3, elementN : typeN ] mkNOUVEAUTYPE;;

qui est équivalent à :

struct NOUVEAUTYPE [ 
	element1 : Type1, 
	element2 : Type2, 
	element3 : type3, 
	elementN : typeN 
	] mkNOUVEAUTYPE;;
	
typeof maVariable = NOUVEAUTYPE;;

fun maFonction(e1, e2, e3, en)=
	set maVariable = mkNOUVEAUTYPE [ e1 e2 e3 en ];
	let mkNOUVEAUTYPE [ e1 e2 e3 en ] -> maVariableLocale in
	...
	0;;


FENÊTRE TRANSPARENTE

Il est possible de coder des fenêtres ObjWin (et/ou des containers ObjContainer) transparentes. Soit pour offrir des interfaces non standard (par exemple, en forme de palette de peintre) soit pour laisser filtrer les éléments graphiques qui sont au-dessous, soit un mix des deux. Bien entendu, le rendu et la fluidité dépendent des ressources systèmes, des pilotes de cartes graphiques, etc. Notez deplus que cette transparence n'est active, pour les systèmes sous MS Windows qu'avec Windows 200 et supérieur.

Cette transparence s'effectue en deux étapes :

  1. on donne la possibilité à une fenêtre d'être transparente;
  2. on active et on définit cette transparence.
La 1 se fait lors de la création de la fenêtre en lui ajoutant le drapeau (flag) WN_TRANSPARENCY.
La 2 se fait en appelant la fonction _SETwindowTransparency fenêtre couleur alpha drapeaux.
où fenêtre est l'ObjWin en question, couleur est la couleur exacte en 24 bits pouvant être transparente, alpha est un facteur de transparence compris entre 0 et 255 et drapeaux définit le genre de transparence qu'on désire (WN_TRANS_COLOR pour activer la transparence par couleur et/ou WN_TRANS_ALPHA pour activer le niveau de transparence globale).

Par exemple :

let _CRwindow _channel nil 0 0 500 200 WN_TRANSPARENCY "test de transparence" -> win in
_SETwindowTransparency win 0xFF0000 128 WN_TRANS_COLOR|WN_TRANS_ALPHA;

Il n'est habituellement pas utile d'indiquer un flag supplémentaire à WN_TRANSPARENCY : à lui seul, la fenêtre pourra donc être transparente mais aussi sans barre de titre, ni menu.

Attention : si la fenêtre est entièrement transparente (aucun élément graphique n'est visible), il n'y aura plus d'interactivité directe possible. Par ailleurs, en cliquant dans une zone transparente, on donne le focus à la fenêtre se trouvant en-dessous (logique).


Déclarations

// On crée le type 'INTERF'
struct INTERF = [
				channel : Chn,		// le canal dans lequel lesélémnts sont créés et utilisés
				window : ObjWin,	// l'objet fenêtre, à la base de l'interface
				windoww : I,		// la largeur de la fenêtre
				windowh : I,		// la hauteur de la fenêtre
				containerLeft : ObjContainer,	// le container père gauche de l'interface (zone d'outils)
				containerRight : ObjContainer,	// le container père droit de l'interface (zone de dessin)
				background : ObjBitmap,		// le bitmap de fond
				colormap : AlphaBitmap,		// le bitmap de choix des couleurs
				font : ObjFont		// la police utilisée pour les textes
				] mkINTERF;;

// On crée une variable globale 'interf' qui aura pour type le type que nous venons de créer				
typeof interf = INTERF;;

var mybgcolor = 0x004400;;
var drawsize = 2;;
var drawcolor = 0;;
typeof drawcursor = ObjCursor;;
typeof drawcursorend = ObjCursor;;

proto cbColorMap = fun [CompBitmap ObjContainer I I I I] I;;


Fonctions

Les fonctions createInterfLeft et createInterfRight insèrent les éléments graphiques respectivement du container père gauche (zone de contrôles du pinceau) et du container père droit (zone de dessin).

fun createInterfLeft()=
	let _GETcontainerPositionSize interf.containerLeft -> [_ _ w h] in
	let _CRcompText interf.channel interf.containerLeft nil [5 5] 
						OBJ_ENABLE|OBJ_VISIBLE|CT_LEFT|CT_LABEL OBJ_CONTAINER_CLICK|OBJ_CONTAINER_UNCLICK|OBJ_CONTAINER_MOVE
						w-65 20 "Tutoriel 4" interf.font [0xFFFFFF 0 0 0] nil nil nil
		-> _ in
	let _CRcompBitmap interf.channel interf.containerLeft nil [w-55 5] 
						OBJ_ENABLE|OBJ_VISIBLE OBJ_CONTAINER_MOVE 
						createAlphaBitmapTexte make_rgb 200 180 10 0 50 20 "Quitter"
						0 0 50 20
		-> compQuitter in
	let _CRcompText interf.channel interf.containerLeft nil [5 40] 
						OBJ_ENABLE|OBJ_VISIBLE|CT_LEFT|CT_LABEL|CT_WORDWRAP OBJ_CONTAINER_CLICK|OBJ_CONTAINER_UNCLICK|OBJ_CONTAINER_MOVE
						w-10 40 "Choisissez une couleur et une taille de pinceau pour dessiner dans le cadre de droite !"
						interf.font [0xFFFFFF 0 0 0] nil nil nil
		-> _ in
	let _CRcompBitmap interf.channel interf.containerLeft nil [5 100] 
						OBJ_ENABLE|OBJ_VISIBLE OBJ_CONTAINER_MOVE 
						interf.colormap
						0 0 263 200
		-> compColorMap in
	let _CRcontainerFromObjCont interf.channel interf.containerLeft w-30 275 25 25 
						CO_CHILDINSIDE 0 nil
		-> contMyColor in
	let _CRcompSlideBar interf.channel interf.containerLeft nil [w-28 100]
						OBJ_ENABLE|OBJ_VISIBLE|SLB_MASK OBJ_CONTAINER_MOVE
						createRscSlide
						[0 100 110] SLB_VERTICAL 0 99 10
		-> compSize in
	let _CRcompText interf.channel interf.containerLeft nil [w-35 210] 
						OBJ_ENABLE|OBJ_VISIBLE|CT_RIGHT|CT_LABEL OBJ_CONTAINER_CLICK|OBJ_CONTAINER_UNCLICK|OBJ_CONTAINER_MOVE
						25 40 "2"
						interf.font [0xFFFFFF 0 0 0] nil nil nil
		-> compSizeText in
	let _CRcompText interf.channel interf.containerLeft nil [5 h-50] 
						OBJ_ENABLE|OBJ_VISIBLE|CT_LEFT|CT_LABEL|CT_WORDWRAP OBJ_CONTAINER_CLICK|OBJ_CONTAINER_UNCLICK|OBJ_CONTAINER_MOVE
						w-10 40 "Cliquez et maintenez appuyé la souris sur l'autre cadre pour dessiner ! Jah :)"
						interf.font [0xFFFFFF 0 0 0] nil nil nil
		-> _ in
	(
	 _CBcompBitmapClick compQuitter @cbQuitter 0;
	 _CBcompBitmapClick compColorMap @cbColorMap contMyColor;
	 _CBcompSlideBarValue compSize @cbSize compSizeText;
	 
	 _PAINTcontainer contMyColor;
	 _PAINTcontainer interf.containerLeft;
	 0
	);;

fun createInterfRight()=
	let _GETcontainerPositionSize interf.containerRight -> [_ _ w h] in
	let createAlphaBitmapTexte mybgcolor 0 w h "" -> abmp in
	let _CRcompBitmap interf.channel interf.containerRight nil [0 0] 
						OBJ_ENABLE|OBJ_VISIBLE OBJ_CONTAINER_MOVE 
						abmp
						0 0 w h
		-> compDraw in
	(
	 _CBcompBitmapClick compDraw @cbDrawClick abmp;
	 _CBcompBitmapUnClick compDraw @cbDrawUnclick 0;
	 _PAINTcontainer interf.containerRight;
	 let _LDbitmap interf.channel _checkpack "tutoriels/codes/4/opencross.bmp" -> bmp in
	 set drawcursor = _CRcursor interf.channel bmp 16 16 0 0xFFFFFF;
	 set drawcursorend = _GETcursorWin interf.window;
	 0
	);;

La fenêtre transparente ne possède aucun moyen de la part du système pour la déplacer sur l'écran. Il convient donc de réaliser soi-même un système de callbacks pour récupérer les évènements utilisateurs tels que les clics souris, les relachements souris et les déplacements à l'écran du pointeur de la souris.

Le principe général est le suivant (il se retrouve, au moins dans ses grandes lignes, dans les interfaces de ce type) :

  1. on réinitialise ce système lorsque l'utilisateur appuie sur le bouton de sa souris (au développeur de préciser le cas échéant les actions selon le bouton appuyé). Un booléen est activé.
  2. si l'utilisateur bouge sa souris et que le booléen est activé, l'interface est déplacée en suivant la position du curseur à l'écran. Sinon l'interface reste fixe.
  3. lorsque l'utilisateur relache son bouton de souris, le booléen est désactivé. L'interface ne peut plus se déplacer.

C'est ce principe qui est appliqué ici avec les callbacks cbClick, cbUnclick, cbLeave. Le booléen n'est cependant pas utilisé car l'API 2d graphic les contient implicitement.

La fonction cbMoveforce l'interface à suivre les déplacements du curseur.

fun cbMove(cont, u, x, y, mask)=
	let u -> [xold yold] in
	let _GETwindowPositionSize interf.window -> [winx winy _ _]   in
	let [winx+(x-xold) winy+(y-yold)] -> [xnew ynew] in
	_POSITIONwindow interf.window xnew ynew interf.windoww interf.windowh;;

fun cbClick(cont, u, x, y, btn, mask)=
	_CBcontainerCursorMove interf.containerLeft @cbMove [x y];;
	
fun cbUnclick(cont, u, x, y, btn, mask)=
	_CBcontainerCursorMove interf.containerLeft nil nil;;
	
fun cbLeave(cont, u)=
	_CBcontainerCursorMove interf.containerLeft nil nil;;

fun createMoveInterface()=
	_CBcontainerClick interf.containerLeft @cbClick 0;
	_CBcontainerUnClick interf.containerLeft @cbUnclick 0;
	_CBcontainerCursorLeave interf.containerLeft @cbLeave nil;
	0;;

La fonction createWindow initialise la fenêtre mère et les deux containers pères de l'interface de notre application. Elle est relativement générique dans le sens où elle prend comme argument notamment l'éventuelle fenêtre "grand-mère" (qui, ici, n'existe pas d'où sa valeur à 'nil') et ses dimensions (w et h). On définit les paramètres de la transparence, les callbacks applicables puis la structure que nous avons déclarés au début.

On le constate, on indique d'abord la possibilité de transparence grâce au flag passé lors de la création de la fenêtre puis on définit son activation.
Enfin, la dernière ligne construit une valeur qui sera retournée : ce sera la valeur de la variable interf, de type INTER. Le type de cette fonction est : fun [ Chn ObjWin I I ] INTERF

fun createWindow(ch, father, w, h)=
	let createBkg ch w h -> bmp in
	let _CRwindow ch father 0 0 w h WN_TRANSPARENCY " tutoriel 4" -> win in
	let _CRcontainerFromObjWin ch win 0 0 (w/2)-50 h CO_CHILDINSIDE mybgcolor nil -> contleft in
	let _CRcontainerFromObjWin ch win (w/2)+50 0 (w/2)-50 h CO_CHILDINSIDE mybgcolor nil -> contright in
	(
	 _SETwindowTransparency win 0x000001 nil WN_TRANS_COLOR;
	 _CBwinDestroy win @end bmp;
	 _CBwinPaint win @cbPaint bmp;
	 _PAINTcontainer contleft;
	 _PAINTcontainer contright;
	 _PAINTwindow win;
	 mkINTERF [ch win w h contleft contright bmp nil nil]
	);;

Fonction appelée par le script d'exécution. Les différents éléments de l'interface sont créés méthodiquement et deux variables sont initialisées.

fun main()=
	set interf = createWindow _channel nil 800 400;
	set interf.font = _CRfont interf.channel 16 0 0 "Arial";
	set interf.colormap = _LDalphaBitmap interf.channel _checkpack "tutoriels/codes/4/colormap.png";
	createInterfLeft;
	createInterfRight;
	createMoveInterface;
	0;;

Liste des articles de la rubrique uiex :

dessiner_a_la_souris