render/canvas/engine.go

174 lines
4.1 KiB
Go

package canvas
import (
"bytes"
"encoding/base64"
"errors"
"image"
"image/png"
"syscall/js"
"time"
"git.kirsle.net/apps/doodle/lib/events"
"git.kirsle.net/apps/doodle/lib/render"
)
// Engine implements a rendering engine targeting an HTML canvas for
// WebAssembly targets.
type Engine struct {
canvas Canvas
startTime time.Time
width int
height int
ticks uint32
// Private fields.
events *events.State
running bool
textures map[string]*Texture // cached texture PNG images
// Event channel. WASM subscribes to events asynchronously using the
// JavaScript APIs, whereas SDL2 polls the event queue which orders them
// all up for processing. This channel will order and queue the events.
queue chan Event
}
// New creates the Canvas Engine.
func New(canvasID string) (*Engine, error) {
canvas := GetCanvas(canvasID)
engine := &Engine{
canvas: canvas,
startTime: time.Now(),
events: events.New(),
width: canvas.ClientW(),
height: canvas.ClientH(),
queue: make(chan Event, 1024),
textures: map[string]*Texture{},
}
return engine, nil
}
// WindowSize returns the size of the canvas window.
func (e *Engine) WindowSize() (w, h int) {
// Good time to recompute it first?
var (
window = js.Global().Get("window")
width = window.Get("innerWidth").Int()
height = window.Get("innerHeight").Int()
)
e.canvas.Value.Set("width", width)
e.canvas.Value.Set("height", height)
return e.canvas.ClientW(), e.canvas.ClientH()
}
// GetTicks returns the number of milliseconds since the engine started.
func (e *Engine) GetTicks() uint32 {
ms := time.Since(e.startTime) * time.Millisecond
return uint32(ms)
}
// TO BE IMPLEMENTED...
func (e *Engine) Setup() error {
return nil
}
func (e *Engine) Present() error {
return nil
}
// Texture can hold on to cached image textures.
type Texture struct {
data string // data:image/png URI
image js.Value // DOM image element
canvas js.Value // Warmed up canvas element
ctx2d js.Value // 2D drawing context for the canvas.
width int
height int
}
// NewTexture caches a texture from a bitmap.
func (e *Engine) NewTexture(filename string, img image.Image) (render.Texturer, error) {
var (
fh = bytes.NewBuffer([]byte{})
imageSize = img.Bounds().Size()
width = imageSize.X
height = imageSize.Y
)
// Encode to PNG format.
if err := png.Encode(fh, img); err != nil {
return nil, err
}
var dataURI = "data:image/png;base64," + base64.StdEncoding.EncodeToString(fh.Bytes())
tex := &Texture{
data: dataURI,
width: width,
height: height,
}
// Preheat a cached Canvas object.
canvas := js.Global().Get("document").Call("createElement", "canvas")
canvas.Set("width", width)
canvas.Set("height", height)
tex.canvas = canvas
ctx2d := canvas.Call("getContext", "2d")
tex.ctx2d = ctx2d
// Load as a JS Image object.
image := js.Global().Call("eval", "new Image()")
image.Call("addEventListener", "load", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
ctx2d.Call("drawImage", image, 0, 0)
return nil
}))
image.Set("src", tex.data)
tex.image = image
// Cache the texture in memory.
e.textures[filename] = tex
return tex, nil
}
// Size returns the dimensions of the texture.
func (t *Texture) Size() render.Rect {
return render.NewRect(int32(t.width), int32(t.height))
}
// NewBitmap initializes a texture from a bitmap image. The image is stored
// in HTML5 Session Storage.
func (e *Engine) NewBitmap(filename string) (render.Texturer, error) {
if tex, ok := e.textures[filename]; ok {
return tex, nil
}
panic("no bitmap for " + filename)
return nil, errors.New("no bitmap data stored for " + filename)
}
// Copy a texturer bitmap onto the canvas.
func (e *Engine) Copy(t render.Texturer, src, dist render.Rect) {
tex := t.(*Texture)
// e.canvas.ctx2d.Call("drawImage", tex.image, dist.X, dist.Y)
e.canvas.ctx2d.Call("drawImage", tex.canvas, dist.X, dist.Y)
}
// Delay for a moment.
func (e *Engine) Delay(delay uint32) {
time.Sleep(time.Duration(delay) * time.Millisecond)
}
// Teardown tasks.
func (e *Engine) Teardown() {}
func (e *Engine) Loop() error {
return nil
}