| // Copyright 2015 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| // +build example |
| // |
| // This build tag means that "go install golang.org/x/exp/shiny/..." doesn't |
| // install this example program. Use "go run main.go" to run it or "go install |
| // -tags=example" to install it. |
| |
| // Tile demonstrates tiling a screen with textures. |
| package main |
| |
| import ( |
| "fmt" |
| "image" |
| "image/color" |
| "image/draw" |
| "log" |
| "sync" |
| |
| "golang.org/x/exp/shiny/driver" |
| "golang.org/x/exp/shiny/screen" |
| "golang.org/x/image/font" |
| "golang.org/x/image/font/inconsolata" |
| "golang.org/x/image/math/fixed" |
| "golang.org/x/mobile/event/key" |
| "golang.org/x/mobile/event/lifecycle" |
| "golang.org/x/mobile/event/mouse" |
| "golang.org/x/mobile/event/paint" |
| "golang.org/x/mobile/event/size" |
| ) |
| |
| func main() { |
| driver.Main(func(s screen.Screen) { |
| w, err := s.NewWindow(&screen.NewWindowOptions{ |
| Title: "Tile Shiny Example", |
| }) |
| if err != nil { |
| log.Fatal(err) |
| } |
| defer w.Release() |
| |
| var ( |
| pool = &tilePool{ |
| screen: s, |
| drawRGBA: drawRGBA, |
| m: map[image.Point]*tilePoolEntry{}, |
| } |
| dragging bool |
| paintPending bool |
| drag image.Point |
| origin image.Point |
| sz size.Event |
| ) |
| for { |
| switch e := w.NextEvent().(type) { |
| case lifecycle.Event: |
| if e.To == lifecycle.StageDead { |
| return |
| } |
| |
| case key.Event: |
| if e.Code == key.CodeEscape { |
| return |
| } |
| |
| case mouse.Event: |
| p := image.Point{X: int(e.X), Y: int(e.Y)} |
| if e.Button == mouse.ButtonLeft && e.Direction != mouse.DirNone { |
| dragging = e.Direction == mouse.DirPress |
| drag = p |
| } |
| if !dragging { |
| break |
| } |
| origin = origin.Sub(p.Sub(drag)) |
| drag = p |
| if origin.X < 0 { |
| origin.X = 0 |
| } |
| if origin.Y < 0 { |
| origin.Y = 0 |
| } |
| if !paintPending { |
| paintPending = true |
| w.Send(paint.Event{}) |
| } |
| |
| case paint.Event: |
| generation++ |
| var wg sync.WaitGroup |
| for y := -(origin.Y & 0xff); y < sz.HeightPx; y += 256 { |
| for x := -(origin.X & 0xff); x < sz.WidthPx; x += 256 { |
| wg.Add(1) |
| go drawTile(&wg, w, pool, origin, x, y) |
| } |
| } |
| wg.Wait() |
| w.Publish() |
| paintPending = false |
| pool.releaseUnused() |
| |
| case size.Event: |
| sz = e |
| |
| case error: |
| log.Print(e) |
| } |
| } |
| }) |
| } |
| |
| func drawTile(wg *sync.WaitGroup, w screen.Window, pool *tilePool, origin image.Point, x, y int) { |
| defer wg.Done() |
| tp := image.Point{ |
| (x + origin.X) >> 8, |
| (y + origin.Y) >> 8, |
| } |
| tex, err := pool.get(tp) |
| if err != nil { |
| log.Println(err) |
| return |
| } |
| w.Copy(image.Point{x, y}, tex, tileBounds, screen.Src, nil) |
| } |
| |
| func drawRGBA(m *image.RGBA, tp image.Point) { |
| draw.Draw(m, m.Bounds(), image.White, image.Point{}, draw.Src) |
| for _, p := range crossPoints { |
| m.SetRGBA(p.X, p.Y, crossColor) |
| } |
| d := font.Drawer{ |
| Dst: m, |
| Src: image.Black, |
| Face: inconsolata.Regular8x16, |
| Dot: fixed.Point26_6{ |
| Y: inconsolata.Regular8x16.Metrics().Ascent, |
| }, |
| } |
| d.DrawString(fmt.Sprint(tp)) |
| } |
| |
| var ( |
| crossColor = color.RGBA{0x7f, 0x00, 0x00, 0xff} |
| crossPoints = []image.Point{ |
| {0x00, 0xfe}, |
| {0x00, 0xff}, |
| {0xfe, 0x00}, |
| {0xff, 0x00}, |
| {0x00, 0x00}, |
| {0x01, 0x00}, |
| {0x02, 0x00}, |
| {0x00, 0x01}, |
| {0x00, 0x02}, |
| |
| {0x80, 0x7f}, |
| {0x7f, 0x80}, |
| {0x80, 0x80}, |
| {0x81, 0x80}, |
| {0x80, 0x81}, |
| |
| {0x80, 0x00}, |
| |
| {0x00, 0x80}, |
| } |
| |
| generation int |
| |
| tileSize = image.Point{256, 256} |
| tileBounds = image.Rectangle{Max: tileSize} |
| ) |
| |
| type tilePoolEntry struct { |
| tex screen.Texture |
| gen int |
| } |
| |
| type tilePool struct { |
| screen screen.Screen |
| drawRGBA func(*image.RGBA, image.Point) |
| |
| mu sync.Mutex |
| m map[image.Point]*tilePoolEntry |
| } |
| |
| func (p *tilePool) get(tp image.Point) (screen.Texture, error) { |
| p.mu.Lock() |
| v, ok := p.m[tp] |
| if v != nil { |
| v.gen = generation |
| } |
| p.mu.Unlock() |
| |
| if ok { |
| return v.tex, nil |
| } |
| tex, err := p.screen.NewTexture(tileSize) |
| if err != nil { |
| return nil, err |
| } |
| buf, err := p.screen.NewBuffer(tileSize) |
| if err != nil { |
| tex.Release() |
| return nil, err |
| } |
| p.drawRGBA(buf.RGBA(), tp) |
| tex.Upload(image.Point{}, buf, tileBounds) |
| buf.Release() |
| |
| p.mu.Lock() |
| p.m[tp] = &tilePoolEntry{ |
| tex: tex, |
| gen: generation, |
| } |
| n := len(p.m) |
| p.mu.Unlock() |
| |
| fmt.Printf("%4d textures; created %v\n", n, tp) |
| return tex, nil |
| } |
| |
| func (p *tilePool) releaseUnused() { |
| p.mu.Lock() |
| defer p.mu.Unlock() |
| |
| for tp, v := range p.m { |
| if v.gen == generation { |
| continue |
| } |
| v.tex.Release() |
| delete(p.m, tp) |
| fmt.Printf("%4d textures; released %v\n", len(p.m), tp) |
| } |
| } |