forked from AllenDang/giu
-
Notifications
You must be signed in to change notification settings - Fork 0
/
ClickableWidgets.go
556 lines (469 loc) · 14 KB
/
ClickableWidgets.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
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
package giu
import (
"fmt"
"image"
"image/color"
imgui "github.com/AllenDang/cimgui-go"
"golang.org/x/image/colornames"
)
var _ Widget = &ButtonWidget{}
// ButtonWidget represents a ImGui button widget.
type ButtonWidget struct {
id ID
width float32
height float32
disabled bool
onClick func()
}
// Button creates a new button widget.
func Button(label string) *ButtonWidget {
return &ButtonWidget{
id: GenAutoID(label),
width: 0,
height: 0,
onClick: nil,
}
}
// Buttonf creates button with formatted label
// NOTE: works like fmt.Sprintf (see `go doc fmt`).
func Buttonf(format string, args ...any) *ButtonWidget {
return Button(fmt.Sprintf(format, args...))
}
// OnClick sets callback called when button is clicked
// NOTE: to set double click, see EventHandler.go.
func (b *ButtonWidget) OnClick(onClick func()) *ButtonWidget {
b.onClick = onClick
return b
}
// Disabled sets button's disabled state
// NOTE: same effect as Style().SetDisabled.
func (b *ButtonWidget) Disabled(d bool) *ButtonWidget {
b.disabled = d
return b
}
// Size sets button's size.
func (b *ButtonWidget) Size(width, height float32) *ButtonWidget {
b.width, b.height = width, height
return b
}
func (b *ButtonWidget) ID(id ID) *ButtonWidget {
b.id = id
return b
}
// Build implements Widget interface.
func (b *ButtonWidget) Build() {
if b.disabled {
imgui.BeginDisabled()
defer imgui.EndDisabled()
}
if imgui.ButtonV(Context.FontAtlas.RegisterString(b.id.String()), imgui.Vec2{X: b.width, Y: b.height}) && b.onClick != nil {
b.onClick()
}
}
var _ Widget = &ArrowButtonWidget{}
// ArrowButtonWidget represents a square button with an arrow.
type ArrowButtonWidget struct {
id ID
dir Direction
onClick func()
}
// ArrowButton creates ArrowButtonWidget.
func ArrowButton(dir Direction) *ArrowButtonWidget {
return &ArrowButtonWidget{
id: GenAutoID("ArrowButton"),
dir: dir,
onClick: nil,
}
}
// OnClick adds callback called when button is clicked.
func (b *ArrowButtonWidget) OnClick(onClick func()) *ArrowButtonWidget {
b.onClick = onClick
return b
}
// ID allows to manually set widget's id.
func (b *ArrowButtonWidget) ID(id ID) *ArrowButtonWidget {
b.id = id
return b
}
// Build implements Widget interface.
func (b *ArrowButtonWidget) Build() {
if imgui.ArrowButton(b.id.String(), imgui.Dir(b.dir)) && b.onClick != nil {
b.onClick()
}
}
var _ Widget = &SmallButtonWidget{}
// SmallButtonWidget is like a button but without frame padding.
type SmallButtonWidget struct {
id ID
onClick func()
}
// SmallButton constructs a new small button widget.
func SmallButton(id string) *SmallButtonWidget {
return &SmallButtonWidget{
id: GenAutoID(id),
onClick: nil,
}
}
// SmallButtonf allows to set formatted label for small button.
// It calls SmallButton(fmt.Sprintf(label, args...)).
func SmallButtonf(format string, args ...any) *SmallButtonWidget {
return SmallButton(fmt.Sprintf(format, args...))
}
// OnClick adds OnClick event.
func (b *SmallButtonWidget) OnClick(onClick func()) *SmallButtonWidget {
b.onClick = onClick
return b
}
// Build implements Widget interface.
func (b *SmallButtonWidget) Build() {
if imgui.SmallButton(Context.FontAtlas.RegisterString(b.id.String())) && b.onClick != nil {
b.onClick()
}
}
var _ Widget = &InvisibleButtonWidget{}
// InvisibleButtonWidget is a clickable region.
// NOTE: you may want to display other widgets on this button.
// to do so, you may move drawing cursor back by Get/SetCursor(Screen)Pos.
type InvisibleButtonWidget struct {
id ID
width float32
height float32
onClick func()
}
// InvisibleButton constructs a new invisible button widget.
func InvisibleButton() *InvisibleButtonWidget {
return &InvisibleButtonWidget{
id: GenAutoID("InvisibleButton"),
width: 0,
height: 0,
onClick: nil,
}
}
// Size sets button's size.
func (b *InvisibleButtonWidget) Size(width, height float32) *InvisibleButtonWidget {
b.width, b.height = width, height
return b
}
// OnClick sets click event.
func (b *InvisibleButtonWidget) OnClick(onClick func()) *InvisibleButtonWidget {
b.onClick = onClick
return b
}
// ID allows to manually set widget's id (no need to use in normal conditions).
func (b *InvisibleButtonWidget) ID(id ID) *InvisibleButtonWidget {
b.id = id
return b
}
// Build implements Widget interface.
func (b *InvisibleButtonWidget) Build() {
if imgui.InvisibleButton(Context.FontAtlas.RegisterString(b.id.String()), imgui.Vec2{X: b.width, Y: b.height}) && b.onClick != nil {
b.onClick()
}
}
var _ Widget = &ImageButtonWidget{}
// ImageButtonWidget is similar to ButtonWidget but with image texture instead of text label.
type ImageButtonWidget struct {
texture *Texture
width float32
height float32
uv0 image.Point
uv1 image.Point
framePadding int
bgColor color.Color
tintColor color.Color
onClick func()
}
// ImageButton constructs image button widget.
func ImageButton(texture *Texture) *ImageButtonWidget {
return &ImageButtonWidget{
texture: texture,
width: 50,
height: 50,
uv0: image.Point{X: 0, Y: 0},
uv1: image.Point{X: 1, Y: 1},
framePadding: -1,
bgColor: colornames.Black,
tintColor: colornames.White,
onClick: nil,
}
}
// Build implements Widget interface.
func (b *ImageButtonWidget) Build() {
if b.texture == nil || b.texture.tex == nil {
return
}
if imgui.ImageButtonV(
fmt.Sprintf("%v", b.texture.tex.ID),
b.texture.tex.ID,
imgui.Vec2{X: b.width, Y: b.height},
ToVec2(b.uv0), ToVec2(b.uv1),
ToVec4Color(b.bgColor),
ToVec4Color(b.tintColor),
) && b.onClick != nil {
b.onClick()
}
}
// Size sets BUTTONS size.
// NOTE: image size is button size - 2 * frame padding.
func (b *ImageButtonWidget) Size(width, height float32) *ImageButtonWidget {
b.width, b.height = width, height
return b
}
// OnClick sets click event.
func (b *ImageButtonWidget) OnClick(onClick func()) *ImageButtonWidget {
b.onClick = onClick
return b
}
// UV sets image's uv.
func (b *ImageButtonWidget) UV(uv0, uv1 image.Point) *ImageButtonWidget {
b.uv0, b.uv1 = uv0, uv1
return b
}
// BgColor sets button's background color.
func (b *ImageButtonWidget) BgColor(bgColor color.Color) *ImageButtonWidget {
b.bgColor = bgColor
return b
}
// TintColor sets tit color for image.
func (b *ImageButtonWidget) TintColor(tintColor color.Color) *ImageButtonWidget {
b.tintColor = tintColor
return b
}
// FramePadding sets button's frame padding (set 0 to fit image to the frame).
func (b *ImageButtonWidget) FramePadding(padding int) *ImageButtonWidget {
b.framePadding = padding
return b
}
var _ Widget = &ImageButtonWithRgbaWidget{}
// ImageButtonWithRgbaWidget does similar to ImageButtonWidget,
// but implements image.Image instead of giu.Texture. It is probably
// more useful than the original ImageButtonWidget.
type ImageButtonWithRgbaWidget struct {
*ImageButtonWidget
rgba image.Image
id ID
}
// ImageButtonWithRgba creates a new widget.
func ImageButtonWithRgba(rgba image.Image) *ImageButtonWithRgbaWidget {
return &ImageButtonWithRgbaWidget{
id: GenAutoID("ImageButtonWithRgba"),
ImageButtonWidget: ImageButton(nil),
rgba: rgba,
}
}
// Size sets button's size.
func (b *ImageButtonWithRgbaWidget) Size(width, height float32) *ImageButtonWithRgbaWidget {
b.ImageButtonWidget.Size(width, height)
return b
}
// OnClick sets click events.
func (b *ImageButtonWithRgbaWidget) OnClick(onClick func()) *ImageButtonWithRgbaWidget {
b.ImageButtonWidget.OnClick(onClick)
return b
}
// UV sets image's uv color.
func (b *ImageButtonWithRgbaWidget) UV(uv0, uv1 image.Point) *ImageButtonWithRgbaWidget {
b.ImageButtonWidget.UV(uv0, uv1)
return b
}
// BgColor sets button's background color.
func (b *ImageButtonWithRgbaWidget) BgColor(bgColor color.Color) *ImageButtonWithRgbaWidget {
b.ImageButtonWidget.BgColor(bgColor)
return b
}
// TintColor sets image's tint color.
func (b *ImageButtonWithRgbaWidget) TintColor(tintColor color.Color) *ImageButtonWithRgbaWidget {
b.ImageButtonWidget.TintColor(tintColor)
return b
}
// FramePadding sets frame padding (see (*ImageButtonWidget).TintColor).
func (b *ImageButtonWithRgbaWidget) FramePadding(padding int) *ImageButtonWithRgbaWidget {
b.ImageButtonWidget.FramePadding(padding)
return b
}
// Build implements Widget interface.
func (b *ImageButtonWithRgbaWidget) Build() {
if state := GetState[imageState](Context, b.id.String()); state == nil {
SetState(Context, b.id.String(), &imageState{})
NewTextureFromRgba(b.rgba, func(tex *Texture) {
SetState(Context, b.id.String(), &imageState{texture: tex})
})
} else {
b.ImageButtonWidget.texture = state.texture
}
b.ImageButtonWidget.Build()
}
var _ Widget = &CheckboxWidget{}
// CheckboxWidget adds a checkbox.
type CheckboxWidget struct {
text ID
selected *bool
onChange func()
}
// Checkbox creates a new CheckboxWidget.
func Checkbox(text string, selected *bool) *CheckboxWidget {
return &CheckboxWidget{
text: GenAutoID(text),
selected: selected,
onChange: nil,
}
}
// OnChange adds callback called when checkbox's state was changed.
func (c *CheckboxWidget) OnChange(onChange func()) *CheckboxWidget {
c.onChange = onChange
return c
}
// Build implements Widget interface.
func (c *CheckboxWidget) Build() {
if imgui.Checkbox(Context.FontAtlas.RegisterString(c.text.String()), c.selected) && c.onChange != nil {
c.onChange()
}
}
var _ Widget = &RadioButtonWidget{}
// RadioButtonWidget is a small, round button.
// It is common to use it for single-choice questions.
// see examples/widgets.
type RadioButtonWidget struct {
text ID
active bool
onChange func()
}
// RadioButton creates a radio button.
func RadioButton(text string, active bool) *RadioButtonWidget {
return &RadioButtonWidget{
text: GenAutoID(text),
active: active,
onChange: nil,
}
}
// OnChange adds callback when button's state gets changed.
func (r *RadioButtonWidget) OnChange(onChange func()) *RadioButtonWidget {
r.onChange = onChange
return r
}
// Build implements Widget interface.
func (r *RadioButtonWidget) Build() {
if imgui.RadioButtonBool(Context.FontAtlas.RegisterString(r.text.String()), r.active) && r.onChange != nil {
r.onChange()
}
}
var _ Widget = &SelectableWidget{}
// SelectableWidget is a window-width button with a label which can get selected (highlighted).
// useful for certain lists.
type SelectableWidget struct {
label ID
selected bool
flags SelectableFlags
width float32
height float32
onClick func()
onDClick func()
}
// Selectable constructs a selectable widget.
func Selectable(label string) *SelectableWidget {
return &SelectableWidget{
label: GenAutoID(label),
selected: false,
flags: 0,
width: 0,
height: 0,
onClick: nil,
}
}
// Selectablef creates a selectable widget with formatted label.
func Selectablef(format string, args ...any) *SelectableWidget {
return Selectable(fmt.Sprintf(format, args...))
}
// Selected sets if selectable widget is selected.
func (s *SelectableWidget) Selected(selected bool) *SelectableWidget {
s.selected = selected
return s
}
// Flags add flags.
func (s *SelectableWidget) Flags(flags SelectableFlags) *SelectableWidget {
s.flags = flags
return s
}
// Size sets selectable's size.
func (s *SelectableWidget) Size(width, height float32) *SelectableWidget {
s.width, s.height = width, height
return s
}
// OnClick sets on click event.
func (s *SelectableWidget) OnClick(onClick func()) *SelectableWidget {
s.onClick = onClick
return s
}
// OnDClick handles mouse left button's double click event.
// SelectableFlagsAllowDoubleClick will set once tonDClick callback is notnull.
// NOTE: IT IS DEPRECATED and could be removed. Use EventHandler instead.
func (s *SelectableWidget) OnDClick(onDClick func()) *SelectableWidget {
s.onDClick = onDClick
return s
}
// Build implements Widget interface.
func (s *SelectableWidget) Build() {
// If onDClick is set, check flags and set related flag when necessary
if s.onDClick != nil && s.flags&SelectableFlagsAllowDoubleClick != 0 {
s.flags |= SelectableFlagsAllowDoubleClick
}
if imgui.SelectableBoolV(Context.FontAtlas.RegisterString(s.label.String()), s.selected, imgui.SelectableFlags(s.flags), imgui.Vec2{X: s.width, Y: s.height}) && s.onClick != nil {
s.onClick()
}
if s.onDClick != nil && IsItemActive() && IsMouseDoubleClicked(MouseButtonLeft) {
s.onDClick()
}
}
var _ Widget = &TreeNodeWidget{}
// TreeNodeWidget is a a wide button with open/close state.
// if is opened, the `layout` is displayed below the widget.
// It can be used to create certain lists, advanced settings sections e.t.c.
type TreeNodeWidget struct {
label string
flags TreeNodeFlags
layout Layout
eventHandler func()
}
// TreeNode creates a new tree node widget.
func TreeNode(label string) *TreeNodeWidget {
return &TreeNodeWidget{
label: Context.FontAtlas.RegisterString(label),
flags: 0,
layout: nil,
eventHandler: nil,
}
}
// TreeNodef adds TreeNode with formatted label.
func TreeNodef(format string, args ...any) *TreeNodeWidget {
return TreeNode(fmt.Sprintf(format, args...))
}
// Flags sets flags.
func (t *TreeNodeWidget) Flags(flags TreeNodeFlags) *TreeNodeWidget {
t.flags = flags
return t
}
// Event create TreeNode with eventHandler
// You could detect events (e.g. IsItemClicked IsMouseDoubleClicked etc...) and handle them for TreeNode inside eventHandler.
// Deprecated: Use EventHandler instead!
func (t *TreeNodeWidget) Event(handler func()) *TreeNodeWidget {
t.eventHandler = handler
return t
}
// Layout sets layout to be displayed when tree node is opened.
func (t *TreeNodeWidget) Layout(widgets ...Widget) *TreeNodeWidget {
t.layout = Layout(widgets)
return t
}
// Build implements Widget interface.
func (t *TreeNodeWidget) Build() {
open := imgui.TreeNodeExStrV(t.label, imgui.TreeNodeFlags(t.flags))
if t.eventHandler != nil {
t.eventHandler()
}
if open {
t.layout.Build()
if (imgui.TreeNodeFlags(t.flags) & imgui.TreeNodeFlagsNoTreePushOnOpen) == 0 {
imgui.TreePop()
}
}
}