lunes, 30 de marzo de 2009

Usando FX.deferAction()


Al desarrollar el componente de acordeón de imágenes, se producía un "pestañeo extraño" al momento de solicitar a los elementos que actualizaran su posición. Le dí algunas vueltas al código hasta comprobar que: no era resultado de algo producido por un error en el código, sino por algo relacionado con el refresco de la pantalla (o algo por el estilo ¿?).

Así que recurrí un poco al instinto y a la memoria... y... recorde haber leido algo sobre deferAction. Así que incluí el código que solicita el ajuste de posición, dentro de la llamada de esta función y el problema terminó:

El siguiente código:


public-read var active: Integer on replace {
for (item in pictures) {
item.adjust();
};
};



Pasó a ser este:


public-read var active: Integer on replace {
FX.deferAction(function():Void {
for (item in pictures) {
item.adjust();
};
});
};



Sólo anidé el for dentro de FX.deferAction(function():Void { ... });

Sí descargan la biblioteca memeFX y quitan el FX.deferAction en ImagesAccordion.fx, quizás puedan ver el problema que me llevó a esta solución (no sé si el problema sea reproducible en otras máquinas). Tampoco me quedó claro que produce el problema ni por qué lo resuelve deferAction, voy a buscar información al respecto. Por ahora, sólo comparto el "tip" en caso que alguien tenga un problema similar.

http://code.google.com/p/memefx/

domingo, 29 de marzo de 2009

Por qué incluir canSkip:true en los timelines


Este video muestra la diferencia en el rendimiento de las animaciones gráficas con y sin canSkip:true. Es recomendable incluir este parametro en los KeyFrame que alteran valores (values:) asociados a animaciones y efectos (cambio de posiciones, variación de sombras, difuminado, etc... cualquier efecto o movimiento gradual).

Una persona del grupo JavafxProgramming creo una aplicación Flex que luego replicó en JavaFX. Su problema era el rendimiento de JavaFX, que hacia su aplicación inutilizable. Pidió consejos al foro, pero nada funcionó... hasta que incluyó el canSkip:true en los timelines... recién ahí la aplicación funcionó como debía.

Quizás sería bueno que Sun incluyera el canSkip con true como valor por defecto. Esto puede terminar desprestigiando la plataforma.

En todo caso, sería bueno que todos comparta este detalle o lo tengan muy presente al momento de usar los timelines o de tener problemas con el rendimiento.

domingo, 22 de marzo de 2009

memeFX 0.2.2 : ImagesAccordion con ORIENTACION


Ahora se puede cambiar la orientación del componente de "acordeón", para que las imágenes se desplacen horizontal o verticalmente. Basta con incluir en la definición del componente, un:

orientation: ImagesAccordion.VERTICAL

para que las imágenes se muevan verticalmente.

viernes, 20 de marzo de 2009

Como crear juegos para móvil con Netbeans 6.5


Realmente, es increible lo fácil que resulta crear un juego para teléfono con Netbeans 6.5. De hecho, este tutorial demuestra como crear uno en ¡10 minutos! Vale la pena verlo y tenerlo en cuenta.

Blogger no me permite incrustar el video aquí, así que ahí les va el enlance (se ve mejor a pantalla completa): CLICK AQUI

- Crea tu propio juego para móvil. Hands on Lab6400 (pdf)
- Mobile Game Project.zip
- Guía para el GameBuilder de Netbeans

memeFX v0.2


Ya está disponible la librería incluyendo una versión final de "acordeón de imágenes". La verdad que tiene muchas opciones para personalizar el componente. Además, permite enlazar algunas acciones del usuario con funciones.

Asi, por ejemplo, se podría hacer el que componente comience a tocar un MP3 distinto al pasar el mouse sobre los distintos elementos del "acordeón", y que muestre detalles sobre la música si se hace click sobre ese elemento. Pudiendo además dejar de tocar al abandonar el "acordeón" (al sacar el mouse de encima).

Proyecto memeFX y descarga código fuente, CLICK AQUI


Demo Java Web Start del ImageAccordion, CLICK AQUI

Esto me dio la idea de hacer una variación que permita crear un "acordeón de formulario", es decir, que haya varios formularios con campos, botones, etc. Y que uno pueda pasar de uno a otro igual que en el acordeón de imágenes. Voy a darle vueltas y veré que resulta.

jueves, 19 de marzo de 2009

Timelines de duración variable y el at(..)


Uno de los inconvenientes de trabajar en un ambientea tan nuevo como el JavaFX script, es que no abundan las fuentes de información.

Una de las cosas que no tenía claras (seguramente porque no investigué lo suficiente, o no miré mejor los ejemplos), era cómo se hace que un timeline tenga duración variable.

Bueno, ahora descubrí que diablos es el at(..) en los timeline y cómo se hace para que la duración sea variable.

Primera cosa, cuando uno usa at(), en realidad está usando una "abreviatura" en la manera de definir un timeline. La idea es que sólo sea una opción breve para escribir el código, pero por lo mismo, no es la forma más "potente".

Tomemos este ejemplo:

var x:Number;
Timeline {
repeatCount: 1
keyFrames: [
at (0s) { x => 0.0}
at (3s) { x => 100.0}
]
}.play();

En él, definimos una variable x, que al segundo 0s -de iniciado el timeline- asume un valor de 0.0, luego, x asumirá gradualmente los valores entre 0.0 y 100.0, alcanzando el máximo valor (100.0) en 3s segundos.

El asunto que me faltaba comprender era... ¿como cambiaba el tiempo en at(3s)?, porque no acepta ni variables ni bind dentro de los paréntesis.

Pues resulta que con at(..) no lo vas a conseguir (por lo menos eso entiendo en este momento), la forma correcta y potente de usar las lineas de tiempo es esta:


var dur=3s;
var x:Number;
Timeline {
repeatCount: 1
keyFrames : [
KeyFrame {
time : 0s
values: [x=>0]
},
KeyFrame {
time: bind dur
values: [x=>100]
}
]
}.play();

De hecho, esta forma de definir un timeline permite anidar timelines o agregar un action: en cada KeyFrame, de manera que una acción sea ejecutada cada vez que una etapa de la animación o transición se completa.


var dur=3s;
var dur2=5s;
var x:Number;
Timeline {
repeatCount: 1
keyFrames : [
KeyFrame {
time : 0s
values: [x=>0]
},
KeyFrame {
time: bind dur
canSkip: true
values: [x=>50]
action: function() {
println("completo etapa 1");
};
},
KeyFrame {
time: bind dur2
canSkip: true
values: [x=>200]
action: function() {
println("completo etapa 2");
};
}
]
}.play();

miércoles, 18 de marzo de 2009

Un "acordeón" de imágenes


Este componente aun no está terminado, pero ya lo pueden probar usando el link de más abajo.

Lanzar demo Java Web Start, CLICK AQUI

Código fuente componente y demo, CLICK AQUI


El código para crear un "Acordeón de imágenes" es el siguiente:

var accordion = ImagesAccordion{
width: 650,
height: 350,
lineWidth: 1.0,
lineColor: Color.WHITE
images: [
ImageItem {
id: "moais",
caption: "Moais"
image: Image { url: "{__DIR__}moais.jpg"
}
message: "Easter Island (Rapa Nui) is a Polynesian island in the "
"southeastern Pacific Ocean, The island is a special "
"territory of Chile. Easter Island is famous for its "
"monumental statues, called moai."
messageArea: Rectangle2D {
minX: 30,
minY: 253,
width: 350,
height: 87
}
call: click
},
ImageItem {
id: "anakena",
caption: "Anakena"
image: Image { url: "{__DIR__}anakena.jpg"
}
message: "‘Anakena is a white coral sand beach in Rapa Nui..."
messageArea: Rectangle2D {
minX: 20,
minY: 233,
width: 360,
height: 107
}
messageFont: Font { size: 12
}
call: click
},
ImageItem {
id: "glacier",
caption: "Grey glacier"
image: Image { url: "{__DIR__}grey.jpg"
}
message: "The Glacier is in the south end..."
messageArea: Rectangle2D {
minX: 30,
minY: 270,
width: 320,
height: 70
}
call: click
},
ImageItem {
id: "torres",
caption: "Torres del Paine"
image: Image { url: "{__DIR__}paine.jpg"
}
message: "Spectacular mountain group in Torres del Paine..."
messageArea: Rectangle2D {
minX: 30,
minY: 270,
width: 320,
height: 70
}
call: click
},
ImageItem {
id: "salar",
caption: "Atacama desert"
image: Image { url: "{__DIR__}salar.jpg"
}
message: "The Atacama Desert is a virtually rainless plateau in Chile..."
messageArea: Rectangle2D {
minX: 20,
minY: 237,
width: 380,
height: 103
}
// messageFont: Font { size: 12 }
call: click
},
/* ImageItem {
id: "crayons",
caption: "Crayones"
message: "Recuerdos de la niñez"
image: Image { url: "{__DIR__}crayons.jpg"}
call: click
},*/
ImageItem {
id: "chaiten",
caption: "Chaiten"
image: Image { url: "{__DIR__}chaiten.jpg"
}
message: "Hell on Earth. Chaitén is a volcanic caldera..."
messageArea: Rectangle2D {
minX: 20,
minY: 270,
width: 420,
height: 70
}
call: click
}
]
effect: Reflection { fraction: 0.1,
}

};

function click(image:ImageItem) {
println(image.id);
}

martes, 17 de marzo de 2009

Un pequeño bug en el Timeline y una solución


Estaba haciendo unas pruebas y de vez en cuando notaba un comportamiento extraño, finalmente descubrí que el problema se origina en la reutilización de un Timeline, que estaba definido como:

var timeline= Timeline {
repeatCount: 1
keyFrames: [
at (0s) { currentX => startX }
at (0.5s) { currentX => posX }
]
};

Aunque no es el ideal que haya bugs, es algo habitual en una plataforma nueva (he leído que Adobe Flex tiene demasiados bugs, así que -aunque es consuelo de tontos- me voy a conformar con este pequeño error en JavaFX).

La solución fue tan simple como... no reutilizar el Timeline una y otra vez, sino que recrearlo cuando lo voy a usar:

var timeline:Timeline;

...

timeline = Timeline {
repeatCount: 1
keyFrames: [
at (0s) { currentX => startX }
at (0.5s) { currentX => posX }
]
};
timeline.playFromStart();

El problema específico de mi programa, era que a veces asignaba currentX igual a 0 en at (0s), aunque startX no tenia ese valor.

En todo caso, no es un problema que ocurra con frecuencia (supongo que debe ser un pequeño error el compilador), así que no es necesario hacer esto cada vez que utilicen un Timeline... pero si notan algo extraño como que un elemento asociado de alguna manera a un timeline salta de manera no esperada, intenten esto (por lo menos hasta que el bug sea corregido).

lunes, 16 de marzo de 2009

for... indexof


Este es un detalle muy menor, pero UTIL... típicamente, cuando uno recorre una lista, crea una variable que sirva de puntero y que se incremente en la medida que pasa los elementos de la lista.


var names=["peter","john","james"];

var i=0;
for (name in names) {
println("{name} {i}");
i++;
}

Con JavaFX script esto no es necesario, sólo basta con un:

var names=["peter","john","james"];

for (name in names) {
println("{name} {indexof name}");
}

¿Comó aplicar más de un efecto a un nodo?


Si por ejemplo, se desea hacer difusa una imagen y al mismo tiempo reducir su brillo.

El "truco" está en que casi todos los efectos tienen un INPUT. Eso permite anidar los efectos.


var img = ImageView {
image: Image { url: "{__DIR__}crayons.jpg"},
effect:
GaussianBlur {
input:
ColorAdjust{
brightness: -0.5
}
}
};

Agregando funcionalidad a eventos, sobre la marcha


Mientras desarrollaba el componente de menú flotante, me encontré con esta posibilidad... agregar funcionalidad a un evento... sobre la marcha.

El asunto es este. Si defino un evento onMousePressed de un círculo, ¿como podría agregar funcionalidad a ese evento sin alterar el código y sin crear un nuevo tipo de clase?

Entonces pense... ¿qué tal si primero obtengo la funcionalidad en un evento (onMousePressed) de un objeto y la guardo en una variable? ... Luego, ¿qué tal si reemplazo el evento onMousePressed de ese objeto, con una nueva función en la que primero ejecute la funcion original (guardada en la variable) y luego agrego la nueva funcionalidad?

Bueno, es posible hacerlo...

Para ver una versión Java Web Start, CLICK AQUI

package addfunctions;

import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.Node;
import javafx.scene.input.*;
import javafx.scene.paint.*;
import javafx.scene.shape.*;
import javafx.util.Sequences;

var colors: Color[]=[Color.RED, Color.YELLOW,
Color.BLUE, Color.GREEN];

var mycircle = Circle {
centerX: 80, centerY: 80,
radius: 40
fill: Color.RED
onMousePressed: function(e) {
println("CLICK");
};
}

var mycircle2 = Circle {
centerX: 150, centerY: 150,
radius: 40
fill: Color.RED
onMousePressed: function(e) {
println("CLICK 2");
};
}

function addColorSwitch(node:Node) {
// obtiene la funcion original en el nodo
var origFunc: function(e:MouseEvent):Void=
node.onMousePressed;
// reemplaza evento en el nodo por nuevo evento
node.onMousePressed= function(e:MouseEvent) {
// incluye la funcion original del nodo
origFunc(e);
// agrega funcionalidad
var shape = (node as Circle);
// obtiene color actual en el circulo
var currentColor = shape.fill;
// busca la posicion del color en la secuencia
var pos = Sequences.indexOf(colors, currentColor);
// si es el ultimo colo de la secuencia, vuelve a cero
// si no, al siguiente elemento
if (pos == sizeof colors - 1) then
pos=0 else pos++;
// asigna nuevo color a circulo
shape.fill=colors[pos];
};
};

// agrega funcionalidad a
// onMousePressed en los circulos
addColorSwitch(mycircle);
addColorSwitch(mycircle2);

Stage {
width: 250, height: 250
scene: Scene {
content: [mycircle, mycircle2]
}
}

sábado, 14 de marzo de 2009

MemeFX ... con Popup Menu (experimental)


Estuve haciendo algunos arreglos al experimento del menú flotante que desarrollé hace algún tiempo, y salvo algunos detalles, funciona sin problemas.

Acabo de subir el código de la biblioteca (memeFX), incluyendo este código y un demo, donde se aprecia como usar el menú flotante.

Para ver el demo (Java Web Start), haz CLICK AQUI

Para visitar el sitio web del proyecto, CLICK AQUI

Algo de código:

Primero, se crea un tipo de menú colgante... lo que puede incluir cambios a la apariencia del menú (colores, grosores, rotación, transparencia, esquinas redondeadas, etc.).


var popupCircle = PopupMenu{
corner: 20, padding: 8, borderWidth: 4,
opacity: 0.9, animate: true


content: [
MenuItem { text: "Animar circulo!",
callNode
: node },

MenuItem { text: "Mostrar nombre y posicion",
callPosId: positionId }

]
};

El tipo de función declarada para el Item (la función que ejecuta al ser seleccionado un item del menú), tiene que ver con los parámetros que retorna el menú al ser seleccionada la opción:

call = no retorna parametros, la función es declarada como:

function algo():Void { ... };

callId = retorna el ID del objeto sobre el que se utilizó el menú, la función es declarada como:

function algo(id:String):Void { ... };

callPos = retorna el MouseEvent que originó la aparición del menú. Permite, por ejemplo, obtener la posición del mouse al lanzar el menú (¿qué otros datos se pueden obtener de MouseEvent?). La función es declarada como:

function algo(e:MouseEvent):Void { ... };

callNode = retorna el Nodo sobre el que se utilizó el menú flotante. Permite manipular directamente el objeto (en este ejemplo, se anima el tamaño del círculo seleccionado). Si se quiere utilizar sobre un tipo particular de nodo, es necesario hacer un casting al tipo de objeto apropiado... ejemplo, si el tipo de objeto es un campo de texto: (node as SwingTextField).text="{node.id}" . La función es declarada como:

function algo(node:Node):Void { ... };

callPosId = retorna el MouseEvent que originó la aparición del menu y el ID del objeto sobre el que se utilizó (puede ser útil para saber en que parte del objeto se hizo click). La función es declarada como:

function algo(e:MouseEvent, id:String) { ... };

callPosNode = retorna el MouseEvent que originó la aparición del menu y el Nodo sobre el que se utilizó (puede ser útil para saber en que parte y sobre que objeto se hizo click). La función es declarada como:

function algo(e:MouseEvent, node:Node) { ... };

En este ejemplo agregaré dos círculos:

var circle1 = Circle {
id: "Cicle 1"
centerX: 100, centerY: 100
radius: 40, fill: Color.RED
};

var circle2 = Circle {
id: "Cicle 2"
centerX: 220, centerY: 300
radius: 80, fill: Color.YELLOW
};

Los ID son opcionales, pero pueden ser usados para reconocer sobre que objeto se lanzó el menú flotante (callId y callPosId).

A continuación, enlazo el menú con los círculos:

popupCircle.to(circle1);
popupCircle.to(circle2);

Se agregan las rutinas que serán ejecutadas por las opciones del menú (el código pudo ser incluido al definir el menú colgante).

function node(node:Node):Void {
Timeline {
repeatCount: 4
autoReverse: true
keyFrames: [
at (0s) {node.scaleX => 1.0;
node.scaleY => 1.0 }

at (0.2s) {node.scaleX => 1.4;
node.scaleY => 1.4}

]
}.play();
};

function positionId(e:MouseEvent, id:String):Void {
println("Mouse at {e.x},{e.y} over {id}");
}

Finalmente, se agregan los círculos y los menús al Stage:

Stage {
scene: Scene {
content: [
circle1,
circle2
PopupMenu.activateMenus()
]
}
}

viernes, 13 de marzo de 2009

MemeFX ... mi biblioteca para JavaFX


El nombre puede parecer "curioso", pero tiene una razón "intelectualoide" de ser...

Meme (se pronuncia MIM), responde a toda una teoría sobre la capacidad de las ideas para replicarse y evolucionar por si mismas, como seres vivos. La idea es que el diseño puede surgir del caos (suena a código abierto, ¿no?).

Se supone que a través de un proceso evolutivo y se selección natural, se consigue un resultado superior al original... ahora, como este proyecto es código abierto, y por lo mismo, fácilmente replicable y modificable (puede mutar y evolucionar libremente)... entonces, podría convertirse en un meme.

Veamos si el EFECTO MEME (MemeFX) ocurre con él... ;)

En este momento la biblioteca contiene sólo tres componentes, pero, no por eso no son útiles ;D ... la idea es que con el tiempo vaya creciendo. Tengo algunas ideas como incorporar controles para un menu flotante gráfico (si han mirado el blog, por ahí ya hice un intento), un componente de acordeón, componentes que permitan fácilmente asignar drag and drop a los elementos y definir areas para ser arrojados, un componente para simplificar que los applets sean arrastrables desde el navegador al escritorio (básicamente que genere el área de arrastre solo, porque de por sí es simple habilitar esa funcionalidad), etc. Las ideas son bienvenidas.

Por ahora, aquí está el código fuente de la biblioteca, demos Java Web Start con sus respectivos códigos fuentes y la documentación sobre como utilizarlos y personalizarlos.

Ojalá les sean de utilidad y puedan unirse al proyecto quienes se sientan motivados.

http://code.google.com/p/memefx/

Aquí hay más info sobre los Memes, si alguien tiene curiosidad.

miércoles, 11 de marzo de 2009

Como guardar datos persistentes en una aplicación JavaFX Web Start


Esto permite que incluso una aplicación JWS -sin autorización para acceder a los recursos locales- almacene objetos y datos localmente, utilizando un mecanismo parecido a los "cookies" de los navegadores web.

Para el desarrollo del proyecto, hay que incluir el archivo javaws.jar entre las bibliotecas del proyecto, de manera de poder compilar el código. El archivo javaws.jar es standard en las distribuciones de Java 6 y posteriores, así que no es necesario incluirlo en la distribución de tu aplicación.

Comenzamos incluyendo los import en la aplicación:

import java.io.*;
import java.net.URL;
import javax.jnlp.*;

A continuación declaramos dos variables:

var persistenceService: PersistenceService = null;
var codebase: URL = null;

En el bloque INIT de la aplicación JavaFX, incluimos:

init {
try {
var basicService: BasicService =
ServiceManager.lookup("javax.jnlp.BasicService")
as BasicService;
codebase = basicService.getCodeBase();
persistenceService =
ServiceManager.lookup("javax.jnlp.PersistenceService")
as PersistenceService;
} catch (use:Exception) {
}

};

Eso inicializa codebase y persistenceService.

En este ejemplo, declaro una clase Java que contendrá los datos que deseo almacenar persistentemente en el computador del usuario:

import java.io.Serializable;

class persistentData implements Serializable {

public float x;
public float y;
public float width;
public float height;

persistentData (float x, float y, float width, float height) {
this.x=x;
this.y=y;
this.width=width;
this.height=height;
};
}

Ahora, puedo incluir el código para almacenar en el repositorio local una instancia de mi clase persistentData:


var cache = new persistentData (x, y, width, height);
try {
var fc: FileContents =
persistenceService.get(codebase);
var oos: ObjectOutputStream =
new ObjectOutputStream
(fc.getOutputStream(true));
oos.writeObject( cache );
oos.flush();
oos.close();
} catch (e:Exception) {
}


Y para recuperar el objeto desde el repositorio:

var cache:persistentData;
try {

var appSettings: FileContents = null;
appSettings = persistenceService.get(codebase);
var ois: ObjectInputStream = new ObjectInputStream
( appSettings.getInputStream() );
cache = ois.readObject() as persistentData;
ois.close();
} catch (fnfe: FileNotFoundException ) {
try {
var size = persistenceService.create(codebase, 1024);
} catch (ioe:IOException) {
}
} catch (e:Exception) {
}


Voila!

Nota: el número 1024 en la línea persistenceService.create(codebase, 1024) indica el espacio (en bytes) que la aplicación está solicitando al repositorio para almacenar datos.

Si vas a ejecutar más de una aplicación Java Web Start desde una misma ruta (el mismo directorio en las URLs), es necesario agregar algo al codebase de manera de distinguir a ambas aplicaciones, porque si no, van a "pisarse" los datos almacenados.

¿Qué usar? ¿Color o Paint?


Desarrollando el componente de "reloj" (no sé como llamarlo en español, en inglés les dicen Gauge), al incluir la posibilidad que el usuario pueda cambiar el color de fondo del "reloj" me topé con este dilema.... ¿Color o Paint?

Obviamente que yo opté por el Paint en el caso de este componente, porque permite pasar como un parametro a una función no solo un "color", sino una "gradiente" de colores, ya sea radial o lineal.

public function customBackground(
color:Paint, effect:Effect):Node {
Circle {
centerX: 150,
centerY: 150
radius: 135
fill: bind color
}
};

La ventaja de usar Paint es que luego uno puede llamar la función (en el caso de mi componente) con :

background: control2.
customBackground(Color.DARKBLUE)

o pasarle una "gradiente":

background: control3.customBackground(
RadialGradient {
centerX: 56, centerY: 56
focusX: 0.1, focusY: 0.1
radius: 103
proportional: false
stops: [
Stop {
color: Color.WHITE
offset: 0.0
},
Stop {
color: Color.DARKRED
offset: 1.0
}
]
})

lo que permite mayor personalización....

Es un detalle menor, pero al menos es bueno tener presente que existe la opción, especialmente si se están desarrollando componentes y se desea dar flexibilidad a quienes los utilicen.

lunes, 9 de marzo de 2009

Pronto liberaré este nuevo componente


No crean que abandoné mi pasión por el JavaFX ;D ... sólo he estado dedicado a desarrollar este componente que voy a liberar como código abierto.

JAVA WEB START DEMO

Sería genial si alguien con algo de tiempo me ayuda a integrar estos componentes (la perilla, el stagecontroller y este nuevo componente) en el proyecto JFXtras (CLICK). Mi problema es de falta de tiempo.

¿Algún voluntario?





package democombo;

import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.Group;
import javafx.scene.paint.*;
import javafx.scene.text.*;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Rectangle;
import org.combofx.gauge.*;
import org.combofx.stage.*;
import org.combofx.knob.*;


/** Stage controller */
var cs = controlStage{
width: 1024, height: 600
initialPosition: controlStage.CENTER
checkMinHeight: true, minHeight: 600
checkMinWidth: true, minWidth: 1024
stickyBorders: true, limitBorders: true
};

// gauge component #1
var gauge1 = Gauge {
ui: BasicGauge{ }
primaryLabel: "Km/H"
secondaryLabel: "Speed"
secondaryLabelFont: Font { size: 15 };
min: 0.0, max: 100.0
scaleX: 0.72, scaleY: 0.72
translateX: -35, translateY: -42
speed: Gauge.SLOW
initialValue: 0
value: bind knob1.value
};

/** knob input #1 */
var knob1 = Knob {
ui: BasicKnob{ }
dialLinesNumber: 31
dialLinesLongEvery: 3
minAngle: 0, maxAngle: 720
translateX: 160, translateY: 160
min: 0, max: 100
scaleX: 0.5, scaleY: 0.5
value: 50
};


// gauge component #2
var control2 = CornerLLGauge{};
var gauge2 = Gauge {
ui: control2
primaryLabel: "Km/H"
secondaryLabel: "Current\nSpeed"
min: 0.0, max: 100.0
dialNumsVisibleEvery: 2
translateX: 300
speed: Gauge.SLOWEST
initialValue: 0
value: bind knob2.value
dialLongLinesColor: Color.YELLOW
// custom background color
background: control2.customBackground(Color.DARKBLUE)
};

/** knob input #2 */
var knob2 = Knob {
ui: BasicKnob{ }
dialLinesNumber: 31
minAngle: 0, maxAngle: 720
dialLinesLongEvery: 3
translateX: 470, translateY: 160
min: 0, max: 100
scaleX: 0.5, scaleY: 0.5
value: 50
};

// gauge component #3
var control3 = RectGauge{ };
var gauge3 = Gauge {
ui: control3
primaryLabel: "Km/H"
primaryLabelColor: Color.ORANGE
secondaryLabel: "Speed"
min: -100.0, max: 100.0
dialNumsLargeEvery: 2
largeNumberFont: Font { size: 16, oblique: true }
smallNumberFont: Font { size: 10, oblique: true }
translateX: 600, translateY: -7
scaleX: 0.94, scaleY: 0.94
speed: Gauge.INMEDIATE
initialValue: 0.1
value: bind knob3.value
dialLongLinesWidth: 4
internalLongRadDial: 118
dialShortLinesColor: Color.YELLOW
dialShortLinesWidth: 0.5
// custom background radial gradient
background: control3.customBackground(
RadialGradient {
centerX: 56, centerY: 56
focusX: 0.1, focusY: 0.1
radius: 103
proportional: false
stops: [
Stop {
color: Color.WHITE
offset: 0.0
},
Stop {
color: Color.DARKRED
offset: 1.0
}
]
})
};

/** knob input #3 */
var knob3 = Knob {
ui: BasicKnob{ }
dialLinesNumber: 31
minAngle: 0, maxAngle: 720
dialLinesLongEvery: 3
translateX: 830, translateY: 160
min: -100, max: 100
scaleX: 0.5, scaleY: 0.5
value: 0
};

// gauge component #4
var control4 = SquareGauge{};
var gauge4 = Gauge {
ui: control4
decorationVisible: false
primaryLabel: "Km/H"
secondaryLabel: "Speed"
secondaryLabelFont: Font { size: 15 }
valueColor: Color.CYAN
valueFont: Font {
size: 19,
oblique: true
name: "Verdana"
}
min: 0.0, max: 100.0
scaleX: 0.72, scaleY: 0.72
translateX: -35, translateY: 250
speed: Gauge.FAST
initialValue: 0
value: bind knob4.value
// custom gauge background color
background: control4.customBackground(Color.DARKGREEN)
// highlighted ranges
highlightRange: [
Gauge.range {
start: 0, end: 70
color: Color.GREEN
opacity: 0.8
},
Gauge.range {
start: 70, end: 90
color: Color.YELLOW
dialLongLinesColor: Color.YELLOW
dialShortLinesColor: Color.YELLOW
opacity: 0.5
}
Gauge.range {
start: 90, end: 100
color: Color.RED
dialLongLinesColor: Color.RED
dialShortLinesColor: Color.RED
opacity: 0.5
}
]
};

/** knob input #4 */
var knob4 = Knob {
ui: OrangeKnob{}
dialLinesNumber: 31
minAngle: 0, maxAngle: 720
dialLinesLongEvery: 3
translateX: 160, translateY: 450
min: 0, max: 100
scaleX: 0.5, scaleY: 0.5
value: 50
};

// gauge component #5
var control5 = SmallGauge{};
var gauge5 = Gauge {
ui: control5
primaryLabel: "Km/H"
secondaryLabel: "Speed"
min: 0.0, max: 100.0
dialNumsVisibleEvery: 2
translateX: 295, translateY: 290
speed: Gauge.FASTEST
initialValue: 0
value: bind knob5.value
valueColor: Color.RED
dialLongLinesColor: Color.RED
shadowColor: Color.RED
shadowX: 2, shadowY: 2
// custom background radial gradient
background: control5.customBackground(
RadialGradient {
centerX: 56, centerY: 56
focusX: 0.1, focusY: 0.1
radius: 103
proportional: false
stops: [
Stop {
color: Color.WHITE
offset: 0.0
},
Stop {
color: Color.BLACK
offset: 1.0
}
]
})
// highlighted gauge range
highlightRange: [
Gauge.range {
start: 80, end: 100,
color: Color.RED
dialLongLinesColor: Color.RED,
dialShortLinesColor: Color.RED
internalRad: 120, externalRad: 123
}
]
};

/** knob input #5 */
var knob5 = Knob {
ui: OrangeKnob{}
dialLinesNumber: 31
minAngle: 0, maxAngle: 720
dialLinesLongEvery: 3
translateX: 470, translateY: 450
min: 0, max: 100
scaleX: 0.5, scaleY: 0.5
value: 50
};

/** knob input noise #6 */
var knob6noise = Knob {
ui: OrangeKnob{ }
dialLinesNumber: 11
dialLinesLongEvery: 2
translateX: 730, translateY: 450
min: 0, max: 1
scaleX: 0.5, scaleY: 0.5
value: 0
};

// gauge component #6
var control6 = FlatRectInvGauge{};
var gauge6 = Gauge {
ui: control6
primaryLabel: ""
secondaryLabel: "Km/H"
secondaryLabelColor: Color.ORANGE
secondaryLabelFont: Font { size: 19 }
min: 0, max: 1
dialNumsVisibleEvery: 2
translateX: 600, translateY: 280
scaleX: 0.94, scaleY: 0.94
speed: Gauge.FAST
initialValue: 0.1
value: bind knob6.value
dialLongLinesWidth: 4
internalLongRadDial: 118
dialShortLinesColor: Color.ORANGE
dialShortLinesWidth: 0.5
noise: true, noiseLevel: bind knob6noise.value
reverseScale: true
dialNumsIntegerOnly: false
dialNumsDecimals: 1
useDots: true
displayVisible: false
decorationVisible: false
shadowX: 0, shadowY: 0
// highlighted gauge ranges
highlightRange: [
Gauge.range {
start: 0, end: 0.5
dialNumsColor: Color.ORANGE
},
Gauge.range {
start: 0.6, end: 0.8
color: Color.DARKRED
opacity: 0.3
internalRad: 0
},
Gauge.range {
start: 0.8, end: 1.0
color: Color.DARKRED
opacity: 0.6
internalRad: 0
}
]
// custom gauge background
background: Group { content: [
Rectangle {
x: 10, y: 10
arcHeight: 20, arcWidth: 20
width: 260, height: 180
fill: Color.RED
}
Circle {
centerX: 140, centerY: 52
radius: 125
fill: Color.DARKRED
opacity: 0.5
clip: Rectangle {
x: 15, y: 35,
width: 250, height: 180
arcHeight: 20, arcWidth: 20
}
}]
}
};

/** knob input #6 */
var knob6 = Knob {
ui: OrangeKnob { }
dialLinesNumber: 31
minAngle: 0, maxAngle: 720
dialLinesLongEvery: 3
translateX: 830, translateY: 450
min: 0, max: 1
scaleX: 0.5, scaleY: 0.5
value: 0.5
};


Stage {
// bind Stage (window) to the controller
width: bind cs.width with inverse
height: bind cs.height with inverse
x: bind cs.x with inverse
y: bind cs.y with inverse

scene: Scene {
content: [gauge1, knob1, gauge2, knob2, gauge3, knob3,
gauge4, knob4, gauge5, knob5, gauge6, knob6, knob6noise]
}
}