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.
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.
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.
¿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
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.
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.
Figura 7.2: Varios morphs triangulares, uno decorado con su halo y sistema de coordenadas
¿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.
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.
Figura 7.3: Morph animado
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.
Figura 7.4: Un submorph tirangular animado y recortado