Introdução à programação
com Python em um contexto visual


Primeiros passos de orientação a objetos: usando a classe Slider

No começo do curso os principais exemplos de código que vimos se valem, em geral, de estratégias de programação sem “Orientação a Objetos”. Agora veremos como Python, assim como diversas outras linguagens, permite usar esta maneira de programar, pomposamente chamada de “paradigma de programação”: a Orientação a objetos (Object Orientation, por vezes abreviada OO). Python permite misturar elementos de diversos paradigmas.

Vamos começar apresentando os primeiros elementos e vocabulários da orientação a objetos.

Ideias principais e vocabulário

Classe (class), tipo (type) ou uma “categoria de objetos”

Tratando como equivalentes os termos classe e tipo, quando falamos sobre os valores manipulados pelo nosso programa é comum mencionarmos a categoria a que pertencem, isto é, de que tipo ou classe são. Os valores mais fundamentais, ditos primitivos, como os números que manipulamos, são em geral do tipo float (ponto flutuante) ou int (abreviação de integer, inteiros), já os textos são da classe str (abreviação de string, uma cadeia de caracteres). Estruturas como listas são objetos do tipo list e assim por diante. Você pode não ter visto mas o Processing nos entrega os dados de imagens carregadas do disco na forma de um objeto PImage. Cada tipo de objeto pode ter propriedades e funcionalidades específicas (atributos e métodos) que os tornam mais úteis em determinados contextos.

Note que fora os tipos embutidos (acima mencionamos int, float, str e list, mas há vários outros), as classes normalmente seguem a convenção de ter a primeira letra maiúscula no nome, com Slider que veremos mais à frente, e é especialmente recomendável seguir essa convençao para as classes que você criar.

Atributos (propriedades ou campos)

Objetos tem “valores ou propriedades” chamadas de atributos, que podem ser consultados usando a “sintaxe do ponto” (objeto.atributo). Por exemplo, quando carregamos uma imagem no Processing podemos consultar as dimensões dela nos atributos .width e .height:

img = loadImage('a.png')  # uma imagem PNG na pasta /data/
w = img.width  # largura em pixels
h = img.height  # altura em pixels    

Métodos (ou funções associadas aos objetos)

Objetos tem funções associadas, conhecidas como métodos, que podem ser invocadas com a “sintaxe do ponto” (objeto.metodo()). Uma lista em Python, por exemplo, possui diversos métodos e já vimos pelo menos um deles, o .append() que é chamado para incluir elementos na lista.

frutas = ['uva', 'banana']
frutas.append('kiwi')   
print(frutas)  # ['uva', 'banana', 'kiwi']

Instanciar (criar uma nova instância de um objeto)

Fora casos especiais em que podemos criar objetos diretammente no código (como a lista de frutas que acabamos de ver) ou com uma função ajudante, no caso de loadImage(nome_arquivo) que cria um objeto PImage, costumamos criar novos objetos chamando o nome da classe, e isso pode ou não demandar argumentos. No exemplo que veremos a seguir vamos criar um slider.

s1 = Slider(0, 90, 50, 'tamanho')  # mínimo, máximo, valor_inicial, etiqueta

O que ficou de fora

Não vamos ver ainda neste momento em detalhes de como funciona a definição ou criação da classe (a parte que segue class Slider:), que codifica como ela produz e inicializa os objetos ou como são definidos os métodos, nem trataremos do assunto mais avançado “herança” em que uma classe é baseada em outra, recebendo desta parte das suas características.

Exemplo de uso da classe Slider

Veja agora um exemplo comentado de como instanciar e usar objetos da classe Slider que vão servir de interface gráfica para modificar um desenho de uma àrvore.

Note que os objetos slider tem os métodos .position() para locá-los na tela depois de terem sido criados, e o método .update(), que chamaremos dentro da função draw() para fazer o duplo trabalho de desenhar o slider na tela e obter o valor indicado pelo slider naquele momento.

from __future__ import unicode_literals  # para textos com acento sem por `u` antes das aspas

def setup():
    global s1, s2, s3
    global seed
    seed = int(random(1000))
    print(seed)
    size(500, 500)
    s1 = Slider(0, 90, 50, 'tamanho')   # instanciando o slider s1
    s1.position(20, 30)                 # posicionando s1 em x:20 y:30
    s2 = Slider(0, 180, 45, 'ângulo')
    s2.position(190, 30)
    s3 = Slider(0, 10, 0, 'variação aleatória')
    s3.position(360, 30)    
                
def draw():
    global angulo, rndvar
    randomSeed(seed)
    background(240, 240, 200)
    translate(250, 440)    
    tamanho = s1.update()          # atualizando, desenha s1 na tela e obtem um valor
    angulo = radians(s2.update())
    rndvar = s3.update() / 10
    galho(tamanho)   
     
def galho(tamanho):
    reducao = 0.75
    sw = tamanho / 10
    strokeWeight(sw)
    line(0, 0, 0, -tamanho)
    if tamanho > 5:
        pushMatrix()
        translate(0, -tamanho)
        rotate(angulo)
        galho(tamanho * reducao - random(-sw, sw) * rndvar)
        rotate(-angulo * 2)
        galho(tamanho * reducao - random(-sw, sw) * rndvar)
        popMatrix()
        
 # ...       
 # Atenção: precisa colar aqui a definição da classe Slider que está mais abaixo nesta página.
 # ou colar em uma nova aba chamada slider.py e acrescentar `from slider import Slider` no início do sketch

slider

Como é a definição da classe Slider? (a classe por dentro)

Atenção: o código abaixo faz parte do exemplo acima. A boa prática diz que, principalmente para projetos mais complexos, é interessante separar as classes em outro arquivo para facilitar a manipulação e leitura do código. Para isso, basta cria uma nova aba slider, que se torna um arquivo slider.py. Nesse caso é preciso usar a instrução from slider import Slider no começo do seu código. Se não quiser fazer isso, simplesmente cole-o na aba principal, após o código anterior`.

Veja uma primeira versão da classe Slider

class Slider:

    def __init__(self, low, high, default, label=''):
        self.low , self.high = low, high
        self.value = default
        self.label = label
        self.w, self.h = 120, 20
        self.position(20, 20)  # default position

    def position(self, x, y):
        self.x, self.y = x, y
        self.rectx = self.x + map(self.value, self.low, self.high, 0, self.w)

    def update(self):
        if mousePressed and dist(mouseX, mouseY, self.rectx, self.y) < self.h:
            self.rectx = mouseX
        self.rectx = constrain(self.rectx, self.x, self.x + self.w)
        self.value = map(self.rectx, self.x, self.x + self.w, self.low, self.high)
        self.display()
        return self.value
        
    def display(self):
        push()  # combina pushMatrix() and pushStyle()
        resetMatrix()
        camera()
        rectMode(CENTER)
        strokeWeight(4)
        stroke(200)
        line(self.x, self.y, self.x + self.w, self.y)
        strokeWeight(1)
        # stroke(0)
        fill(255)
        stroke(0)
        rect(self.rectx, self.y, self.w / 12, self.h)
        fill(0)
        textAlign(CENTER, CENTER)
        text("{:.1f}".format(self.value), self.rectx, self.y + self.h)
        text(self.label, self.x + self.w / 2, self.y - self.h)
        pop()  # popStyle() and popMat

Páginas relacionadas

Extra: Uma segunda versão da classe Slider

Acrescentando alguns extras e comentários à classe Slider. Permite o uso de sliders em sketchs com 3D.

class Slider:

    template = "{:.1f}"  # para formatar como mostra o valor
    label_align = CENTER

    def __init__(self, low, high, default, label=''):
        """
        Slider needs range from low to high
        and and a default value. Label is optional.
        """
        self.low = low
        self.high = high
        self.value = default
        self.label = label
        self.w, self.h = 120, 20
        self.position(20, 20)  # Pos default

    def position(self, x, y):
        """Define as coordenadas na tela, e calcula rectx, pos. do 'handle'"""
        self.x = x
        self.y = y
        # the position of the rect you slide:
        self.rectx = self.x + map(self.value, self.low, self.high, 0, self.w)

    def update(self):
        """Atualiza o slider e devolve o valor (self.value). Chama display()"""
        # mousePressed moves slider
        if mousePressed and dist(mouseX, mouseY, self.rectx, self.y) < self.h:
            self.rectx = mouseX
        # constrain rectangle
        self.rectx = constrain(self.rectx, self.x, self.x + self.w)
        self.value = map(self.rectx,
                         self.x, self.x + self.w,
                         self.low, self.high)
        self.display()
        return self.value
        
    def display(self):
        """Desenha o slider na tela, usando coordenadas sem transformar"""
        push()         # Combina pushMatrix() e pushStyle()
        resetMatrix()  # push(), seguido de resetMatrix() e camera() permitem...
        camera()       # ... desenhar o slider no sistema de coordenadas original
        rectMode(CENTER)
        # Linha cinza sob o slider
        strokeWeight(4)
        stroke(200)
        line(self.x, self.y, self.x + self.w, self.y)
        # O retângulo, elemento principal da interface do slider
        strokeWeight(1)
        stroke(0)
        fill(255)
        translate(0, 0, 1)
        rect(self.rectx, self.y, self.w / 12, self.h)
        # Mostra o valor (value) atual
        fill(0)
        textSize(10)
        textAlign(CENTER, CENTER)
        text(self.template.format(self.value), self.rectx, self.y + self.h)
        # draw label
        if self.label_align == LEFT:
            textAlign(self.label_align)
            text(self.label, self.x, self.y - self.h)
        else:
            text(self.label, self.x + self.w / 2, self.y - self.h)
        pop()  # equivale a popStyle() and popMatrix()