/
rendermodel.go
224 lines (197 loc) · 6.38 KB
/
rendermodel.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
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
// Copyright 2016 Howard C. Shaw III. All rights reserved.
// Use of this source code is governed by the MIT-license
// as defined in the LICENSE file.
package renderview
import (
"image"
"math"
"sync"
)
// RenderModel is the interface you will implement to stand between your visualization code
// and the RenderView. You are primarily responsible for providing a set of RenderParameters
// and providing an Image upon request via Render.
type RenderModel interface {
Lock()
Unlock()
GetParameterNames() []string
GetHintedParameterNames(hint int) []string
GetHintedParameterNamesWithFallback(hint int) []string
GetParameter(name string) RenderParameter
Render() image.Image
SetRequestPaintFunc(func())
GetRequestPaintFunc() func()
}
// EmptyRenderModel concretizes the most important elements of the RenderModel, the bag of Parameters (Params)
// and the RequestPaint function (which the view sets) - call this latter function to inform the view
// that you have provided a new image or set of information needing a render. It is not usable as a RenderModel
// by itself, as the implementation of Render simply returns nil. Embed it in your RenderModel struct.
type EmptyRenderModel struct {
sync.Mutex
Params []RenderParameter
RequestPaint func()
}
// GetParameterNames returns a list of valid parameter names
func (e *EmptyRenderModel) GetParameterNames() []string {
s := make([]string, len(e.Params))
for i := 0; i < len(e.Params); i++ {
s[i] = e.Params[i].GetName()
}
return s
}
// GetHintedParameterNames returns a list of parameters with one of the passed hints
func (e *EmptyRenderModel) GetHintedParameterNames(hints int) []string {
s := make([]string, 0, len(e.Params))
for i := 0; i < len(e.Params); i++ {
if e.Params[i].GetHint()&hints > 0 {
s = append(s, e.Params[i].GetName())
}
}
return s
}
// GetHintedParameterNamesWithFallback retrieves the names of parameters matching hints,
// if that is the empty set, it retrieves the names of parameters with no hints
func (e *EmptyRenderModel) GetHintedParameterNamesWithFallback(hints int) []string {
s := make([]string, 0, len(e.Params))
for i := 0; i < len(e.Params); i++ {
if e.Params[i].GetHint()&hints > 0 {
s = append(s, e.Params[i].GetName())
}
}
if len(s) == 0 {
for i := 0; i < len(e.Params); i++ {
if e.Params[i].GetHint() == 0 {
s = append(s, e.Params[i].GetName())
}
}
}
return s
}
// GetParameter returns a named parameter. If you implement your own RenderModel from scratch,
// without using the EmptyRenderModel as a basis, you must either include ALL the default
// parameters, or duplicate the behavior of EmptyRenderModel in returning an EmptyParameter
// when a non-existent parameter is requested.
func (e *EmptyRenderModel) GetParameter(name string) RenderParameter {
for _, p := range e.Params {
if name == p.GetName() {
return p
}
}
return &EmptyParameter{}
}
func (e *EmptyRenderModel) Render() image.Image {
return nil
}
// AddParameters accepts any number of parameters and adds them to the Params bag.
// It does not do ANY checking for duplication!
func (e *EmptyRenderModel) AddParameters(Params ...RenderParameter) {
e.Params = append(e.Params, Params...)
}
// Included for completeness. In general, there is no need for your code to use
// the RenderModel interface instead of a concrete form, so you can simply
// access e.RequestPaint directly.
func (e *EmptyRenderModel) GetRequestPaintFunc() func() {
return e.RequestPaint
}
// Used by the RenderView to supply a function you can call to inform the view
// that it should perform a repaint.
func (e *EmptyRenderModel) SetRequestPaintFunc(f func()) {
e.RequestPaint = f
}
/*
// EmptyRenderModel is not functional by itself
func NewEmptyRenderModel() *EmptyRenderModel {
return &EmptyRenderModel{
Params: make([]RenderParameter, 0, 10),
}
}*/
// InitializeEmptyRenderModel should be called to initialize the EmptyRenderModel
// when embedded in your own struct.
func InitializeEmptyRenderModel(m *EmptyRenderModel) {
m.Params = make([]RenderParameter, 0, 10)
}
// BasicRenderModel should suffice for many users, and can be embedded to provide its
// functionality to your own models. It provides an easy way to attach your own
// rendering implementation that will be called in a separate goroutine.
type BasicRenderModel struct {
EmptyRenderModel
RequestRender chan interface{}
NeedsRender bool
Rendering bool
Img image.Image
started bool
InnerRender func()
}
// Called by RenderView
func (m *BasicRenderModel) Render() image.Image {
m.Lock()
defer m.Unlock()
rendering := m.Rendering
if !rendering {
m.RequestRender <- true
m.NeedsRender = false
} else {
m.NeedsRender = true
}
return m.Img
}
// GoRender is called by Start and calls your provided InnerRender function when needed.
func (m *BasicRenderModel) GoRender() {
for {
select {
case <-m.RequestRender:
if !(m.InnerRender == nil) {
m.InnerRender()
if m.NeedsRender {
m.NeedsRender = false
m.InnerRender()
}
}
}
}
}
// Start only needs to be called if you have embedded BasicRenderModel in your own struct.
func (m *BasicRenderModel) Start() {
if !m.started {
m.started = true
go m.GoRender()
}
}
func NewBasicRenderModel() *BasicRenderModel {
m := BasicRenderModel{
EmptyRenderModel: EmptyRenderModel{
Params: make([]RenderParameter, 0, 10),
},
RequestRender: make(chan interface{}, 10),
}
m.started = true
go m.GoRender()
return &m
}
// Use Initialize to set up a BasicRenderModel when you have embedded it in
// your own model
// Remember to add a go m.GoRender() or call Start()
func InitializeBasicRenderModel(m *BasicRenderModel) {
m.Params = make([]RenderParameter, 0, 10)
m.RequestRender = make(chan interface{}, 10)
}
func DefaultParameters(useint bool, hint int, options int, left float64, top float64, right float64, bottom float64) []RenderParameter {
if useint {
return SetHints(hint,
NewIntRP("left", int(math.Floor(left))),
NewIntRP("top", int(math.Floor(top))),
NewIntRP("right", int(math.Floor(right))),
NewIntRP("bottom", int(math.Floor(bottom))),
NewIntRP("width", 100),
NewIntRP("height", 100),
NewIntRP("options", options))
} else {
return SetHints(hint,
NewFloat64RP("left", left),
NewFloat64RP("top", top),
NewFloat64RP("right", right),
NewFloat64RP("bottom", bottom),
NewIntRP("width", 100),
NewIntRP("height", 100),
NewIntRP("options", options))
}
}