DRAW - Desenhos e animação programáticos



Executar desenhos usando ferramentas de programação Red (loops, matemática, ramificações, etc.) requer alguma estruturação do script. Eu sugiro a seguinte estrutura como regra geral:


Red [needs: view]

draw-changing: function [  ]

view compose/deep/only [

       face focus

       draw[commands (arguments)]

       on-event [ draw-changing ]

]


draw-changing - Estas são as funções a serem chamadas de um event para fazer cálculos e, em seguida, alterar o campo "draw" do objeto da face. Você deve alterar este campo daqui porque não pode mudá-lo de dentro do bloco do dialeto draw.

face focus -  Alguns events (como key) parecem só ser gerado se houver  focus nas faces como base ou box, cuidado.

draw - Executa o dialeto draw. Qualquer argumento calculado (variável) deve estar entre parênteses para ser computado por compose/deep/only.  

on-event - Chama a função apropriada de draw-changing, considerando o tipo de evento.


Animação simples:


Red [needs 'view]


position: 0x0        

       

update-canvas: func [] [        

   position: position + 1x1

   canvas/draw:  reduce ['circle position 5]

]


view [

   canvas: base 100x100  rate 25

   on-time [update-canvas]

]



Explicando o código:


Red [needs 'view]


{ "position" é o centro do círculo que vai se mover

aqui ele está no canto superior esquerdo}


position: 0x0        


{a função "update-canvas" faz todo o

processamento necessário e "passa" a

rotina de draw para o objeto de "canvas". 

Observe três coisas no código abaixo:

1- Sim, draw é um campo de um objeto!

2- Você deve usar "reduce" para enviar o

valor atual da posição;

3- Deve haver um apóstrofo antes

"circle". "circle" é um comando do

dialeto draw, e por isso deve ser passado "como é"}


       

update-canvas: func [] [        

   position: position + 1x1

   canvas/draw:  reduce ['circle position 5]

]


{A rotina do view cria uma base chamada

"canvas" que se atualiza 25 vezes

por segundo}



view [

   canvas: base 100x100  rate 25

   on-time [update-canvas]

]



Para mostrar que a canvas é um objeto!, feche a visualização gráfica depois uns instantes, mas deixe o console aberto. Digite ? canvas no console. Você vai ter:


>> ? canvas

CANVAS is an object! with the following words and values:

    type             word!         base

    offset           pair!         10x10

    size             pair!         100x100

    text             none!         none

    image            none!         none

    color            tuple!        128.128.128

    menu             none!         none

    data             none!         none

    enabled?         logic!        true

    visible?         logic!        true

    selected         none!         none

    flags            none!         none

    options          block!        length: 6  [style: base vid-align: top at-o...

    parent           object!       [type offset size text image color menu dat...

    pane             none!         none

    state            none!         none

    rate             integer!      25

    edge             none!         none

    para             none!         none

    font             none!         none

    actors           object!       [on-time]

    extra            none!         none

    draw             block!        length: 3  [circle 37x37 5]

    on-change*       function!     [word old new /local srs same-pane? f saved]

    on-deep-change*  function!     [owner word target action new index part]



No próximo exemplo, em vez de alterar o bloco de draw , vamos fazer um append  com novos comandos de draw . O resultado é que todos os desenhos anteriores são mantidos (na verdade, são redesenhados, mas ...), criando um rastro de desenhos:



Red [ needs 'view ]

position: 0x0

command: []        ; initialized to prevent error.

               

update-canvas: func [] [

   position: position + 1x1

   append command reduce ['circle position 5]

   canvas/draw:  command

]


view [

   canvas: base 100x100  rate 25

   on-time [update-canvas]

]



Note que se você fechar a janela gráfica e digitar ? canvas no console, você verá um longo bloco como o valor de draw:


>> ? canvas

...

    draw     block!   length: 84  [circle 1x1 5 circle 2x2 5 circle 3x3 5 circle 4x4 5 ...

...



Um exemplo de programa desenhado:


Red [needs: view]


drawcircles: does [

   command: [pen red fill-pen blue]

   repeat x 8 [

       repeat y 8 [

           position:(x * 11x0) + (y * 0x11)

           append command reduce ['circle position 4]

       ]

   ]

           canvas/draw:  command

]


view [                

   canvas: base 100x100

   do [drawcircles]

]



Você poderia ter escrito o programa acima sem usar uma função, mas você precisaria do refinamento no-wait para view , assim:


Red [needs: view]


command: [pen red fill-pen blue]


view/no-wait [                

   canvas: base 100x100

]

{o refinamento "no-wait" acima permite

script criar a view (base) e, em seguida

continuar direto para o bloco do"repeat".

Sem "no-wait", o script permaneceria no

bloco do "view"}


repeat x 8 [

   repeat y 8 [

       position:(x * 11x0) + (y * 0x11)

       append command reduce ['circle position 4]

   ]

]


canvas/draw:  command

probe command {apenas para mostrar o que foi enviado para draw.

Você deve usar o probe em vez de print, porque print

tenta computar (avaliar), e "caneta" e "círculo" não têm

valor, o que gera um erro}



[pen red fill-pen blue circle 11x11 4 circle 11x22 4 circle 11x33 4 circle 11x44 4 circle 11x55 4 circle 11x66 4 circle 11x77 4 circle 11x88 4 circle 22x11 4 circle 22x22 4 circle 22x33 4 circle 22x44 4 circle 22x55 4 circle 22x66 4 circle ...


Você vê que Red atualiza automaticamente a base com os desenhos gerados pelo bloco do draw , mesmo depois que a face já foi criada por View. Isso acontece porque no Red, ao contrário do Rebol, o padrão é que sempre que você alterar algum campo do objeto face, esta é atualizada automaticamente. Isso não teria acontecido se você adicionasse a instrução system/view/auto-sync?: off no início do script, conforme descrito aqui .



O programa Paint mais simples do mundo:


Red [needs: view]

newposition: 40x40                ;desculpe, mas sempre começa no centro.

linedraw: func [offset] [        ;func, não function. Variáveis são globais.

   oldposition: newposition

   newposition: offset        

       ; agora vamos adicionando linhas ao bloco de draw:

   append canvas/draw reduce['line oldposition newposition]

]


view [

canvas: base draw[]                ;crea um campo draw no objeto.

   on-down [                        ;quando um botão é clicado...

       do [linedraw event/offset]  ;envia a posição do mouse.

   ]        

]


Toda vez que você clica no mouse na base, um novo segmento de linha é desenhado:




Aqui está uma versão muito melhorada do script que, no entanto, não usa a estrutura "regra geral":



Red [needs: view]

EnableWrite: false

view [

   canvas: base 150x150 white all-over

   draw[]

   on-down [                              ;quando clica o mouse...

       EnableWrite: true              ;... habilita desenho...

       startpoint: event/offset    ;...e pega a posição do cursor

   ]

   on-up [EnableWrite: false]        ;quando o botão é solto, desabilita o desenho

   on-over [                              ;quando o cursor está sobre a base...

       if EnableWrite [        

           endpoint: event/offset        ;pega a posição atual

                       ; agora vai adicionando linhas...

           append canvas/draw reduce['line startpoint endpoint]

           startpoint: endpoint

       ]  

   ]        

]



Observe que o flag all-over permite que o evento over crie eventos para cada movimento do mouse, conforme explicado aqui .




Movimentando um shape com as setas do teclado


Esse script desenha um "alien" no centro de uma  base  e permite que as teclas de seta movam a shape para cima, para baixo, para a esquerda e para a direita. Ele usa a transformação translate para fazer o movimento. Observe o uso da compose para computar o que está entre parênteses.



Red [needs: view]

pos: 28x31                ; This is the initial position of the "alien"


{O seguinte bloco é apenas a forma de um "alien"}

alien: [line 4x0 4x2

'hline 2 'vline 2 'hline -2 'vline 2

'hline -2 'vline 2 'hline -2 'vline 6

'hline 2 'vline -4 'hline 2 'vline 4

'hline 2 'vline 2 'hline 4 'vline -2

'hline -4 'vline -2 'hline 10 'vline 2

'hline -4 'vline 2 'hline 4 'vline -2

'hline 2 'vline -4 'hline 2 'vline 4

'hline 2 'vline -6 'hline -2  'vline -2

'hline -2 'vline -2 'hline -2 'vline -2

'hline 2 'vline -2 'hline -2 'vline 2

'hline -2 'vline 2 'hline -6 'vline -2

'hline -2 'vline -2 'hline -2

move 6x6 'hline 2 'vline 2 'hline -2 'vline -2

move 14x6 'hline 2 'vline 2 'hline -2 'vline -2

]


{A próxima função atualiza o bloco 'draw' do objeto cosmos.

Remove a palavra 'translate e o par seguinte!

a partir do início do bloco e, em seguida, insere 'translate

novamente e o par da posição atualizado!}


update-cosmos: func [] [

       remove/part cosmos/draw 2    

       insert cosmos/draw reduce ['translate pos]

]                

               

view compose/deep/only [

       cosmos: base black focus ; use focus para ter evento on-key

       draw [

               translate (pos)         ; translação inicial. Centraliza o "alien"

               pen white        

               fill-pen white

           shape (alien)

       ]

       

       on-key  [

               switch event/key [

                       up        [pos: pos - 0x1]        ; decreases y axis

                       down        [pos: pos + 0x1]        ; increases y axis

                       left        [pos: pos - 1x0]        ; decreases x axis

                       right [pos: pos + 1x0]        ; increases x axis

               ]

             update-cosmos

       ]

]



Sugiro você alterar o program para testar a transformação  rotate .



Movendo duas ou mais shapes separadamente


O script a seguir usa a seta para a esquerda e para a direita para mover a  "nave espacial", e "z" e "x" para mover o "alien".Observe o escopo de reduce e compose :


Red [needs: view]

;======= initial positions: ========

alienposition: 28x15

shipposition: 32x60


;======= just the shapes ===========

alien: [line 4x0 4x2

'hline 2 'vline 2 'hline -2 'vline 2

'hline -2 'vline 2 'hline -2 'vline 6

'hline 2 'vline -4 'hline 2 'vline 4

'hline 2 'vline 2 'hline 4 'vline -2

'hline -4 'vline -2 'hline 10 'vline 2

'hline -4 'vline 2 'hline 4 'vline -2

'hline 2 'vline -4 'hline 2 'vline 4

'hline 2 'vline -6 'hline -2  'vline -2

'hline -2 'vline -2 'hline -2 'vline -2

'hline 2 'vline -2 'hline -2 'vline 2

'hline -2 'vline 2 'hline -6 'vline -2

'hline -2 'vline -2 'hline -2

move 6x6 'hline 2 'vline 2 'hline -2 'vline -2

move 14x6 'hline 2 'vline 2 'hline -2 'vline -2

]

spaceship: [move 0x12 'hline 14 'vline -6

'hline -2 'vline -2 'hline -4 'vline -4 'hline -2

'vline 4  'hline -4 'vline 2 'hline -2 'vline 6

]

;======= The draw block updating function ======

; this time we create the whole block and just replace

; the original cosmos/draw

update-cosmos: does[

   drawblock: reduce compose/deep[

       'pen white

       'fill-pen white

       'translate alienposition [shape [(alien)]]

       'translate shipposition [shape [(spaceship)]]

   ]

   ;probe drawblock ;uncomment if you want to see it

   cosmos/draw: drawblock

]


view compose/deep/only [

   cosmos: base black focus

;this "draw" be "executed" only once:

   draw [        

       pen white

       fill-pen white

       translate (alienposition) [shape (alien)]

       translate (shipposition) [shape (spaceship)]

   ]

; now the draw block will be recreated on every key press

   on-key  [

               switch event/key [

                       #"z"        [alienposition: alienposition - 1x0]        ; decreases x axis

                       #"x"        [alienposition: alienposition + 1x0]        ; increases x axis

                       left [shipposition: shipposition - 1x0]            ; decreases x axis

                       right [shipposition: shipposition + 1x0]        ; increases x axis

       ]

      update-cosmos ; calls the "draw block recreating function"

       ]

]