-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathwidgets.tree.go
455 lines (403 loc) · 16.8 KB
/
widgets.tree.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
package imgui
import "fmt"
// Widgets: Trees
// - TreeNode functions return true when the node is open, in which case you need to also call TreePop() when you are finished displaying the tree node contents.
// helper variation to easily decorelate the id from the displayed string. Read the FAQ about why and how to use ID. to align arbitrary text at the same level as a TreeNode() you can use Bullet().
func TreeNodeF(str_id string, format string, args ...any) bool {
var window = GetCurrentWindow()
if window.SkipItems {
return false
}
return TreeNodeBehavior(window.GetIDs(str_id), 0, fmt.Sprintf(format, args...))
}
func TreeNodeInterface(ptr_id any, format string, args ...any) bool {
var window = GetCurrentWindow()
if window.SkipItems {
return false
}
return TreeNodeBehavior(window.GetIDInterface(ptr_id), 0, fmt.Sprintf(format, args...))
}
func TreeNodeEx(str_id string, flags ImGuiTreeNodeFlags, format string, args ...any) bool {
var window = GetCurrentWindow()
if window.SkipItems {
return false
}
return TreeNodeBehavior(window.GetIDs(str_id), flags, fmt.Sprintf(format, args...))
}
func TreeNodeInterfaceEx(ptr_id any, flags ImGuiTreeNodeFlags, format string, args ...any) bool {
var window = GetCurrentWindow()
if window.SkipItems {
return false
}
return TreeNodeBehavior(window.GetIDInterface(ptr_id), flags, fmt.Sprintf(format, args...))
}
// ~ Indent()+PushId(). Already called by TreeNode() when returning true, but you can call TreePush/TreePop yourself if desired.
func TreePush(str_id string) {
var window = GetCurrentWindow()
Indent(0)
window.DC.TreeDepth++
if str_id != "" {
PushString(str_id)
} else {
PushString("#TreePush")
}
}
func TreePushInterface(ptr_id any) {
var window = GetCurrentWindow()
Indent(0)
window.DC.TreeDepth++
if ptr_id != nil {
PushInterface(ptr_id)
} else {
PushString("#TreePush")
}
} // "
// horizontal distance preceding label when using TreeNode*() or Bullet() == (g.FontSize + style.FramePadding.x*2) for a regular unframed TreeNode
func GetTreeNodeToLabelSpacing() float {
var g = GImGui
return g.FontSize + (g.Style.FramePadding.x * 2.0)
}
// CollapsingHeader returns true when opened but do not indent nor push into the ID stack (because of the ImGuiTreeNodeFlags_NoTreePushOnOpen flag).
// This is basically the same as calling TreeNodeEx(label, ImGuiTreeNodeFlags_CollapsingHeader). You can remove the _NoTreePushOnOpen flag if you want behavior closer to normal TreeNode().
// if returning 'true' the header is open. doesn't indent nor push on ID stack. user doesn't have to call TreePop().
func CollapsingHeader(label string, flags ImGuiTreeNodeFlags) bool {
var window = GetCurrentWindow()
if window.SkipItems {
return false
}
return TreeNodeBehavior(window.GetIDs(label), flags|ImGuiTreeNodeFlags_CollapsingHeader, label)
}
// when 'p_visible != NULL': if '*p_visible==true' display an additional small close button on upper right of the header which will set the to bool false when clicked, if '*p_visible==false' don't display the header.
// p_visible == nil : regular collapsing header
// p_visible != nil && *p_visible == true : show a small close button on the corner of the header, clicking the button will set *p_visible = false
// p_visible != nil && *p_visible == false : do not show the header at all
// Do not mistake this with the Open state of the header itself, which you can adjust with SetNextItemOpen() or ImGuiTreeNodeFlags_DefaultOpen.
func CollapsingHeaderVisible(label string, p_visible *bool, flags ImGuiTreeNodeFlags) bool {
var window = GetCurrentWindow()
if window.SkipItems {
return false
}
if p_visible != nil && !*p_visible {
return false
}
var id = window.GetIDs(label)
flags |= ImGuiTreeNodeFlags_CollapsingHeader
if p_visible != nil {
flags |= ImGuiTreeNodeFlags_AllowItemOverlap | ImGuiTreeNodeFlags_ClipLabelForTrailingButton
}
var is_open = TreeNodeBehavior(id, flags, label)
if p_visible != nil {
// Create a small overlapping close button
// FIXME: We can evolve this into user accessible helpers to add extra buttons on title bars, headers, etc.
// FIXME: CloseButton can overlap into text, need find a way to clip the text somehow.
var g = GImGui
var last_item_backup = g.LastItemData
var button_size = g.FontSize
var button_x = ImMax(g.LastItemData.Rect.Min.x, g.LastItemData.Rect.Max.x-g.Style.FramePadding.x*2.0-button_size)
var button_y = g.LastItemData.Rect.Min.y
var close_button_id = GetIDWithSeed("#CLOSE", id)
if CloseButton(close_button_id, &ImVec2{button_x, button_y}) {
*p_visible = false
}
g.LastItemData = last_item_backup
}
return is_open
}
// set next TreeNode/CollapsingHeader open state.
func SetNextItemOpen(is_open bool, cond ImGuiCond) {
var g = GImGui
if g.CurrentWindow.SkipItems {
return
}
g.NextItemData.Flags |= ImGuiNextItemDataFlags_HasOpen
g.NextItemData.OpenVal = is_open
if cond != 0 {
g.NextItemData.OpenCond = cond
} else {
g.NextItemData.OpenCond = ImGuiCond_Always
}
}
func TreeNode(label string) bool {
var window = GetCurrentWindow()
if window.SkipItems {
return false
}
return TreeNodeBehavior(window.GetIDs(label), 0, label)
}
func TreePushOverrideID(id ImGuiID) {
var g = GImGui
var window = g.CurrentWindow
Indent(0)
window.DC.TreeDepth++
window.IDStack = append(window.IDStack, id)
}
// ~ Unindent()+PopId()
func TreePop() {
var g = GImGui
var window = g.CurrentWindow
Unindent(0)
window.DC.TreeDepth--
var tree_depth_mask ImU32 = (1 << window.DC.TreeDepth)
// Handle Left arrow to move to parent tree node (when ImGuiTreeNodeFlags_NavLeftJumpsBackHere is enabled)
if g.NavMoveDir == ImGuiDir_Left && g.NavWindow == window && NavMoveRequestButNoResultYet() {
if g.NavIdIsAlive && (window.DC.TreeJumpToParentOnPopMask&tree_depth_mask) != 0 {
SetNavID(window.IDStack[len(window.IDStack)-1], g.NavLayer, 0, &ImRect{})
NavMoveRequestCancel()
}
}
window.DC.TreeJumpToParentOnPopMask &= tree_depth_mask - 1
IM_ASSERT(len(window.IDStack) > 1) // There should always be 1 element in the IDStack (pushed during window creation). If this triggers you called TreePop/PopID too much.
PopID()
}
// Consume previous SetNextItemOpen() data, if any. May return true when logging
func TreeNodeBehaviorIsOpen(id ImGuiID, flags ImGuiTreeNodeFlags) bool {
if flags&ImGuiTreeNodeFlags_Leaf != 0 {
return true
}
// We only write to the tree storage if the user clicks (or explicitly use the SetNextItemOpen function)
var g = GImGui
var window = g.CurrentWindow
var storage = &window.DC.StateStorage
var is_open bool
if g.NextItemData.Flags&ImGuiNextItemDataFlags_HasOpen != 0 {
if g.NextItemData.OpenCond&ImGuiCond_Always != 0 {
is_open = g.NextItemData.OpenVal
storage.SetInt(id, bool2int(is_open))
} else {
// We treat ImGuiCond_Once and ImGuiCond_FirstUseEver the same because tree node state are not saved persistently.
var stored_value = storage.GetInt(id, -1)
if stored_value == -1 {
is_open = g.NextItemData.OpenVal
storage.SetInt(id, bool2int(is_open))
} else {
is_open = stored_value != 0
}
}
} else {
if (flags & ImGuiTreeNodeFlags_DefaultOpen) != 0 {
is_open = storage.GetInt(id, 1) != 0
} else {
is_open = storage.GetInt(id, 0) != 0
}
}
// When logging is enabled, we automatically expand tree nodes (but *NOT* collapsing headers.. seems like sensible behavior).
// NB- If we are above max depth we still allow manually opened nodes to be logged.
if g.LogEnabled && (flags&ImGuiTreeNodeFlags_NoAutoOpenOnLog) == 0 && (window.DC.TreeDepth-g.LogDepthRef) < g.LogDepthToExpand {
is_open = true
}
return is_open
}
func TreeNodeBehavior(id ImGuiID, flags ImGuiTreeNodeFlags, label string) bool {
var window = GetCurrentWindow()
if window.SkipItems {
return false
}
var g = GImGui
var style = &g.Style
var display_frame = (flags & ImGuiTreeNodeFlags_Framed) != 0
var padding ImVec2
if display_frame || (flags&ImGuiTreeNodeFlags_FramePadding) != 0 {
padding = style.FramePadding
} else {
padding = ImVec2{style.FramePadding.x, ImMin(window.DC.CurrLineTextBaseOffset, style.FramePadding.y)}
}
label = FindRenderedTextEnd(label)
var label_size = CalcTextSize(label, false, 0)
// We vertically grow up to current line height up the typical widget height.
var frame_height = ImMax(ImMin(window.DC.CurrLineSize.y, g.FontSize+style.FramePadding.y*2), label_size.y+padding.y*2)
var frame_bb ImRect
if (flags & ImGuiTreeNodeFlags_SpanFullWidth) != 0 {
frame_bb.Min.x = window.WorkRect.Min.x
} else {
frame_bb.Min.x = window.DC.CursorPos.x
}
frame_bb.Min.y = window.DC.CursorPos.y
frame_bb.Max.x = window.WorkRect.Max.x
frame_bb.Max.y = window.DC.CursorPos.y + frame_height
if display_frame {
// Framed header expand a little outside the default padding, to the edge of InnerClipRect
// (FIXME: May remove this at some point and make InnerClipRect align with WindowPadding.x instead of WindowPadding.x*0.5f)
frame_bb.Min.x -= IM_FLOOR(window.WindowPadding.x*0.5 - 1.0)
frame_bb.Max.x += IM_FLOOR(window.WindowPadding.x * 0.5)
}
// Collapser arrow width + Spacing
var text_offset_x = g.FontSize
if display_frame {
text_offset_x += padding.x * 3
} else {
text_offset_x += padding.x * 2
}
var text_offset_y = ImMax(padding.y, window.DC.CurrLineTextBaseOffset) // Latch before ItemSize changes it
var text_width = g.FontSize
if label_size.x > 0.0 { // Include collapser
text_width += padding.x * 2
}
var text_pos = ImVec2{window.DC.CursorPos.x + text_offset_x, window.DC.CursorPos.y + text_offset_y}
ItemSizeVec(&ImVec2{text_width, frame_height}, padding.y)
// For regular tree nodes, we arbitrary allow to click past 2 worth of ItemSpacing
var interact_bb = frame_bb
if !display_frame && (flags&(ImGuiTreeNodeFlags_SpanAvailWidth|ImGuiTreeNodeFlags_SpanFullWidth)) == 0 {
interact_bb.Max.x = frame_bb.Min.x + text_width + label_size.x + style.ItemSpacing.x*2.0
}
// Store a flag for the current depth to tell if we will allow closing this node when navigating one of its child.
// For this purpose we essentially compare if g.NavIdIsAlive went from 0 to 1 between TreeNode() and TreePop().
// This is currently only support 32 level deep and we are fine with (1 << Depth) overflowing into a zero.
var is_leaf = (flags & ImGuiTreeNodeFlags_Leaf) != 0
var is_open = TreeNodeBehaviorIsOpen(id, flags)
if is_open && !g.NavIdIsAlive && (flags&ImGuiTreeNodeFlags_NavLeftJumpsBackHere) != 0 && flags&ImGuiTreeNodeFlags_NoTreePushOnOpen == 0 {
window.DC.TreeJumpToParentOnPopMask |= (1 << window.DC.TreeDepth)
}
var item_add = ItemAdd(&interact_bb, id, nil, 0)
g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HasDisplayRect
g.LastItemData.DisplayRect = frame_bb
if !item_add {
if is_open && flags&ImGuiTreeNodeFlags_NoTreePushOnOpen == 0 {
TreePushOverrideID(id)
}
return is_open
}
var button_flags = ImGuiButtonFlags_None
if flags&ImGuiTreeNodeFlags_AllowItemOverlap != 0 {
button_flags |= ImGuiButtonFlags_AllowItemOverlap
}
if !is_leaf {
button_flags |= ImGuiButtonFlags_PressedOnDragDropHold
}
// We allow clicking on the arrow section with keyboard modifiers held, in order to easily
// allow browsing a tree while preserving selection with code implementing multi-selection patterns.
// When clicking on the rest of the tree node we always disallow keyboard modifiers.
var arrow_hit_x1 = (text_pos.x - text_offset_x) - style.TouchExtraPadding.x
var arrow_hit_x2 = (text_pos.x - text_offset_x) + (g.FontSize + padding.x*2.0) + style.TouchExtraPadding.x
var is_mouse_x_over_arrow = (g.IO.MousePos.x >= arrow_hit_x1 && g.IO.MousePos.x < arrow_hit_x2)
if window != g.HoveredWindow || !is_mouse_x_over_arrow {
button_flags |= ImGuiButtonFlags_NoKeyModifiers
}
// Open behaviors can be altered with the _OpenOnArrow and _OnOnDoubleClick flags.
// Some alteration have subtle effects (e.g. toggle on MouseUp vs MouseDown events) due to requirements for multi-selection and drag and drop support.
// - Single-click on label = Toggle on MouseUp (default, when _OpenOnArrow=0)
// - Single-click on arrow = Toggle on MouseDown (when _OpenOnArrow=0)
// - Single-click on arrow = Toggle on MouseDown (when _OpenOnArrow=1)
// - Double-click on label = Toggle on MouseDoubleClick (when _OpenOnDoubleClick=1)
// - Double-click on arrow = Toggle on MouseDoubleClick (when _OpenOnDoubleClick=1 and _OpenOnArrow=0)
// It is rather standard that arrow click react on Down rather than Up.
// We set ImGuiButtonFlags_PressedOnClickRelease on OpenOnDoubleClick because we want the item to be active on the initial MouseDown in order for drag and drop to work.
if is_mouse_x_over_arrow {
button_flags |= ImGuiButtonFlags_PressedOnClick
} else if flags&ImGuiTreeNodeFlags_OpenOnDoubleClick != 0 {
button_flags |= ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnDoubleClick
} else {
button_flags |= ImGuiButtonFlags_PressedOnClickRelease
}
var selected = (flags & ImGuiTreeNodeFlags_Selected) != 0
var was_selected = selected
var hovered, held bool
var pressed = ButtonBehavior(&interact_bb, id, &hovered, &held, button_flags)
var toggled = false
if !is_leaf {
if pressed && g.DragDropHoldJustPressedId != id {
if (flags&(ImGuiTreeNodeFlags_OpenOnArrow|ImGuiTreeNodeFlags_OpenOnDoubleClick)) == 0 || (g.NavActivateId == id) {
toggled = true
}
if flags&ImGuiTreeNodeFlags_OpenOnArrow != 0 {
toggled = toggled || (is_mouse_x_over_arrow && !g.NavDisableMouseHover) // Lightweight equivalent of IsMouseHoveringRect() since ButtonBehavior() already did the job
}
if (flags&ImGuiTreeNodeFlags_OpenOnDoubleClick) != 0 && g.IO.MouseDoubleClicked[0] {
toggled = true
}
} else if pressed && g.DragDropHoldJustPressedId == id {
IM_ASSERT(button_flags&ImGuiButtonFlags_PressedOnDragDropHold != 0)
if !is_open { // When using Drag and Drop "hold to open" we keep the node highlighted after opening, but never close it again.
toggled = true
}
}
if g.NavId == id && g.NavMoveDir == ImGuiDir_Left && is_open {
toggled = true
NavMoveRequestCancel()
}
if g.NavId == id && g.NavMoveDir == ImGuiDir_Right && !is_open { // If there's something upcoming on the line we may want to give it the priority?
toggled = true
NavMoveRequestCancel()
}
if toggled {
is_open = !is_open
window.DC.StateStorage.SetInt(id, bool2int(is_open))
g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_ToggledOpen
}
}
if flags&ImGuiTreeNodeFlags_AllowItemOverlap != 0 {
SetItemAllowOverlap()
}
// In this branch, TreeNodeBehavior() cannot toggle the selection so this will never trigger.
if selected != was_selected { //-V547
g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_ToggledSelection
}
// Render
var text_col = GetColorU32FromID(ImGuiCol_Text, 1)
var nav_highlight_flags = ImGuiNavHighlightFlags_TypeThin
if display_frame {
// Framed type
var bg_col ImU32
if held && hovered {
bg_col = GetColorU32FromID(ImGuiCol_HeaderActive, 1)
} else if hovered {
bg_col = GetColorU32FromID(ImGuiCol_HeaderHovered, 1)
} else {
bg_col = GetColorU32FromID(ImGuiCol_Header, 1)
}
RenderFrame(frame_bb.Min, frame_bb.Max, bg_col, true, style.FrameRounding)
RenderNavHighlight(&frame_bb, id, nav_highlight_flags)
if flags&ImGuiTreeNodeFlags_Bullet != 0 {
RenderBullet(window.DrawList, ImVec2{text_pos.x - text_offset_x*0.60, text_pos.y + g.FontSize*0.5}, text_col)
} else if !is_leaf {
var arrow ImGuiDir
if is_open {
arrow = ImGuiDir_Down
} else {
arrow = ImGuiDir_Right
}
RenderArrow(window.DrawList, ImVec2{text_pos.x - text_offset_x + padding.x, text_pos.y}, text_col, arrow, 1.0)
} else { // Leaf without bullet, left-adjusted text
text_pos.x -= text_offset_x
}
if flags&ImGuiTreeNodeFlags_ClipLabelForTrailingButton != 0 {
frame_bb.Max.x -= g.FontSize + style.FramePadding.x
}
if g.LogEnabled {
LogSetNextTextDecoration("###", "###")
}
RenderTextClipped(&text_pos, &frame_bb.Max, label, &label_size, nil, nil)
} else {
// Unframed typed for tree nodes
if hovered || selected {
var bg_col ImU32
if held && hovered {
bg_col = GetColorU32FromID(ImGuiCol_HeaderActive, 1)
} else if hovered {
bg_col = GetColorU32FromID(ImGuiCol_HeaderHovered, 1)
} else {
bg_col = GetColorU32FromID(ImGuiCol_Header, 1)
}
RenderFrame(frame_bb.Min, frame_bb.Max, bg_col, false, 0)
}
RenderNavHighlight(&frame_bb, id, nav_highlight_flags)
if flags&ImGuiTreeNodeFlags_Bullet != 0 {
RenderBullet(window.DrawList, ImVec2{text_pos.x - text_offset_x*0.5, text_pos.y + g.FontSize*0.5}, text_col)
} else if !is_leaf {
var arrow ImGuiDir
if is_open {
arrow = ImGuiDir_Down
} else {
arrow = ImGuiDir_Right
}
RenderArrow(window.DrawList, ImVec2{text_pos.x - text_offset_x + padding.x, text_pos.y + g.FontSize*0.15}, text_col, arrow, 0.70)
}
if g.LogEnabled {
LogSetNextTextDecoration(">", "")
}
RenderText(text_pos, label, false)
}
if is_open && flags&ImGuiTreeNodeFlags_NoTreePushOnOpen == 0 {
TreePushOverrideID(id)
}
return is_open
}