Shine envolve as funções de desenho do javascript em uma API declarativa.
Fortemente inspirado no gloss.
demonstração (exemplos de brilho compilados) aqui
Você precisa de gcjs
Uma ótima maneira de construir pacotes ghcjs é usar a plataforma reflex
Picture
Para representar seu desenho você deve construir uma árvore usando o tipo de dados Picture
.
pic :: Picture
pic = Rect 10 20 -- represents a 10x20 square
Para compor múltiplas Picture
s você pode usar Over
, que aceita duas Picture
s e as sobrepõe.
Picture
é um monóide: <>
é um apelido para Over
e mempty
é a imagem vazia.
-- draw some shapes on top of each other
pic :: Picture
pic = Rect 10 20
<> Translate 30 30 ( Circle 15 )
<> Colored ( Color 255 0 0 0.2 ) ( RectF 4 4 )
<> Text " Sans 12px " LeftAlign 200 " The quick brown fox jumps over the lazy dog. "
Usando Foldable
você pode fazer coisas como
concentricCircles :: Picture
concentricCircles = foldMap Circle [ 1 , 10 .. 100 ]
Picture
Antes de desenhar qualquer coisa você precisa obter um CanvasRenderingContext2D
(e às vezes um Document
). Para isso, o Shine oferece duas funções utilitárias: fullScreenCanvas
e fixedSizeCanvas
{-# LANGUAGE CPP #-}
import Graphics.Shine
import Graphics.Shine.Input
import Graphics.Shine.Picture
import GHCJS.DOM ( currentDocumentUnchecked )
-- This is how the ghcjs-dom hello-world does it.
-- It's boilerplate, so in the next shine version there
-- will probably be a ready-to-use run function
#if defined(ghcjs_HOST_OS)
run :: a -> a
run = id
#elif defined(MIN_VERSION_jsaddle_wkwebview)
import Language.Javascript.JSaddle.WKWebView ( run )
#else
import Language.Javascript.JSaddle.WebKitGTK ( run )
#endif
main :: IO ()
main = run $ do
doc <- currentDocumentUnchecked -- use currentDocument to handle failure
ctx <- fixedSizeCanvas doc 400 400
-- do something with ctx (and maybe doc)
Para renderizar uma Picture
em um contexto você tem três opções:
render
Você pode desenhá-lo manualmente usando render
de Graphics.Shine.Render
main :: IO ()
{- omitted: get the context, see before -}
render ctx concentricCircles
animate
Você pode desenhar uma Picture
que depende do tempo. Ou seja, um Float -> Picture
.
-- An expanding-and-contracting circle.
animation :: Float -> Picture
animation = Translate 200 200
. Circle
. ( * 100 ) . ( + 1 ) -- bigger positive oscillation
. sin -- the circle's radius oscillates
main :: IO ()
main = do
{- omitted: get the context, see before -}
animate ctx 30 animation
play
Finalmente, você pode desenhar uma Picture
que depende do tempo, das entradas (teclado e mouse) e de um estado interno. Isto é especialmente útil para jogos, daí o nome.
-- this code draws a black rectangle in the center of the canvas only when the
-- left mouse button is pressed
main :: IO ()
main = do
{- omitted: get the context and the document, see before -}
play ctx doc 30 initialState draw handleInput step
where
-- our state represents the state of the left mouse button
initialState = Up
-- we draw a square only if the button is pressed
draw Up = Empty
draw Down = Translate 200 200 $ RectF 200 200
-- when an event is fired we store the button state
handleInput ( MouseBtn BtnLeft buttonState _) = const buttonState
handleInput _ = id -- catch-all for all other events
step _ = id -- our state does not depend on time
Veja o pacote de shine-examples
(Hackage).
Lidar com ativos com ghcjs e o navegador é um tanto complicado.
A maneira usual de fazer isso para programas haskell normais é usar data-files
. Infelizmente, isso funciona codificando um caminho absoluto no executável, o que não funcionará quando o programa for colocado em um servidor em um caminho diferente, ou com uma substituição de variável de ambiente ( $pkgname_datadir
), que não funcionará porque as variáveis de ambiente não não existe no navegador.
Portanto, por enquanto, a solução é especificar explicitamente caminhos absolutos ou relativos no código-fonte e, em seguida, copiar os ativos para o local apropriado após construir/implantar o código. Foi isso que fiz na demonstração animated-shapes
para a imagem dos pimentões.
Bilhete de cabala relevante, discutindo uma possível nova forma de lidar com ativos: haskell/cabal#6096 (comentário)