-
-
Notifications
You must be signed in to change notification settings - Fork 138
/
Copy pathContext.go
206 lines (161 loc) · 4.5 KB
/
Context.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
package giu
import (
"fmt"
"sync"
"github.com/AllenDang/cimgui-go/imgui"
"gopkg.in/eapache/queue.v1"
)
// GenAutoID automatically generates widget's ID.
// It returns an unique value each time it is called.
func GenAutoID(id string) ID {
idx := int(0)
idxAny, ok := Context.widgetIndex.Load(id)
if ok {
idx, ok = idxAny.(int)
Assert(ok, "Context", "GenAutoID", "unexpected type of widgetIndex value: expected int, instead found %T", idxAny)
idx++
}
Context.widgetIndex.Store(id, idx)
return ID(fmt.Sprintf("%s##%d", id, idx))
}
// Context represents a giu context.
var Context *GIUContext
// Disposable should be implemented by all states stored in context.
// Dispose method is called when state is removed from context.
type Disposable interface {
Dispose()
}
type genericDisposable[T any] interface {
Disposable
*T
}
type state struct {
valid bool
data Disposable
}
// GIUContext represents a giu context. (Current context is giu.Context.
//
//nolint:revive // I WANT TO CALL THIS GIUContext!
type GIUContext struct {
backend GIUBackend
isRunning bool
widgetIndex sync.Map
// Indicate whether current application is running
isAlive bool
// when dirty is true, flushStates must be called before any GetState use
// when it is false, calling flushStates is noop
dirty bool
// States will used by custom widget to store data
state sync.Map
InputHandler InputHandler
FontAtlas *FontAtlas
textureLoadingQueue *queue.Queue
textureFreeingQueue *queue.Queue
cssStylesheet cssStylesheet
m *sync.Mutex
}
// CreateContext creates a new giu context.
func CreateContext(b GIUBackend) *GIUContext {
result := GIUContext{
cssStylesheet: make(cssStylesheet),
backend: b,
FontAtlas: newFontAtlas(),
textureLoadingQueue: queue.New(),
textureFreeingQueue: queue.New(),
m: &sync.Mutex{},
}
// Create font
if len(result.FontAtlas.defaultFonts) == 0 {
fonts := result.IO().Fonts()
fonts.AddFontDefault()
fontTextureImg, w, h, _ := fonts.GetTextureDataAsRGBA32()
tex := result.backend.CreateTexture(fontTextureImg, int(w), int(h))
fonts.SetTexID(tex)
fonts.SetTexReady(true)
} else {
result.FontAtlas.shouldRebuildFontAtlas = true
}
return &result
}
// IO returns the imgui.IO object.
func (c *GIUContext) IO() *imgui.IO {
return imgui.CurrentIO()
}
// SetDirty permits MasterWindow defering setting dirty states after it's render().
func (c *GIUContext) SetDirty() {
c.dirty = true
}
// cleanStates removes all states that were not marked as valid during rendering,
// then reset said flag before new usage
// should always be called before first Get/Set state use in renderloop
// since afterRender() and beforeRender() are not waranted to run (see glfw_window_refresh_callback)
// we call it at the very start of our render()
// but just in case something happened, we also use the "dirty" flag to enforce (or avoid) flushing
// on critical path.
func (c *GIUContext) cleanStates() {
if !c.dirty {
return
}
c.state.Range(func(k, v any) bool {
if s, ok := v.(*state); ok {
c.m.Lock()
valid := s.valid
c.m.Unlock()
if valid {
s.valid = false
} else {
c.state.Delete(k)
s.data.Dispose()
}
}
return true
})
c.widgetIndex.Clear()
c.dirty = false
}
// Backend returns the imgui.backend used by the context.
func (c *GIUContext) Backend() GIUBackend {
return c.backend
}
// SetState is a generic version of Context.SetState.
func SetState[T any, PT genericDisposable[T]](c *GIUContext, id ID, data PT) {
c.cleanStates()
c.state.Store(id, &state{valid: true, data: data})
}
// SetState stores data in context by id.
func (c *GIUContext) SetState(id ID, data Disposable) {
c.cleanStates()
c.state.Store(id, &state{valid: true, data: data})
}
// GetState is a generic version of Context.GetState.
func GetState[T any, PT genericDisposable[T]](c *GIUContext, id ID) PT {
c.cleanStates()
if s, ok := c.load(id); ok {
c.m.Lock()
s.valid = true
c.m.Unlock()
data, isOk := s.data.(PT)
Assert(isOk, "Context", "GetState", "got state of unexpected type: expected %T, instead found %T", new(T), s.data)
return data
}
return nil
}
// GetState returns previously stored state by id.
func (c *GIUContext) GetState(id ID) any {
c.cleanStates()
if s, ok := c.load(id); ok {
c.m.Lock()
s.valid = true
c.m.Unlock()
return s.data
}
return nil
}
func (c *GIUContext) load(id any) (*state, bool) {
if v, ok := c.state.Load(id); ok {
if s, ok := v.(*state); ok {
return s, true
}
}
return nil, false
}