-
Notifications
You must be signed in to change notification settings - Fork 0
/
shape.go
176 lines (143 loc) · 4.7 KB
/
shape.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
package naiad
import "github.com/faiface/pixel"
// ShapeKind determines what sort of shape a Shape should be cast to. It can be
// retrieved by calling the Kind() method.
type ShapeKind int
const (
ShapeKindPath ShapeKind = iota
ShapeKindText
ShapeKindGroup
)
// Shape is an interface representing an on-screen shape. For a shape to be
// inserted into naiad's shape hierarchy, it must support these behaviors.
type Shape interface {
// draw draws the shape onto the specified target.
draw(target pixel.Target)
// Kind returns what kind of shape it is.
Kind() (kind ShapeKind)
// Dirty returns wether the shape is dirty or not.
Dirty() (isDirty bool)
// SetDirty causes the shape to be flagged as dirty.
SetDirty()
// SetClean causes the shape to be flagged as clean.
SetClean()
// Bounds returns the bounds of the shape, relative to its origin. This
// is usually in the top left, which means min will usually be (0, 0).
// Hovever, in shapes such as paths, the min bound may be in a different
// spot due to things such as stroke thickness and points with negative
// coordinates.
Bounds() (min, max Vector)
// Contains takes in mouse coordinates, and recursively checks if those
// coordinates are contained in the shape or any of its children. It
// returns a slice of all shapes (including itself) that the points are
// contained within. If the position is not inside the shape, it should
// immediately stop, return nil, and not recurse into anything else.
Contains(position Vector) (shapes []Shape)
// setParent sets the shape's parent. This does not actually parent the
// shape - it should be called by the parent as the shape is being
// parented.
setParent(parent Shape)
// getParent returns the shape's parent.
getParent() (parent Shape)
}
// shapeBase is a struct which should be included in all shapes. It defines some
// basic behaviors and properties such as position, bounds, and dirtiness.
type shapeBase struct {
parent Shape
matrix pixel.Matrix
position Vector
min Vector
max Vector
clean bool
}
// SetPosition sets the position of the shape.
func (base *shapeBase) SetPosition(position Vector) {
if base.position != position {
base.position = position
base.SetDirty()
}
base.calculateTransform()
}
// SetX sets the x position of the shape.
func (base *shapeBase) SetX(x float64) {
if base.position.X() != x {
base.position.SetX(x)
base.SetDirty()
}
base.calculateTransform()
}
// SetY sets the y position of the shape.
func (base *shapeBase) SetY(y float64) {
if base.position.Y() != y {
base.position.SetY(y)
base.SetDirty()
}
base.calculateTransform()
}
// X returns the x position of the shape.
func (base *shapeBase) X() (x float64) {
return base.position.X()
}
// Y returns the y position of the shape.
func (base *shapeBase) Y() (y float64) {
return base.position.Y()
}
// Dirty returns wether the shape is dirty or not.
func (base *shapeBase) Dirty() (isDirty bool) {
return !base.clean
}
// SetDirty causes the shape to be flagged as dirty.
func (base *shapeBase) SetDirty() {
if base.Dirty() {
return
}
base.clean = false
// if this shape needs to be redrawn, then so do all of its parents.
if base.parent != nil {
base.parent.SetDirty()
}
}
// SetClean causes the shape to be flagged as clean.
func (base *shapeBase) SetClean() {
base.clean = true
}
// Bounds returns the bounds of the shape, relative to its origin. This is
// usually in the top left, which means min will usually be (0, 0). Hovever, in
// shapes such as paths, the min bound may be in a different spot due to things
// such as stroke thickness and points with negative coordinates.
func (base *shapeBase) Bounds() (min, max Vector) {
return base.min, base.max
}
// setParent sets the shape's parent. This does not actually parent the shape -
// it should be called by the parent as the shape is being parented.
func (base *shapeBase) setParent(parent Shape) {
base.parent = parent
}
// getParent returns the shape's parent.
func (base *shapeBase) getParent() (parent Shape) {
return base.parent
}
// contractMin ensures that the shape's minimum bound is no greater than min.
func (base *shapeBase) contractMin(min Vector) {
if min.X() < base.min.X() {
base.min.SetX(min.X())
}
if min.Y() < base.min.Y() {
base.min.SetY(min.Y())
}
}
// expandMax ensures that the shape's maximum bound is no less than max.
func (base *shapeBase) expandMax(max Vector) {
if max.X() > base.max.X() {
base.max.SetX(max.X())
}
if max.Y() > base.max.Y() {
base.max.SetY(max.Y())
}
}
// calculateTransform recalcualtes the transformation matrix of the shape.
func (base *shapeBase) calculateTransform() {
// recalculate matrix
base.matrix = pixel.IM.Moved(
pixel.V(base.position.X(), base.position.Y()))
}