forked from tomnz/scroll-phat-hd-go
-
Notifications
You must be signed in to change notification settings - Fork 0
/
display.go
153 lines (133 loc) · 3.66 KB
/
display.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
package scrollphat
import (
"periph.io/x/periph/conn/i2c"
)
// New instantiates a new Scroll pHAT HD display, the supplied options. This method requires
// an I2C bus to be supplied, which will be used to connect to the actual hardware device.
// For example:
//
// import (
// "github.com/tomnz/scroll-phat-hd-go"
// "periph.io/x/periph/conn/i2c/i2creg"
// "periph.io/x/periph/host"
// )
// _, _ := host.Init()
// bus, _ := i2creg.Open("1")
// display, _ := scrollphat.New(bus)
//
func New(bus i2c.Bus, opts ...DisplayOption) (*Display, error) {
device, err := NewDriver(bus)
if err != nil {
return nil, err
}
disp := NewWithDevice(device, opts...)
return disp, nil
}
// NewWithDevice instantiates a new Scroll pHAT HD display, using the supplied device and
// options. For using the standard I2C hardware device, you likely want to just use New
// instead.
// This constructor is useful for passing a non-standard device implementation, such as a
// mock or terminal emulator.
// You can also override some of the options for the standard driver by declaring it first,
// then passing it to this constructor.
func NewWithDevice(device Device, opts ...DisplayOption) *Display {
options := defaultDisplayOptions
for _, opt := range opts {
opt(&options)
}
outBuf := make([][]byte, device.Height())
for y := range outBuf {
outBuf[y] = make([]byte, device.Width())
}
d := &Display{
options: options,
device: device,
outBuf: outBuf,
}
d.resetBuffer()
return d
}
// Display is the primary struct for interacting with the Scroll pHAT HD device.
type Display struct {
options displayOptions
device Device
buffer [][]byte
width, height,
scrollX, scrollY int
flipX, flipY bool
// We maintain the output buffer for the device ourselves, to reduce the amount of
// memory allocation and copying that goes on
outBuf [][]byte
// TODO: Make this goroutine-safe? Would involve wrapping any buffer operations with a mutex.
}
// Device is an abstraction that defines the capabilities that the display requires from
// its actual device (hardware or otherwise).
type Device interface {
SetBuffer(buffer [][]byte)
SetBrightness(brightness byte)
Show() error
Width() int
Height() int
}
// SetBrightness configures the display's brightness.
// 0 is off, 255 is maximum brightness.
func (d *Display) SetBrightness(brightness byte) {
d.device.SetBrightness(brightness)
}
// SetFlip configures flipping for the display.
func (d *Display) SetFlip(flipX, flipY bool) {
d.flipX = flipX
d.flipY = flipY
}
// ScrollTo configures the top left coordinate to use from the buffer for display.
func (d *Display) ScrollTo(scrollX, scrollY int) {
d.scrollX = scrollX
d.scrollY = scrollY
}
// Scroll scrolls the buffer relative to its current position.
func (d *Display) Scroll(deltaX, deltaY int) {
d.scrollX += deltaX
d.scrollY += deltaY
}
// Show renders the current state of the display to the device. Scrolling and flipping are applied,
// and the relevant subset of the display is sent to the device for actual rendering.
func (d *Display) Show() {
for y, row := range d.outBuf {
for x := range row {
row[x] = d.getSourcePixel(x, y)
}
}
d.device.SetBuffer(d.outBuf)
d.device.Show()
}
func (d *Display) getSourcePixel(devX, devY int) byte {
x := devX
x += d.scrollX
if d.options.tile {
x %= d.width
}
if d.flipX {
x = d.width - x - 1
}
// Fail early if x is nonsense
if x < 0 || x >= d.width {
return 0
}
y := devY
y += d.scrollY
if d.options.tile {
y %= d.height
}
if d.flipY {
y = d.height - y - 1
}
if y < 0 || y >= d.height {
return 0
}
return d.buffer[y][x]
}
// Clear clears the entire display.
func (d *Display) Clear() {
d.resetBuffer()
d.Show()
}