Aller au contenu

Contrôles utilisateur

Imaginons maintenant que ayons besoin d’un contrôle qui n’existe pas dans X4. Mettons une gauge: Un cercle qui va afficher un pourcentage et un chiffre au centre.

Pour cela, le plus simple est de dériver du SVGComponent. Nous pourrons dessiner le cercle, le pourcentage et le texte directement en SVG.

Voyons voir, d’abord nous dérivons simplement de SVGComponent, nous déclarons nos membres de travail et enfin nous allons dessiner le contrôle.

import {SVGComponent} from "x4js";
class MyGauge extends SVGComponent {
private m_percent: number; // between 0 and 1
constructor( props ) {
super( props );
this.m_percent = props.percent;
this.mkSVGPath( this.m_percent );
}
// construct the svgpath, we will use SVGPathBuilder
mkSVGPath( percent: number ) {
const builder = new SVGPathBuilder( );
// we will render in a 'virtual' rect of 100x100
// the we will let svg render into the correct destination
// rectangle
// 1. background circle
builder.path( )
.arc( 50, 50, 50, 0, 360 )
.fill( "transparent" )
.stroke( "gray", 5 );
// 2. percent circle
builder.path( )
.arc( 50, 50, 50, 0, percent*360 )
.fill( "transparent" )
.stroke( "red", 5 );
// 3. text
builder.text( 50, 50, Math.round(percent*100)+"%" )
.fontSize( 25 )
.textAlign( "center" )
.verticalAlign( "center" )
.fill( "black" )
// 4. make this drawing our content
// we tell svg that the virtual rect is 0, 0, 100, 100
this.setSVG( 0, 0, 100, 100, builder );
}
}

Et voilà notre gauge fonctionne. Nous pouvons simplement l’utiliser comme ceci:

new MyGauge( {
percent: 0.5
} );

Remarque: l’argument du constructeur est un objet, Typescript va analyser qu’il s’agit d’un objet de type SVGProp, argument par défaut d’un composant SVGComponent. Il n’est pas typé et votre éditeur préféré ne va pas vous proposer le membre percent vu qu’il ne le connait pas. Pour résoudre ceci, nous avons 2 solutions:

  1. ajouter un paramètre au constructeur qui est le pourcentage. Le fonctionnement standard de x4 ne recommande pas ceci.
  2. typer le paramètre du constructeur Ce que nous allons privilégier.

Comme nous allons changer le paramètre reçu, X4 va faire un certain nombre de choses pour vous:

  1. il va garder ce qui a été reçu (props) dans un membre qui s’appelle m_props.
  2. il va permettre a l’éditeur de connaître ce paramètre.

Voici la nouvelle définition de MyGauge (nous ignorons la partie de dessin qui ne change pas):

import {SVGComponent,SVGProps} from "x4js";
// new properties
interface MyGaugeProps extends SVGProps {
percent: number; // between 0 and 1
}
// we let x4 known about our props
class MyGauge extends SVGComponent<MyGaugeProps> {
constructor( props: MyGaugeProps ) {
super( props );
this.mkSVGPath( this.m_props.percent );
}
// mkPath( ) {
// }
}

Maintenant l’objectif est de générer un évènement lorsque l’utilisateur tourne la molette de sa souris au dessus de notre gauge afin de modifier le pourcentage affiché.

Le premier point est de capter l’évènement wheel en provenance du browser, pour cela nous allons demander a x4 de lier cet évènement à une méthode que nous créons:

constructor( props: MyGaugeProps ) {
super( props );
this.mkSVGPath( this.m_props.percent );
// we 'capture' wheel event
this.setDomEvent( "wheel", e => this.onWheel( e ) );
}
onWheel( e: MouseWheelEvent ) {
...
}

Ensuite, il va falloir émettre notre évènement: (nous ne rentrerons pas deans le détail de l’évènement wheel du dom, pour cela vous pouvez consulter mdn)

onWheel( e: WheelEvent ) {
// we transform the dom 'wheel' event in an X4 'wheel' event
this.emit( 'wheel', EvMessage('wheel',e.deltaY) );
}

Pour résoudre ce problème, nous allons dire à typescript que notre composant va être source d’évènements de type wheel.

Modifions simplement les définitions qui suivent:

// new event map
interface MyGaugeEventMap extends CEventMap {
wheel: EvMessage; // we will fire events named wheel with a EvMessage parameter
}
// new properties
interface MyGaugeProps extends SVGProps<MyGaugeEventMap> {
percent: number; // between 0 and 1
}
// we let x4 known about our props AND event map
class MyGauge extends SVGComponent<MyGaugeProps,MyGaugeEventMap> {
...
}

Et voilà, typescript est content, l’éditeur aussi car il nous propose de gérer l’évènement wheel, donc nous aussi.

Dernier point, après vous allez pouvoir boire un café bien mérité.

Nous pouvons créer notre nouveau composant

new MyGauge( {
percent: 0.5,
events: {
wheel: e => this.onWheel( e )
}
} );

En soit c’est bien mais j’aimerais que ce soit plus simple:

new MyGauge( {
percent: 0.5,
wheel: e => this.onWheel( e )
} );

Comment réaliser ceci ?

// new event map
interface MyGaugeEventMap extends CEventMap {
wheel: EvMessage; // we will fire events named wheel with a EvMessage parameter
}
// new properties
interface MyGaugeProps extends SVGProps<MyGaugeEventMap> {
percent: number; // between 0 and 1
wheel: EvHandler<EvMessage>; // HERE
}
// we let x4 known about our props AND event map
class MyGauge extends SVGComponent<MyGaugeProps,MyGaugeEventMap> {
constructor( props: MyGaugeProps ) {
super( props );
// AND HERE
this.mapPropEvents( props, "wheel" );
...
}
}