Arrastando círculos

Neste exemplo vamos usar três funções que o Processing dispara pra nós em eventos do mouse (mousePressed(), mouseReleased() e mouseDragged()) para criar um elemento de interação interessante, um círculo que possa ser arrastado.
A ideia é que você possa adaptar este código para, por exemplo, arrastar pontos de controle de uma curva/polígono, ou então, outros ‘elementos gráficos’ do seu sketch.
Arrastando um círculo
-
Vamos precisar de um indicador de estado (flag) parara saber se o arraste começou, para isso vamos usar a variável global
arrastando, e vamos precisar também duas variáveis para a posição do círculo,x_circuloey_circulo. -
Dentro de
mousePressed()vamos checar se o mouse está sobre o círculo. A estratégia escolhida foi usar a funçãodist()para comparar a distância do mouse até o centro do círculo com o raio do círculo (se a distância for menor que o raio, o mouse está sobre o círculo). Esse tipo de checagem é conhecida em programação de jogos e interfaces como “checagem de colisão”.Neste caso estamos fazendo uma checagem de colisão ponto-círculo (a posição do mouse é o ponto), e para outros casos, outros elementos gráficos, é preciso encontrar a estratégia apropriada. Veja, por exemplo, o código do botão simples para ver como é a checagem de colisão ponto-retângulo.
Caso o mouse esteja dentro do círculo quando for apertado, mudamos
arrastandodeFalseparaTrue. -
Dentro de
mouseReleased()vamos só mudararrastandoparaFalse. Isto significa que quando um botão do mouse for solto acabou qualquer arraste que por ventura estivesse em andamento. Se não havia círculo sendo arrastado, nada muda. -
Dentro de
mouseDragged(), executado quando o mouse é movido apertado, isto é, em ‘arraste’ (drag), se o indicadorarrastandoforTrue, indicando que o círculo estava sob o mouse, vamos atualizar as variáveis globaisx_circuloey_circulocom o deslocamento do mouse. O deslocamento é obtido pela diferença da posição atual do mouse,mouseXemouseY, para a posição imediatamente anterior (previous) que temos compmouseXepmouseY.
arrastando = False
x_circulo, y_circulo = 150, 150
D_CIRCULO = 100 # diâmetro do círculo
def setup():
size(400, 400)
strokeWeight(3)
fill(0)
def draw():
background(0, 0, 200)
if arrastando:
stroke(200, 0, 0)
else:
stroke(255)
ellipse(x_circulo, y_circulo, D_CIRCULO, D_CIRCULO)
def mousePressed(): # quando um botão do mouse é apertado
global arrastando
dist_mouse_circulo = dist(mouseX, mouseY, x_circulo, y_circulo)
raio = D_CIRCULO / 2
if dist_mouse_circulo < raio:
arrastando = True
def mouseReleased(): # quando um botão do mouse é solto
global arrastando
arrastando = False
def mouseDragged(): # quando o mouse é movido apertado
global x_circulo, y_circulo
if arrastando:
# mouseX - pmouseX é o que o mouse foi arrastado em X
x_circulo += mouseX - pmouseX
# mouseY - pmouseY é o que o mouse foi arrastado em Y
y_circulo += mouseY - pmouseY
Arrastando vários círculos

Para acompanhar o próximo exemplo você precisa estar familiarizado com sequências e laços de repetição, uma vez que vamos usar uma estrutura de dados, uma lista, com tuplas dentro, para manter a posição e tamanho de vários círculos, permitindo que qualquer um deles seja arrastado!. Um dos elementos sofisticados deste exemplo é que pegamos para olhar os dados de um círculo por vez, mas ao mesmo tempo, graças à função enumerate() recebemos a informação do índice, da posição desses dados na lista circulos. Usamos esse índice para indicar qual círculo está sendo arrastado.
-
A variável global
arrastandovai manter registro da situação de arraste, como no exemplo anterior, só que agora também indicando o índice de posição de um círculo na listacirculos. Vamos estabelecer queNonesignifica que nenhum círculo está sendo arrastado (um papel feito porFalseno exemplo anterior). -
Na função
mousePressed()vamos checar uma a uma cada tupla da lista, contendo X, Y e diâmetro dos círculos, e caso algum deles esteja sob o mouse, vamos atualizar a variávelarrastandoapontando o índice dessa tupla na lista. O primeiro círculo encontrado interrompe a busca, só um círculo pode ser arrastado por vez. No caso de vários círculos estarem sob o mouse, é selecionado o que vier antes na lista, por conta disso, é selecionado aquele que é desenhado primeiro, o ‘mais de baixo’ entre eles. -
A função
mouseReleased()alteraarrastandoparaNone. Nenhum círculo está sendo arrastado. -
A função
mouseDragged(), casoarrastandonão sejaNone, é criada uma nova tupla com a posição atualizada do círculo e é alterada a lista na posição indicada porarrastando.
arrastando = None # None quer dizer nenhum círculo sendo arrastado
circulos = [] # lista com coordenadas e tamanhos dos círculos
def setup():
size(400, 400)
strokeWeight(3)
fill(0, 200) # preenchimento translúcido
for _ in range(5): # vamos sortear 5 círculos
d = random(30, 100)
x = random(d, width - d)
y = random(d, height - d)
circulos.append((x, y, d))
def draw():
background(0, 0, 200)
for i, circulo in enumerate(circulos):
x, y, d = circulo
if i == arrastando:
stroke(200, 0, 0)
else:
stroke(255)
ellipse(x, y, d, d)
def mousePressed(): # quando um botão do mouse é apertado
global arrastando
# vamos olhar um círculo por vez da lista `circulos`
for i, circulo in enumerate(circulos): # i é o índice na lista
x, y, d = circulo
dist_mouse_circulo = dist(mouseX, mouseY, x, y)
raio = d / 2
if dist_mouse_circulo < raio: # se o mouse estiver dentro
arrastando = i
break # interrompe o laço, não checa mais outros!
def mouseReleased(): # quando um botão do mouse é solto
global arrastando
arrastando = None
def mouseDragged(): # quando o mouse é movido apertado
if arrastando is not None:
x, y, d = circulos[arrastando]
x += mouseX - pmouseX
y += mouseY - pmouseY
circulos[arrastando] = (x, y, d)
Desafio
Note que quando os círculos se sobrepõe, o clique do mouse “captura” o mais de baixo para o arraste. Você conseguiria mudar este comportamento?
Dica: É possível usar reversed() para inverter uma lista, mas o problema é que enumerate() não nos entrega uma lista… é possível converter o “objeto enumerador” entregue por enumerate() em uma lista com list().