1 package etk
2
3 import (
4 "image"
5 "image/color"
6
7 "code.rocket9labs.com/tslocum/etk/messeji"
8 "github.com/hajimehoshi/ebiten/v2"
9 "golang.org/x/image/font"
10 "golang.org/x/image/font/sfnt"
11 )
12
13
14 type Button struct {
15 *Box
16 field *messeji.TextField
17 btnBackground color.RGBA
18 textFont *sfnt.Font
19 textSize int
20 textAutoSize int
21 borderSize int
22 borderTop color.RGBA
23 borderRight color.RGBA
24 borderBottom color.RGBA
25 borderLeft color.RGBA
26 onSelected func() error
27 pressed bool
28 }
29
30
31 func NewButton(label string, onSelected func() error) *Button {
32 textColor := Style.ButtonTextColor
33 if textColor.A == 0 {
34 textColor = Style.TextColorDark
35 }
36 f := newText()
37 f.SetText(label)
38 f.SetForegroundColor(textColor)
39 f.SetHorizontal(messeji.AlignCenter)
40 f.SetVertical(messeji.AlignCenter)
41 f.SetScrollBarVisible(false)
42
43 b := &Button{
44 Box: NewBox(),
45 field: f,
46 btnBackground: Style.ButtonBgColor,
47 textFont: Style.TextFont,
48 textSize: Scale(Style.TextSize),
49 onSelected: onSelected,
50 borderSize: Scale(Style.ButtonBorderSize),
51 borderTop: Style.ButtonBorderTop,
52 borderRight: Style.ButtonBorderRight,
53 borderBottom: Style.ButtonBorderBottom,
54 borderLeft: Style.ButtonBorderLeft,
55 }
56 b.SetBackground(Style.ButtonBgColor)
57 b.resizeFont()
58 return b
59 }
60
61
62 func (b *Button) SetRect(r image.Rectangle) {
63 b.Box.rect = r
64
65 b.field.SetRect(r)
66 b.resizeFont()
67
68 for _, w := range b.children {
69 w.SetRect(r)
70 }
71 }
72
73
74 func (b *Button) SetBorderSize(size int) {
75 b.Lock()
76 defer b.Unlock()
77
78 b.borderSize = size
79 }
80
81
82 func (b *Button) SetBorderColors(top color.RGBA, right color.RGBA, bottom color.RGBA, left color.RGBA) {
83 b.Lock()
84 defer b.Unlock()
85
86 b.borderTop = top
87 b.borderRight = right
88 b.borderBottom = bottom
89 b.borderLeft = left
90 }
91
92
93 func (b *Button) SetForeground(c color.RGBA) {
94 b.Lock()
95 defer b.Unlock()
96
97 b.field.SetForegroundColor(c)
98 }
99
100
101 func (b *Button) SetBackground(background color.RGBA) {
102 b.Lock()
103 defer b.Unlock()
104
105 b.btnBackground = background
106 b.field.SetBackgroundColor(b.btnBackground)
107 }
108
109
110 func (b *Button) Text() string {
111 b.Lock()
112 defer b.Unlock()
113
114 return b.field.Text()
115 }
116
117
118 func (b *Button) SetText(text string) {
119 b.Lock()
120 defer b.Unlock()
121
122 b.field.SetText(text)
123 b.resizeFont()
124 }
125
126
127 func (b *Button) SetFont(fnt *sfnt.Font, size int) {
128 b.Lock()
129 defer b.Unlock()
130
131 b.textFont, b.textSize = fnt, size
132 b.resizeFont()
133 }
134
135 func (b *Button) resizeFont() {
136 w, h := b.rect.Dx()-b.field.Padding()*2, b.rect.Dy()-b.field.Padding()*2
137 if w == 0 || h == 0 {
138 if b.textAutoSize == b.textSize {
139 return
140 }
141 b.textAutoSize = b.textSize
142 ff := FontFace(b.textFont, b.textSize)
143 b.field.SetFont(ff, fontMutex)
144 return
145 }
146
147 var autoSize int
148 var ff font.Face
149 for autoSize = b.textSize; autoSize > 0; autoSize-- {
150 ff = FontFace(b.textFont, autoSize)
151 bounds := BoundString(ff, b.field.Text())
152 if bounds.Dx() <= w && bounds.Dy() <= h {
153 break
154 }
155 }
156 if b.textAutoSize == autoSize {
157 return
158 }
159
160 b.field.SetFont(ff, fontMutex)
161 b.textAutoSize = autoSize
162 }
163
164
165 func (b *Button) SetHorizontal(h Alignment) {
166 b.Lock()
167 defer b.Unlock()
168
169 b.field.SetHorizontal(messeji.Alignment(h))
170 }
171
172
173 func (b *Button) SetVertical(v Alignment) {
174 b.Lock()
175 defer b.Unlock()
176
177 b.field.SetVertical(messeji.Alignment(v))
178 }
179
180
181
182 func (b *Button) Cursor() ebiten.CursorShapeType {
183 return ebiten.CursorShapePointer
184 }
185
186
187 func (b *Button) HandleKeyboard(ebiten.Key, rune) (handled bool, err error) {
188 return false, nil
189 }
190
191
192 func (b *Button) HandleMouse(cursor image.Point, pressed bool, clicked bool) (handled bool, err error) {
193 if !clicked {
194 if b.pressed && !pressed {
195 b.Lock()
196 b.pressed = false
197 b.background = b.btnBackground
198 b.Unlock()
199 }
200 return true, nil
201 }
202
203 b.Lock()
204 b.pressed = true
205 b.background = color.RGBA{uint8(float64(b.btnBackground.R) * 0.95), uint8(float64(b.btnBackground.G) * 0.95), uint8(float64(b.btnBackground.B) * 0.95), 255}
206 onSelected := b.onSelected
207 if onSelected == nil {
208 b.Unlock()
209 return true, nil
210 }
211 b.Unlock()
212
213 return true, onSelected()
214 }
215
216
217 func (b *Button) Draw(screen *ebiten.Image) error {
218 r := b.rect
219
220
221 b.field.Draw(screen)
222
223
224 if b.borderSize != 0 {
225 if !b.pressed {
226 screen.SubImage(image.Rect(r.Min.X, r.Min.Y, r.Min.X+b.borderSize, r.Max.Y)).(*ebiten.Image).Fill(b.borderLeft)
227 screen.SubImage(image.Rect(r.Min.X, r.Min.Y, r.Max.X, r.Min.Y+b.borderSize)).(*ebiten.Image).Fill(b.borderTop)
228 screen.SubImage(image.Rect(r.Max.X-b.borderSize, r.Min.Y, r.Max.X, r.Max.Y)).(*ebiten.Image).Fill(b.borderRight)
229 screen.SubImage(image.Rect(r.Min.X, r.Max.Y-b.borderSize, r.Max.X, r.Max.Y)).(*ebiten.Image).Fill(b.borderBottom)
230 } else {
231 screen.SubImage(image.Rect(r.Max.X-b.borderSize, r.Min.Y, r.Max.X, r.Max.Y)).(*ebiten.Image).Fill(b.borderLeft)
232 screen.SubImage(image.Rect(r.Min.X, r.Max.Y-b.borderSize, r.Max.X, r.Max.Y)).(*ebiten.Image).Fill(b.borderTop)
233 screen.SubImage(image.Rect(r.Min.X, r.Min.Y, r.Min.X+b.borderSize, r.Max.Y)).(*ebiten.Image).Fill(b.borderRight)
234 screen.SubImage(image.Rect(r.Min.X, r.Min.Y, r.Max.X, r.Min.Y+b.borderSize)).(*ebiten.Image).Fill(b.borderBottom)
235 }
236 }
237
238 return nil
239 }
240
View as plain text