-
Notifications
You must be signed in to change notification settings - Fork 5
/
frame.go
401 lines (344 loc) · 16.9 KB
/
frame.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
package imgui
func ErrorCheckNewFrameSanityChecks() {
var g = GImGui
// Check user IM_ASSERT macro
// (IF YOU GET A WARNING OR COMPILE ERROR HERE: it means your assert macro is incorrectly defined!
// If your macro uses multiple statements, it NEEDS to be surrounded by a 'do { ... } while (0)' block.
// This is a common C/C++ idiom to allow multiple statements macros to be used in control flow blocks.)
// #define IM_ASSERT(EXPR) if (SomeCode(EXPR)) SomeMoreCode(); // Wrong!
// #define IM_ASSERT(EXPR) do { if (SomeCode(EXPR)) SomeMoreCode(); } while (0) // Correct!
if true {
IM_ASSERT(true)
} else {
IM_ASSERT(false)
}
// Check user data
// (We pass an error message in the assert expression to make it visible to programmers who are not using a debugger, as most assert handlers display their argument)
IM_ASSERT(g.Initialized)
IM_ASSERT_USER_ERROR((g.IO.DeltaTime > 0.0 || g.FrameCount == 0), "Need a positive DeltaTime!")
IM_ASSERT_USER_ERROR((g.FrameCount == 0 || g.FrameCountEnded == g.FrameCount), "Forgot to call Render() or EndFrame() at the end of the previous frame?")
IM_ASSERT_USER_ERROR(g.IO.DisplaySize.x >= 0.0 && g.IO.DisplaySize.y >= 0.0, "Invalid DisplaySize value!")
IM_ASSERT_USER_ERROR(g.IO.Fonts.IsBuilt(), "Font Atlas not built! Make sure you called ImGui_ImplXXXX_NewFrame() function for renderer backend, which should call io.Fonts.GetTexDataAsRGBA32() / GetTexDataAsAlpha8()")
IM_ASSERT_USER_ERROR(g.Style.CurveTessellationTol > 0, "Invalid style setting!")
IM_ASSERT_USER_ERROR(g.Style.CircleTessellationMaxError > 0.0, "Invalid style setting!")
IM_ASSERT_USER_ERROR(g.Style.Alpha >= 0.0 && g.Style.Alpha <= 1.0, "Invalid style setting!") // Allows us to avoid a few clamps in color computations
IM_ASSERT_USER_ERROR(g.Style.WindowMinSize.x >= 1.0 && g.Style.WindowMinSize.y >= 1.0, "Invalid style setting.")
IM_ASSERT(g.Style.WindowMenuButtonPosition == ImGuiDir_None || g.Style.WindowMenuButtonPosition == ImGuiDir_Left || g.Style.WindowMenuButtonPosition == ImGuiDir_Right)
for n := ImGuiKey(0); n < ImGuiKey_COUNT; n++ {
IM_ASSERT_USER_ERROR(g.IO.KeyMap[n] >= -1 && g.IO.KeyMap[n] < int(len(g.IO.KeysDown)), "io.KeyMap[] contains an out of bound value (need to be 0..512, or -1 for unmapped key)")
}
// Check: required key mapping (we intentionally do NOT check all keys to not pressure user into setting up everything, but Space is required and was only added in 1.60 WIP)
if g.IO.ConfigFlags&ImGuiConfigFlags_NavEnableKeyboard != 0 {
IM_ASSERT_USER_ERROR(g.IO.KeyMap[ImGuiKey_Space] != -1, "ImGuiKey_Space is not mapped, required for keyboard navigation.")
}
// Check: the io.ConfigWindowsResizeFromEdges option requires backend to honor mouse cursor changes and set the ImGuiBackendFlags_HasMouseCursors flag accordingly.
if g.IO.ConfigWindowsResizeFromEdges && g.IO.BackendFlags&ImGuiBackendFlags_HasMouseCursors == 0 {
g.IO.ConfigWindowsResizeFromEdges = false
}
}
// start a new Dear ImGui frame, you can submit any command from this pountil int Render()/EndFrame().
func NewFrame() {
IM_ASSERT_USER_ERROR(GImGui != nil, "No current context. Did you call ImGui::CreateContext() and ImGui::SetCurrentContext() ?")
var g = GImGui
// Remove pending delete hooks before frame start.
// This deferred removal avoid issues of removal while iterating the hook vector
for n := len(g.Hooks) - 1; n >= 0; n-- {
if g.Hooks[n].Type == ImGuiContextHookType_PendingRemoval_ {
//erase
g.Hooks[n] = g.Hooks[len(g.Hooks)-1]
g.Hooks = g.Hooks[:len(g.Hooks)-1]
}
}
CallContextHooks(g, ImGuiContextHookType_NewFramePre)
// Check and assert for various common IO and Configuration mistakes
ErrorCheckNewFrameSanityChecks()
// Load settings on first frame, save settings when modified (after a delay)
UpdateSettings()
g.Time += double(g.IO.DeltaTime)
g.WithinFrameScope = true
g.FrameCount += 1
g.TooltipOverrideCount = 0
g.WindowsActiveCount = 0
g.MenusIdSubmittedThisFrame = g.MenusIdSubmittedThisFrame[:0]
// Calculate frame-rate for the user, as a purely luxurious feature
g.FramerateSecPerFrameAccum += g.IO.DeltaTime - g.FramerateSecPerFrame[g.FramerateSecPerFrameIdx]
g.FramerateSecPerFrame[g.FramerateSecPerFrameIdx] = g.IO.DeltaTime
g.FramerateSecPerFrameIdx = (g.FramerateSecPerFrameIdx + 1) % int(len(g.FramerateSecPerFrame))
g.FramerateSecPerFrameCount = ImMinInt(g.FramerateSecPerFrameCount+1, int(len(g.FramerateSecPerFrame)))
if g.FramerateSecPerFrameAccum > 0.0 {
g.IO.Framerate = (1.0 / (g.FramerateSecPerFrameAccum / (float)(g.FramerateSecPerFrameCount)))
} else {
g.IO.Framerate = FLT_MAX
}
UpdateViewportsNewFrame()
// Setup current font and draw list shared data
g.IO.Fonts.Locked = true
SetCurrentFont(GetDefaultFont())
IM_ASSERT(g.Font.IsLoaded())
var virtual_space = ImRect{ImVec2{FLT_MAX, FLT_MAX}, ImVec2{-FLT_MAX, -FLT_MAX}}
for n := range g.Viewports {
virtual_space.AddRect(g.Viewports[n].GetMainRect())
}
g.DrawListSharedData.ClipRectFullscreen = virtual_space.ToVec4()
g.DrawListSharedData.CurveTessellationTol = g.Style.CurveTessellationTol
g.DrawListSharedData.SetCircleTessellationMaxError(g.Style.CircleTessellationMaxError)
g.DrawListSharedData.InitialFlags = ImDrawListFlags_None
if g.Style.AntiAliasedLines {
g.DrawListSharedData.InitialFlags |= ImDrawListFlags_AntiAliasedLines
}
if g.Style.AntiAliasedLinesUseTex && g.Font.ContainerAtlas.Flags&ImFontAtlasFlags_NoBakedLines == 0 {
g.DrawListSharedData.InitialFlags |= ImDrawListFlags_AntiAliasedLinesUseTex
}
if g.Style.AntiAliasedFill {
g.DrawListSharedData.InitialFlags |= ImDrawListFlags_AntiAliasedFill
}
if g.IO.BackendFlags&ImGuiBackendFlags_RendererHasVtxOffset != 0 {
g.DrawListSharedData.InitialFlags |= ImDrawListFlags_AllowVtxOffset
}
// Mark rendering data as invalid to prevent user who may have a handle on it to use it.
for n := range g.Viewports {
var viewport = g.Viewports[n]
viewport.DrawDataP.Clear()
}
// Drag and drop keep the source ID alive so even if the source disappear our state is consistent
if g.DragDropActive && g.DragDropPayload.SourceId == g.ActiveId {
KeepAliveID(g.DragDropPayload.SourceId)
}
// Update HoveredId data
if g.HoveredIdPreviousFrame == 0 {
g.HoveredIdTimer = 0.0
}
if g.HoveredIdPreviousFrame == 0 || (g.HoveredId != 0 && g.ActiveId == g.HoveredId) {
g.HoveredIdNotActiveTimer = 0.0
}
if g.HoveredId != 0 {
g.HoveredIdTimer += g.IO.DeltaTime
}
if g.HoveredId != 0 && g.ActiveId != g.HoveredId {
g.HoveredIdNotActiveTimer += g.IO.DeltaTime
}
g.HoveredIdPreviousFrame = g.HoveredId
g.HoveredIdPreviousFrameUsingMouseWheel = g.HoveredIdUsingMouseWheel
g.HoveredId = 0
g.HoveredIdAllowOverlap = false
g.HoveredIdUsingMouseWheel = false
g.HoveredIdDisabled = false
// Update ActiveId data (clear reference to active widget if the widget isn't alive anymore)
if g.ActiveIdIsAlive != g.ActiveId && g.ActiveIdPreviousFrame == g.ActiveId && g.ActiveId != 0 {
ClearActiveID()
}
if g.ActiveId != 0 {
g.ActiveIdTimer += g.IO.DeltaTime
}
g.LastActiveIdTimer += g.IO.DeltaTime
g.ActiveIdPreviousFrame = g.ActiveId
g.ActiveIdPreviousFrameWindow = g.ActiveIdWindow
g.ActiveIdPreviousFrameHasBeenEditedBefore = g.ActiveIdHasBeenEditedBefore
g.ActiveIdIsAlive = 0
g.ActiveIdHasBeenEditedThisFrame = false
g.ActiveIdPreviousFrameIsAlive = false
g.ActiveIdIsJustActivated = false
if g.TempInputId != 0 && g.ActiveId != g.TempInputId {
g.TempInputId = 0
}
if g.ActiveId == 0 {
g.ActiveIdUsingNavDirMask = 0x00
g.ActiveIdUsingNavInputMask = 0x00
g.ActiveIdUsingKeyInputMask = 0x00
}
// Drag and drop
g.DragDropAcceptIdPrev = g.DragDropAcceptIdCurr
g.DragDropAcceptIdCurr = 0
g.DragDropAcceptIdCurrRectSurface = FLT_MAX
g.DragDropWithinSource = false
g.DragDropWithinTarget = false
g.DragDropHoldJustPressedId = 0
// Update keyboard input state
// Synchronize io.KeyMods with individual modifiers io.KeyXXX bools
g.IO.KeyMods = GetMergedKeyModFlags()
copy(g.IO.KeysDownDurationPrev[:], g.IO.KeysDownDuration[:])
for i := 0; i < len(g.IO.KeysDown); i++ {
if g.IO.KeysDown[i] {
if g.IO.KeysDownDuration[i] < 0.0 {
g.IO.KeysDownDuration[i] = 0.0
} else {
g.IO.KeysDownDuration[i] += g.IO.DeltaTime
}
} else {
g.IO.KeysDownDuration[i] = -1.0
}
}
// Update gamepad/keyboard navigation
NavUpdate()
// Update mouse input state
UpdateMouseInputs()
// Find hovered window
// (needs to be before UpdateMouseMovingWindowNewFrame so we fill g.HoveredWindowUnderMovingWindow on the mouse release frame)
UpdateHoveredWindowAndCaptureFlags()
// Handle user moving window with mouse (at the beginning of the frame to avoid input lag or sheering)
UpdateMouseMovingWindowNewFrame()
// Background darkening/whitening
if GetTopMostPopupModal() != nil || (g.NavWindowingTarget != nil && g.NavWindowingHighlightAlpha > 0.0) {
g.DimBgRatio = ImMin(g.DimBgRatio+g.IO.DeltaTime*6.0, 1.0)
} else {
g.DimBgRatio = ImMax(g.DimBgRatio-g.IO.DeltaTime*10.0, 0.0)
}
g.MouseCursor = ImGuiMouseCursor_Arrow
g.WantCaptureMouseNextFrame = -1
g.WantCaptureKeyboardNextFrame = -1
g.WantTextInputNextFrame = -1
g.PlatformImePos = ImVec2{1.0, 1.0} // OS Input Method Editor showing on top-left of our window by default
// Mouse wheel scrolling, scale
UpdateMouseWheel()
// Update legacy TAB focus
UpdateTabFocus()
// Mark all windows as not visible and compact unused memory.
IM_ASSERT(len(g.WindowsFocusOrder) <= len(g.Windows))
var memory_compact_start_time float
if g.GcCompactAll || g.IO.ConfigMemoryCompactTimer < 0.0 {
memory_compact_start_time = FLT_MAX
} else {
memory_compact_start_time = (float)(g.Time - double(g.IO.ConfigMemoryCompactTimer))
}
for i := range g.Windows {
var window = g.Windows[i]
window.WasActive = window.Active
window.BeginCount = 0
window.Active = false
window.WriteAccessed = false
// Garbage collect transient buffers of recently unused windows
if !window.WasActive && !window.MemoryCompacted && window.LastTimeActive < memory_compact_start_time {
GcCompactTransientWindowBuffers(window)
}
}
// Garbage collect transient buffers of recently unused tables
/*for i := range g.TablesLastTimeActive {
if g.TablesLastTimeActive[i] >= 0.0 && g.TablesLastTimeActive[i] < memory_compact_start_time {
TableGcCompactTransientBuffers(g.Tables[i])
}
}
for i := range g.TablesTempDataStack {
if g.TablesTempDataStack[i].LastTimeActive >= 0.0 && g.TablesTempDataStack[i].LastTimeActive < memory_compact_start_time {
TableGcCompactTransientBuffers(&g.TablesTempDataStack[i])
}
}*/
if g.GcCompactAll {
GcCompactTransientMiscBuffers()
}
g.GcCompactAll = false
// Closing the focused window restore focus to the first active root window in descending z-order
if g.NavWindow != nil && !g.NavWindow.WasActive {
FocusTopMostWindowUnderOne(nil, nil)
}
// No window should be open at the beginning of the frame.
// But in order to allow the user to call NewFrame() multiple times without calling Render(), we are doing an explicit clear.
g.CurrentWindowStack = g.CurrentWindowStack[:0]
g.BeginPopupStack = g.BeginPopupStack[:0]
g.ItemFlagsStack = g.ItemFlagsStack[:0]
g.ItemFlagsStack = append(g.ItemFlagsStack, ImGuiItemFlags_None)
g.GroupStack = g.GroupStack[:0]
// [DEBUG] Item picker tool - start with DebugStartItemPicker() - useful to visually select an item and break into its call-stack.
UpdateDebugToolItemPicker()
// Create implicit/fallback window - which we will only render it if the user has added something to it.
// We don't use "Debug" to avoid colliding with user trying to create a "Debug" window with custom flags.
// This fallback is particularly important as it avoid ImGui:: calls from crashing.
g.WithinFrameScopeWithImplicitWindow = true
SetNextWindowSize(&ImVec2{400, 400}, ImGuiCond_FirstUseEver)
Begin("Debug##Default", nil, 0)
IM_ASSERT(g.CurrentWindow.IsFallbackWindow)
CallContextHooks(g, ImGuiContextHookType_NewFramePost)
}
func ErrorCheckEndFrameSanityChecks() {
var g = GImGui
// Verify that io.KeyXXX fields haven't been tampered with. Key mods should not be modified between NewFrame() and EndFrame()
// One possible reason leading to this assert is that your backends update inputs _AFTER_ NewFrame().
// It is known that when some modal native windows called mid-frame takes focus away, some backends such as GLFW will
// send key release events mid-frame. This would normally trigger this assertion and lead to sheared inputs.
// We silently accommodate for this case by ignoring/ the case where all io.KeyXXX modifiers were released (aka key_mod_flags == 0),
// while still correctly asserting on mid-frame key press events.
var key_mod_flags = GetMergedKeyModFlags()
IM_ASSERT_USER_ERROR((key_mod_flags == 0 || g.IO.KeyMods == key_mod_flags), "Mismatching io.KeyCtrl/io.KeyShift/io.KeyAlt/io.KeySuper vs io.KeyMods")
// Recover from errors
ErrorCheckEndFrameRecover(nil, nil)
// Report when there is a mismatch of Begin/BeginChild vs End/EndChild calls. Important: Remember that the Begin/BeginChild API requires you
// to always call End/EndChild even if Begin/BeginChild returns false! (this is unfortunately inconsistent with most other Begin* API).
if len(g.CurrentWindowStack) != 1 {
if len(g.CurrentWindowStack) > 1 {
IM_ASSERT_USER_ERROR(len(g.CurrentWindowStack) == 1, "Mismatched Begin/BeginChild vs End/EndChild calls: did you forget to call End/EndChild?")
for len(g.CurrentWindowStack) > 1 {
End()
}
} else {
IM_ASSERT_USER_ERROR(len(g.CurrentWindowStack) == 1, "Mismatched Begin/BeginChild vs End/EndChild calls: did you call End/EndChild too much?")
}
}
IM_ASSERT_USER_ERROR(len(g.GroupStack) == 0, "Missing EndGroup call!")
}
// ends the Dear ImGui frame. automatically called by Render(). If you don't need to render data (skipping rendering) you may call EndFrame() without Render()... but you'll have wasted CPU already! If you don't need to render, better to not create any windows and not call NewFrame() at all!
func EndFrame() {
var g = GImGui
IM_ASSERT(g.Initialized)
// Don't process EndFrame() multiple times.
if g.FrameCountEnded == g.FrameCount {
return
}
IM_ASSERT_USER_ERROR(g.WithinFrameScope, "Forgot to call ImGui::NewFrame()?")
CallContextHooks(g, ImGuiContextHookType_EndFramePre)
ErrorCheckEndFrameSanityChecks()
// Notify OS when our Input Method Editor cursor has moved (e.g. CJK inputs using Microsoft IME)
if g.IO.ImeSetInputScreenPosFn != nil && (g.PlatformImeLastPos.x == FLT_MAX || ImLengthSqrVec2(g.PlatformImeLastPos.Sub(g.PlatformImePos)) > 0.0001) {
g.IO.ImeSetInputScreenPosFn((int)(g.PlatformImePos.x), (int)(g.PlatformImePos.y))
g.PlatformImeLastPos = g.PlatformImePos
}
// Hide implicit/fallback "Debug" window if it hasn't been used
g.WithinFrameScopeWithImplicitWindow = false
if g.CurrentWindow != nil && !g.CurrentWindow.WriteAccessed {
g.CurrentWindow.Active = false
}
End()
// Update navigation: CTRL+Tab, wrap-around requests
NavEndFrame()
// Drag and Drop: Elapse payload (if delivered, or if source stops being submitted)
if g.DragDropActive {
var is_delivered = g.DragDropPayload.Delivery
var is_elapsed = (g.DragDropPayload.DataFrameCount+1 < g.FrameCount) && ((g.DragDropSourceFlags&ImGuiDragDropFlags_SourceAutoExpirePayload != 0) || !IsMouseDown(g.DragDropMouseButton))
if is_delivered || is_elapsed {
ClearDragDrop()
}
}
// Drag and Drop: Fallback for source tooltip. This is not ideal but better than nothing.
if g.DragDropActive && g.DragDropSourceFrameCount < g.FrameCount && g.DragDropSourceFlags&ImGuiDragDropFlags_SourceNoPreviewTooltip == 0 {
g.DragDropWithinSource = true
SetTooltip("...")
g.DragDropWithinSource = false
}
// End frame
g.WithinFrameScope = false
g.FrameCountEnded = g.FrameCount
// Initiate moving window + handle left-click and right-click focus
UpdateMouseMovingWindowEndFrame()
// Sort the window list so that all child windows are after their parent
// We cannot do that on FocusWindow() because children may not exist yet
g.WindowsTempSortBuffer = g.WindowsTempSortBuffer[:0]
g.WindowsTempSortBuffer = make([]*ImGuiWindow, 0, len(g.Windows))
for i := range g.Windows {
var window = g.Windows[i]
if window.Active && (window.Flags&ImGuiWindowFlags_ChildWindow != 0) { // if a child is active its parent will add it
continue
}
AddWindowToSortBuffer(&g.WindowsTempSortBuffer, window)
}
// This usually assert if there is a mismatch between the ImGuiWindowFlags_ChildWindow / ParentWindow values and DC.ChildWindows[] in parents, aka we've done something wrong.
IM_ASSERT(len(g.Windows) == len(g.WindowsTempSortBuffer))
g.Windows, g.WindowsTempSortBuffer = g.WindowsTempSortBuffer, g.Windows
g.IO.MetricsActiveWindows = g.WindowsActiveCount
// Unlock font atlas
g.IO.Fonts.Locked = false
// Clear Input data for next frame
g.IO.MouseWheel = 0
g.IO.MouseWheelH = 0.0
g.IO.InputQueueCharacters = g.IO.InputQueueCharacters[:0]
g.IO.KeyModsPrev = g.IO.KeyMods // doing it here is better than in NewFrame() as we'll tolerate backend writing to KeyMods. If we want to firmly disallow it we should detect it.
g.IO.NavInputs = [20]float{}
CallContextHooks(g, ImGuiContextHookType_EndFramePost)
}