5. Elementos de interação: botões, teclado e mouse#
Programas simples costumam receber um arquivo de entrada, processá-lo até o final e terminam devolvendo todos os recursos que haviam alocado para a máquina. Um programa interativo precisa ficar “dormindo” para evitar consumir recursos da máquina até receber algum comando da(o) usuária(o). Esse comando é executado e, tipicamente, o programa volta a dormir até receber o próximo comando.
Por isso, programas interativos são orientados a eventos. Um evento nesse caso pode corresponder a uma ação do(a) usuário(a), como um clique em um botão, tecla, ou movimentação do mouse. Assim, um programa orientado a eventos deve associar as interrupções geradas por dispositivos de entrada (quando, por exemplo, um botão é pressionado) a rotinas de callback. Em nosso caso, vamos escrever funções em JavaScript específicas para tratar cada tipo de evento.
5.1. Tipos de eventos#
Os eventos podem ser classificados segundo a fonte da interrupção. São duas as principais fontes: usuários e sistema.
Eventos do usuário: como clique do mouse, movimento do mouse, clique em botões da interface gráfica, cliques no teclado etc.
Eventos do sistema: dentre os diversos eventos gerados pelo sistema podemos citar o evento de redesenho ou redisplay. Esse evento é chamado, por exemplo, para redesenhar o conteúdo de uma janela que esteja parcialmente escondida sob outra janela que é movida ou fechada. O sistema também pode gerar eventos quando uma janela muda de tamanho, ou quando é tempo para gerar um novo quadro de uma animação, como veremos na próxima aula.
Nessa aula vamos tratar de alguns elementos básicos de interação que vão nos permitir criar interfaces gráficas simples usando o teclado, mouse e botões.
5.2. Programação orientada a eventos#
Os eventos gerados por usuários tem sua origem em algum dispositivo físico, como o teclado e o mouse. Uma interface é composta por elementos gráficos. Cada um deles precisa ser registrado para receber algum evento. Por exemplo, devemos registar o canvas para receber eventos do mouse, ou um botão virtual para receber um clique. Ao registrar esses elementos, devemos associar a função de callback a ser chamada para tratar o evento.
Os exemplos a seguir mostram alguns programas que criam e registram botões, e também mostram como podemos usar o mouse e o teclado para interagir com o canvas.
5.4. Teclado#
As interfaces gráficas atuais são capazes de apresentar várias janelas e, em muitos aplicativos, como o seu próprio navegador, é comum possuir elementos que aceitam a entrada de texto pelo teclado. Nesses casos, quando há várias janelas que podem receber os eventos do teclado, apenas a janela ativa (ou com foco) recebe esses eventos.
A janela abaixo pode ser acessada diretamente no JSitor pelo link https://jsitor.com/24GJMAR9E.
Clique na aba Browser
e digite algumas teclas. O console exibe mensagens para quando as teclas são pressionadas (onkeydown
) e quando elas são soltas (onkeyup
).
Para nossas aplicações, os eventos do teclado poderiam ser recebidas pelo canvas. No entanto, para conseguir um comportamento mais robusto, vamos associar os eventos do teclado à própria janela do navegador.
O seguinte trecho de código associa as funções de callback à janela para os eventos onkeydown
e onkeyup
.
window.onkeydown = callbackKeyDown;
window.onkeyup = callbackKeyUp;
O tratamento desses eventos é realizado pelas seguintes funções:
// ------------------------------------------------------------------
// funções de CallBack
/**
* Trata o evento keyDown - tecla pressionada. A função
* limpa a tela e desenha uma nova mensagem.
* @param {*} event
* @returns
*/
function callbackKeyDown(event) {
const keyName = event.key;
console.log("tecla Down = ", keyName)
if (keyName === 'Control') {
interface.controlDown = true;
return ;
}
let msg = `Tecla pressionada: ${keyName}`;
if (interface.controlDown) {
msg = `Tecla pressionada: Ctrl + ${keyName}`;
}
ctx.clearRect(0,0,width,height);
ctx.fillText(msg, 20, 65);
}
// ------------------------------------------------------------------
/**
* Trata o evento keyUp - tecla solta. A combinação
* de informações com keyDown permite a combinação de
* teclas como control e alt.
*
* @param {*} event
*/
function callbackKeyUp(event) {
const keyName = event.key;
console.log("tecla Up = ", keyName)
if (keyName === 'Control') {
interface.controlDown = false;
}
}
Observe nesse exemplo que a detecção de combinações de teclas com a tecla Control
é realizado usando a variável interface.controlDown
.
5.5. Mouse#
Clicar e arrastar o mouse enquanto um botão se encontra pressionado permite criar interações poderosas. Nesse exemplo vamos ver como usar os eventos onmousedown
, onmouseup
e onmousemove
para desenhar com o mouse no canvas usando linhas de cores aleatórias.
A janela abaixo pode ser acessada diretamente no JSitor pelo link https://jsitor.com/nPk_BW-9l.
Clique na aba Browser
e clique e mantenha pressionado o botão esquerdo do mouse para desenhar no canvas, arrastando o mouse.
O seguinte trecho de código do arquivo mouse.js
registra as funções de callback no canvas:
canvas.onmousedown = onMouseDownCallback;
canvas.onmouseup = onMouseUpCallback;
canvas.onmousemove = onMouseMoveCallback;
Os eventos onmousedown
e onmouseup
são acionados quando o botão esquerdo do mouse é pressionado e depois solto, respectivamente. Já o evento onmousemove
ocorre sempre que o mouse é arrastado.
As funções que tratam desses eventos são ilustradas abaixo. A ideia é manter o estado do botão, armazenado no atributo interface.buttonDown
. Quando o botão é pressionado, seu estado se torna true
, e essa posição é armazenada. O programa também sorteia uma nova cor para desenhar a linha.
O sorteio da cor é realizado usando a função random
do módulo Math
do JS, que retorna um número real no intervalo [0.0, 1.0]. Esse real é convertido para um valor inteiro usando a função Math.floor
.
Veja a função sorteieCor
no arquivo mouse.js
para ver os detalhes da implementação.
function onMouseDownCallback( e ) {
interface.setXY( e.offsetX, e.offsetY );
interface.buttonDown = true;
ctx.strokeStyle = sorteieCor();
}
function onMouseUpCallback( e ) {
if (interface.buttonDown == true) {
interface.desenheLinha(e.offsetX, e.offsetY);
interface.clear();
}
}
function onMouseMoveCallback( e ) {
if (interface.buttonDown == true) {
interface.desenheLinha( e.offsetX, e.offsetY );
}
}
Com o botão apertado, quando o canvas recebe um evento de movimento, a função onMouseMoveCallback
desenha a linha da última posição registrada até a nova posição. Quando o botão é solto, a função onMouseUpCallback
desenha o último trecho e limpa a interface.
Nesse exemplo, aproveitamos para criar um objeto interface
com atributos e métodos, como no trecho abaixo.
var interface = {
// atributos
mouseX : 0,
mouseY : 0,
buttonDown : false,
// métodos
clear : function() {
this.mouseX = 0;
this.mouseY = 0;
this.buttonDown = false;
},
setXY : function(x, y) {
this.mouseX = x;
this.mouseY = y;
}
};
// Veja a seguir uma forma de estender objetos,
// criando a chave 'desenheLinha' e associando uma função
// veja que a gente poderia ter incluído desenheLinha
// direto na definição acima.
/**
* Método para desenhar uma linha a partir da última posição
* até a nova.
* @param {*} novoX - nova pos X do mouse
* @param {*} novoY - nova pos Y do mouse
*/
interface.desenheLinha = function(novoX, novoY) {
ctx.beginPath();
ctx.moveTo(this.mouseX, this.mouseY);
ctx.lineTo(novoX , novoY);
ctx.stroke();
ctx.closePath();
this.setXY(novoX, novoY);
};
Os atributos mouseX
, mouseY
e buttonDown
são como variáveis que armazenam valores ou propriedades do objeto.
Os métodos clear
, setXY
e desenheLinha
são funções, ou ações que o objeto é capaz de realizar.
Observe que, para acessar internamente os atributos e métodos, é necessário utilizar o prefixo this
.
Novos atributos e métodos podem ser adicionados posteriormente, como no caso do método desenheLinha
.
5.6. Onde estamos e para onde vamos?#
Nessa aula vimos como criar interfaces simples que permitem a entrada de comandos por meio do mouse, teclado e outros elementos do HTML. Como esses comandos podem ser recebidos a qualquer instante, os programas interativos, ao invés de esperar pelos comandos, respondem a eventos gerados pelos dispositivos de entrada. Para implementar um programa baseado em eventos precisamos criar uma rotina de callback para cada evento que precisa ser tratado e associar cada rotina aos eventos no início do programa.
Além das(os) usuárias(os), o próprio sistema precisa também tratar alguns eventos para, por exemplo, redesenhar o conteúdo de uma janela que estava “escondida”, total ou parcialmente, por outra janela que foi fechada ou movida e, no caso de animações, gerar periodicamente um evento para atualizar a animação. Esse é o tema de nossa próxima aula.
5.7. Exercícios#
Escreva um programa de desenhe um quadrado vermelho de lado 50 centrado em um canvas branco (ou qualquer outra cor diferente de vermelho) de dimensão \(400x400\). Seu programa deve permitir que a posição do quadrado seja controlada por 4 botões usando o
<input type="button">
do HTML. Organize os botões na forma de um losango, com os botões com valores “^” e “v” nos cantos superior e inferior do losango, e os botões “<” e “>” na linha intermediária do losango para controlar os movimentos laterais. Use uma barra de intervalo [1,10] (range
) para controlar o passo do deslocamento, ou seja, se a barra estiver em 5, então cada clique move o quadrado de 5 pixels em alguma direção. Parte opcional: permita também que o quadrado seja controlado pelas teclasi
,j
,k
em
.Modifique o programa anterior para controlar a posição do quadrado usando o mouse. Ao clicar com o botão da esquerda sobre o mouse, o centro do quadrado é movido para a posição clicada. Ao manter o botão pressionado e arrastar o mouse, o quadrado deve ser arrastado também.
Modifique o programa anterior para criar um quadrado novo quando o botão direito do mouse é clicado. Invente uma forma também de definir o tamanho do quadrado, por exemplo, usando um segundo clique ou mantendo o botão da direita pressionado e controlar o tamanho arrastando o mouse.
Modifique o programa para desenhar linhas com o mouse para que a grossura da linha seja controlada pelo teclado. Por exemplo, ao clicar na seta “para cima”, a grossura da linha é incrementada e ao clicar na seta “para baixo” a grossura é decrementada caso for positiva.
5.8. Para saber mais#
Para saber mais recomendamos as seguintes leituras: