7.1 Pasando a Vector

Para Cuis-Smalltalk, creamos Morphic 3, la tercera iteración de diseño de estas ideas, después de Morphic 1 de Self y Morphic 2 de Squeak. Si ya conoces Morphic en Self o Squeak, la mayoría de los conceptos son similares, aunque con algunas mejoras: las coordenadas de Morphic 3 no se limitan a ser números enteros, el tamaño aparente (nivel de zoom) de los elementos no está vinculado a la densidad de píxeles y todos los dibujos se realizan con antialiasing de alta calidad (subpíxel). Estas mejoras son posibles gracias al enorme avance en los recursos de //hardware// desde que se diseñaron Self y Squeak (a finales de los años 80 y 90, respectivamente). Además, el cuidadoso diseño del marco libera a los programadores de Morph de gran parte de la complejidad que se requería, especialmente en lo que respecta a la geometría.

7.1.1 Un primer ejemplo

Comencemos con algunos ejemplos. Lo que queremos es crear nuestros propios objetos gráficos, o Morphs. Una clase Morph forma parte de la jerarquía Morph y suele incluir un método drawOn: para dibujar su aspecto distintivo. Si nos olvidamos por un momento de los ordenadores y pensamos en dibujar con lápices de colores en una hoja de papel, una de las cosas más básicas que podemos hacer es dibujar líneas rectas.

Entonces, iniciemos una ventana del Browser del Sistema y construyamos un objeto de línea recta:

Morph subclass: #LineExampleMorph
   instanceVariableNames: ''
   classVariableNames: ''
   poolDictionaries: ''
   category: 'Morphic-Learning'

En la categoría de métodos drawing añade:

LineExampleMorph>>drawOn: aCanvas
   aCanvas strokeWidth: 20 color: Color green do: [
      aCanvas
         moveTo: 100 @ 100;
         lineTo: 400 @ 200 ].

Ahora, en un Workspace, ejecuta:

LineExampleMorph new openInWorld

Si aparece un mensaje preguntándole si desea instalar y activar la compatibilidad con gráficos vectoriales, responde que sí. Ya está. Ya has creado tu primera clase Morph.

ch07-01-LineDetails

Figura 7.1: Detalle de nuestro morph de línea

El código es evidente, el método drawOn: toma una instancia VectorCanvas como argumento. VectorCanvas proporciona muchas operaciones de dibujo para que las utilicen los morphs. Puedes jugar con las diversas operaciones de dibujo y sus parámetros, y ver el resultado. Si cometes un error y el método drawOn: falla, aparecerá un cuadro de error rojo y amarillo. Después de corregir el método drawOn:, ve al ...menú World → Debug...Start drawing all again... para que tu morph se vuelva a dibujar correctamente.

 CuisLogo ¿Cómo modificarás nuestra línea morph para que se dibuje como una cruz con una extensión de 200 píxeles?

Ejercicio 7.1: Morph cruz

7.1.2 Morph que puedes mover

Es posible que ya hayas intentado hacer clic y arrastrar tu línea, como se puede hacer con las ventanas normales y la mayoría de los demás Morphs. Si no lo has hecho, pruébalo ahora. ¡Pero no pasa nada! El motivo es que nuestro Morph está fijado en un lugar del morph propietario (el WorldMorph). Está fijado porque drawOn: dice que debe ser una línea entre 100  100 y 400  200. Moverlo significaría modificar esos puntos. Una forma posible de hacerlo podría ser almacenar esos puntos en variables de instancia.

Pero ahora sólo deseamos codificar nuestro morph de la manera más sencilla posible y seguir pudiendo moverlo. La solución es convertirlo en una subclase de PlacedMorph, en lugar de Morph.

Para ello, primero evalúa el código siguiente para eliminar todas las instancias de LineExampleMorph:

LineExampleMorph allInstancesDo: [ :m | m
delete]

Ejemplo 7.1: Borrar todas las instancias de un morph dado

A continuación, en la declaración de clase del navegador del sistema para LineExampleMorph, escribe PlacedMorph en lugar de Morph y guarda. Ahora vuelve a ejecutar:

LineExampleMorph new openInWorld

Obtendrá una línea que podrá seleccionar con el ratón y moverla. PlacedMorph añade una nueva variable de instancia llamada location. Si un morph tiene una location, se puede mover modificándola. La location también define un nuevo sistema de coordenadas local. Todas las coordenadas utilizadas en el método drawOn: ahora son relativas a este nuevo sistema de coordenadas. Por eso no necesitamos modificar el método drawOn:. drawOn: ahora indica cómo se debe dibujar el morph, pero no dónde. La location también especifica un posible factor de rotación y escala. Esto significa que las subinstancias de PlacedMorph también se pueden rotar y ampliar.

7.1.3 Morph relleno

Creemos otro morph para tener más diversión.

PlacedMorph subclass: #TriangleExampleMorph
   instanceVariableNames: 'borderColor fillColor'
   classVariableNames: ''
   poolDictionaries: ''
   category: 'Morphic-Learning'

En la categoría de métodos initialization, añade:

TriangleExampleMorph>>initialize
   super initialize.
   borderColor := Color random alpha: 0.8.
   fillColor := Color random alpha: 0.6.

En la categoría de métodos drawing, añade:

TriangleExampleMorph>>drawOn: aCanvas
   aCanvas strokeWidth: 10 color: borderColor fillColor: fillColor do: [
      aCanvas
         moveTo: 0 @ 100;
         lineTo: 87 @ -50;
         lineTo: -87 @ -50;
         lineTo: 0 @ 100 ].

Tómate un momento para comprender ese código, para adivinar qué hará. Ahora ejecuta:

TriangleExampleMorph new openInWorld

Hazlo varias veces y mueve cada triángulo. Cada nuevo triángulo que crees tendrá colores diferentes. Y estos colores no son completamente opacos. Esto significa que cuando coloques tu triángulo sobre otra forma, podrás ver a través de él.

ch07-02-Triangles

Figura 7.2: Varios morphs triangulares, uno decorado con su halo y sistema de coordenadas

 CuisLogo ¿Cómo escribirías un rectángulo móvil con unas dimensiones x,y de 200 por 100? El rectángulo se rellenará con un color translúcido aleatorio y estará rodeado por una fina línea azul.

Ejercicio 7.2: Morph rectángulo

Como hemos aprendido anteriormente, Morphic te ofrece formas adicionales de interactuar con tus morphs. Con un ratón de tres botones o un ratón con rueda, coloca el puntero del ratón (una instancia de HandMorph) sobre uno de tus triángulos y haz clic con el botón central o la rueda del ratón. Si no tienes un ratón de tres botones, sustituye por Command-clic. Obtendrás una constelación de pequeños círculos de colores alrededor de tu morph. Esto se denomina halo del morph, y cada círculo de color es un controlador de halo. Véase Figura 7.2.

En la parte superior izquierda tienes el controlador rojo Remove (Eliminar). Al hacer clic en él, simplemente se elimina el morph del mundo. Pasa el cursor por encima de cada controlador y aparecerá una ventana emergente con su nombre. Otros controladores te permiten Duplicate (Duplicar) un morph, abrir un Menu (Menú) con acciones, Pick up (Recoger) (lo mismo que arrastrarlo con el ratón como hiciste antes). La operación Move (Mover) es similar a Pick up (Recoger), pero no elimina el morph del propietario actual. Más adelante se ofrece más información al respecto. El controlador Debug (Depurar) abre un menú desde el que se puede abrir un Inspector o un Browser jerárquico para estudiar el morph.

También tienes los controles Rotate (Girar) y Change scale (Cambiar escala). ¡Pruébalos! Para utilizarlos, mueve la mano hacia el control, pulsa el botón del ratón y arrástralo. Como habrás adivinado, los controles de rotación hacen girar tu morph alrededor del centro del rectángulo que lo rodea. Los controles de escala controlan el zoom aparente aplicado a tu morph. Tanto la escala como la rotación (y también el desplazamiento, como cuando mueves tu morph) se implementan modificando el sistema de coordenadas interno definido por tu morph. El desplazamiento, la rotación y la escala son números de punto flotante, por lo que no se limitan a números enteros.

Para cambiar el centro de rotación de un Morph, sobreescribe el método rotationCenter según corresponda:

RectangleExampleMorph>>rotationCenter
   ^ 0 @ 0

Observa cómo nuestro rectángulo morph reacciona ahora al controlador de rotación. Aprenderemos a controlar todo esto con código y a animar nuestro morph.

7.1.4 Morph animado

Añadamos dos métodos a nuestro TriangleExampleMorph para dar vida a nuestro triángulo:

En la categoría de métodos stepping, define:

TriangleExampleMorph>>wantsSteps
   ^ true

...y:

TriangleExampleMorph>>step
   fillColor := Color random.
   self redrawNeeded

A continuación, crea algunos triángulos adicionales como hiciste antes.

Esto hará que nuestros triángulos cambien de color una vez por segundo. Pero lo más interesante es editar el método:

TriangleExampleMorph>>stepTime
   ^ 100

...y:

TriangleExampleMorph>>step
   self morphPosition: self morphPosition + (0.4@0).
   self redrawNeeded

Ahora, nuestro morph se mueve diez veces por segundo y se desplaza hacia la derecha a una velocidad de cuatro píxeles por segundo. En cada paso se desplaza 0.4 píxeles, y no un número entero de píxeles. ¡El dibujo con antialiasing de alta calidad nos permite hacerlo! Puedes hacer que se mueva a una velocidad de cuatro veces por segundo y que se desplace 1 píxel cada vez, y verás lo diferente que se ve.

Ahora prueba esto:

TriangleExampleMorph>>step
   self morphPosition: self morphPosition + (0.2@0).
   self rotateBy: 4 degreesToRadians.
   self redrawNeeded

Y aún hay más. Primero, elimina todas las instancias:

TriangleExampleMorph allInstancesDo: [ :m | m delete]

Y modifica estos métodos:

TriangleExampleMorph>>initialize
   super initialize.
   borderColor := Color random alpha: 0.8.
   fillColor := Color random alpha: 0.6.
   scaleBy := 1.1

Acepta scaleBy como una nueva variable de instancia de la clase TriangleExampleMorph.

TriangleExampleMorph>>step
   self morphPosition: self morphPosition + (0.2@0).
   self rotateBy: 4 degreesToRadians.
   self scaleBy: scaleBy.
   self scale > 1.2 ifTrue: [scaleBy := 0.9].
   self scale < 0.2 ifTrue: [scaleBy := 1.1].
   self redrawNeeded

Después, crea un nuevo triángulo:

TriangleExampleMorph new openInWorld

Fíjate en que, cuando el triángulo está haciendo su loca danza, aún puedes abrir un halo e interactuar con él.

ch07-03-AnimatedMorph

Figura 7.3: Morph animado

7.1.5 Morph dentro de morphs

Ahora, probemos algo diferente. Coge uno de tus LineExampleMorph. Con el halo, amplíalo hasta que tenga aproximadamente el tamaño de tu triángulo. Ahora coloca el triángulo sobre tu línea. Abre un halo en el triángulo, haz clic en el controlador Menu (Menú) y selecciona ...embed into (incrustar en) → LineExampleMorph. Esto convierte al triángulo en un submorph de la línea. Ahora, si mueves, escalas o giras la línea, el triángulo también se ajusta.

Puedes abrir un halo en el triángulo. Para ello, haz doble clic con el botón central del ratón sobre él. Con el halo en el triángulo, puedes girarlo o ampliarlo independientemente de la línea. Ten en cuenta también que cuando agarras el triángulo con la mano (sin usar el halo), agarras la línea + el triángulo compuesto. No puedes simplemente arrastrar el triángulo. Para ello, necesitas el halo del triángulo. Usa su controlador Move (Mover)23 para colocarlo sin sacarlo de la línea. Usa su controlador Pick up para cogerlo con la mano y soltarlo en el mundo. Ahora, el triángulo ya no es un submorph de la línea, y los morphs se pueden mover, rotar o escalar de forma independiente.

Pero probemos algo. Volvamos a crear el submorph triangular de la línea. Ahora añadamos el siguiente método a la categoría geometry testing de la clase LineExampleMorph:

LineExampleMorph>>clipsSubmorphs
   ^ true

El dibujo del triángulo se corta exactamente en los límites de la línea. Esto resulta muy útil para implementar paneles de desplazamiento que solo muestran una parte de su contenido, pero también puede tener otros usos.

ch07-04-AnimatedAndClippedSubmorph

Figura 7.4: Un submorph tirangular animado y recortado


Notas al pie

(23)

A estas alturas, es probable que el triángulo se haya desplazado bastante