forked from ebitenui/ebitenui
-
Notifications
You must be signed in to change notification settings - Fork 0
/
ui.go
159 lines (131 loc) · 3.73 KB
/
ui.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
package ebitenui
import (
"image"
"github.com/kintar/ebitenui/v2/event"
"github.com/kintar/ebitenui/v2/input"
internalinput "github.com/kintar/ebitenui/v2/internal/input"
"github.com/kintar/ebitenui/v2/widget"
"github.com/hajimehoshi/ebiten/v2"
)
// UI encapsulates a complete user interface that can be rendered onto the screen.
// There should only be exactly one UI per application.
type UI struct {
// Container is the root container of the UI hierarchy.
Container *widget.Container
// ToolTip is used to render mouse hover tool tips. It may be nil to disable rendering.
ToolTip *widget.ToolTip
// DragAndDrop is used to render drag widgets while dragging and dropping. It may be nil to disable rendering.
DragAndDrop *widget.DragAndDrop
lastRect image.Rectangle
focusedWidget widget.HasWidget
inputLayerers []input.Layerer
renderers []widget.Renderer
windows []*widget.Window
}
// RemoveWindowFunc is a function to remove a Window from rendering.
type RemoveWindowFunc func()
// Update updates u. This method should be called in the Ebiten Update function.
func (u *UI) Update() {
internalinput.Update()
}
// Draw renders u onto screen. This function should be called in the Ebiten Draw function.
//
// If screen's size changes from one frame to the next, u.Container.RequestRelayout is called.
func (u *UI) Draw(screen *ebiten.Image) {
event.ExecuteDeferred()
internalinput.Draw()
defer internalinput.AfterDraw()
w, h := screen.Size()
rect := image.Rect(0, 0, w, h)
defer func() {
u.lastRect = rect
}()
if rect != u.lastRect {
u.Container.RequestRelayout()
}
u.handleFocus()
u.setupInputLayers()
u.Container.SetLocation(rect)
u.render(screen)
}
func (u *UI) handleFocus() {
if input.MouseButtonJustPressed(ebiten.MouseButtonLeft) {
if u.focusedWidget != nil {
u.focusedWidget.(widget.Focuser).Focus(false)
u.focusedWidget = nil
}
x, y := input.CursorPosition()
w := u.Container.WidgetAt(x, y)
if w != nil {
if f, ok := w.(widget.Focuser); ok {
f.Focus(true)
u.focusedWidget = w
}
}
}
}
func (u *UI) setupInputLayers() {
num := 1 // u.Container
if len(u.windows) > 0 {
num += len(u.windows)
}
if u.DragAndDrop != nil {
num++
}
if cap(u.inputLayerers) < num {
u.inputLayerers = make([]input.Layerer, num)
}
u.inputLayerers = u.inputLayerers[:0]
u.inputLayerers = append(u.inputLayerers, u.Container)
for _, w := range u.windows {
u.inputLayerers = append(u.inputLayerers, w)
}
if u.DragAndDrop != nil {
u.inputLayerers = append(u.inputLayerers, u.DragAndDrop)
}
// TODO: SetupInputLayersWithDeferred should reside in "internal" subpackage
input.SetupInputLayersWithDeferred(u.inputLayerers)
}
func (u *UI) render(screen *ebiten.Image) {
num := 1 // u.Container
if len(u.windows) > 0 {
num += len(u.windows)
}
if u.ToolTip != nil {
num++
}
if u.DragAndDrop != nil {
num++
}
if cap(u.renderers) < num {
u.renderers = make([]widget.Renderer, num)
}
u.renderers = u.renderers[:0]
u.renderers = append(u.renderers, u.Container)
for _, w := range u.windows {
u.renderers = append(u.renderers, w)
}
if u.ToolTip != nil {
u.renderers = append(u.renderers, u.ToolTip)
}
if u.DragAndDrop != nil {
u.renderers = append(u.renderers, u.DragAndDrop)
}
// TODO: RenderWithDeferred should reside in "internal" subpackage
widget.RenderWithDeferred(screen, u.renderers)
}
// AddWindow adds window w to u for rendering. It returns a function to remove w from u.
func (u *UI) AddWindow(w *widget.Window) RemoveWindowFunc {
u.windows = append(u.windows, w)
return func() {
u.removeWindow(w)
}
}
func (u *UI) removeWindow(w *widget.Window) {
for i, uw := range u.windows {
if uw == w {
u.windows = append(u.windows[:i], u.windows[i+1:]...)
break
}
}
}