forked from gophergala2016/rex
/
rexdemo.go
170 lines (148 loc) · 3.67 KB
/
rexdemo.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
package rexdemo
import (
"fmt"
"image"
"image/color"
"image/draw"
"sync"
"time"
"github.com/bmatsuo/rex/room"
"github.com/golang/freetype/truetype"
"golang.org/x/image/font"
"golang.org/x/image/math/fixed"
"golang.org/x/mobile/event/size"
"golang.org/x/mobile/exp/gl/glutil"
"golang.org/x/mobile/geom"
)
// Room is the room used by clients and servers for the demo.
var Room = &room.Room{
Name: "REx Demo",
Service: "_rexdemo._tcp.",
}
// RemotePoint is a touch event from another client
type RemotePoint struct {
X float64
Y float64
}
// Pt is a simple constructor for RemotePoint.
func Pt(x, y float64) RemotePoint {
return RemotePoint{x, y}
}
// Demo is the state of a demo a copy of the state is present in the server and
// all clients.
type Demo struct {
Mut *sync.Mutex `json:"-"`
X float64 `json:"x"`
Y float64 `json:"y"`
Counter int `json:"counter"`
Last time.Time `json:"last"`
}
// NewDemo returns a new Demo object
func NewDemo() *Demo {
// It's a little weird that a pointer is preferred over sync.Mutex. But
// due to how state is shored be the client and server a reference makes
// more sense.
return &Demo{
Mut: new(sync.Mutex),
}
}
// State returns a snapshot of d.
func (d *Demo) State() *Demo {
_d := &Demo{}
*_d = *d
_d.Mut = nil
return _d
}
// State is an interface satisfied by other Demo types.
type State interface {
State() *Demo
}
// StatusPainter is an object responsible for rendering the demo status at the
// top of the client UI.
type StatusPainter struct {
bg image.Image
demo State
ttf *truetype.Font
opt *truetype.Options
face font.Face
frozen *Demo
sz size.Event
image *glutil.Image
images *glutil.Images
}
// NewStatusPainter initializes and returns a StatusPainter.
func NewStatusPainter(demo State, font *truetype.Font, bg color.Color, images *glutil.Images) *StatusPainter {
return &StatusPainter{
demo: demo,
bg: image.NewUniform(bg),
ttf: font,
images: images,
}
}
// Release calls Release on underlying gl elements.
func (p *StatusPainter) Release() {
p.image.Release()
}
// Draw renders the demo state to the screen
func (p *StatusPainter) Draw(sz size.Event, pad int, opt *truetype.Options) {
if sz.WidthPx == 0 && sz.HeightPx == 0 {
return
}
fsize := opt.Size
if fsize == 0 {
fsize = 12
}
pixY := int(float64(fsize)*float64(sz.PixelsPerPt)) + pad
if p.sz != sz {
p.sz = sz
if p.image != nil {
p.image.Release()
}
p.image = p.images.NewImage(sz.WidthPx, pixY)
// BUG: face will not update if opt changes
p.opt = nil
}
if p.opt == nil || *opt != *p.opt {
if p.opt != nil {
dpi := p.opt.DPI
p.opt.DPI = opt.DPI
if *opt == *p.opt {
// we have already correctly computed the DPI so this opt is
// nothing new.
p.opt.DPI = dpi
} else {
p.opt = nil
}
}
if p.opt == nil {
p.opt = &truetype.Options{}
*p.opt = *opt
p.opt.DPI = float64(sz.PixelsPerPt) * 72
p.face = truetype.NewFace(p.ttf, p.opt)
}
}
state := p.demo.State()
state.Mut = nil
if p.frozen == nil || *state != *p.frozen {
// generate a current image
draw.Draw(p.image.RGBA, p.image.RGBA.Bounds(), p.bg, image.Pt(0, 0), draw.Over)
originX := 2
originY := pixY - 2
drawer := &font.Drawer{
Dst: p.image.RGBA,
Src: image.NewUniform(color.Black),
Face: p.face,
Dot: fixed.P(originX, originY),
}
text := fmt.Sprintf("%v Count=%d", state.Last.Format(time.RFC3339), state.Counter)
drawer.DrawString(text)
p.image.Upload()
}
p.image.Draw(
sz,
geom.Point{X: 0, Y: 0},
geom.Point{X: sz.WidthPt, Y: 0},
geom.Point{X: 0, Y: geom.Pt(float64(pixY) / float64(sz.PixelsPerPt))},
p.image.RGBA.Bounds(),
)
}