-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathwidgets.input.go
1339 lines (1180 loc) · 52.8 KB
/
widgets.input.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
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
package imgui
import (
"bytes"
"fmt"
"reflect"
"strings"
)
// Create text input in place of another active widget (e.g. used when doing a CTRL+Click on drag/slider widgets)
// FIXME: Facilitate using this in variety of other situations.
func TempInputText(bb *ImRect, id ImGuiID, label string, buf *[]byte, flags ImGuiInputTextFlags) bool {
// On the first frame, g.TempInputTextId == 0, then on subsequent frames it becomes == id.
// We clear ActiveID on the first frame to allow the InputText() taking it back.
var g = GImGui
var init = (g.TempInputId != id)
if init {
ClearActiveID()
}
g.CurrentWindow.DC.CursorPos = bb.Min
size := bb.GetSize()
var value_changed = InputTextEx(label, "", buf, &size, flags|ImGuiInputTextFlags_MergedItem, nil, nil)
if init {
// First frame we started displaying the InputText widget, we expect it to take the active id.
IM_ASSERT(g.ActiveId == id)
g.TempInputId = g.ActiveId
}
return value_changed
}
// Note that Drag/Slider functions are only forwarding the min/max values clamping values if the ImGuiSliderFlags_AlwaysClamp flag is set!
// This is intended: this way we allow CTRL+Click manual input to set a value out of bounds, for maximum flexibility.
// However this may not be ideal for all uses, as some user code may break on out of bound values.
func TempInputScalar(bb *ImRect, id ImGuiID, label string, data_type ImGuiDataType, p_data any, format string, p_clamp_min any, p_clamp_max any) bool {
var g = GImGui
p_data_val := reflect.ValueOf(p_data).Elem()
var data_buf = []byte(strings.TrimSpace(fmt.Sprintf(format, p_data_val)))
var flags = ImGuiInputTextFlags_AutoSelectAll | ImGuiInputTextFlags_NoMarkEdited
flags |= ImGuiInputTextFlags_CharsDecimal
if data_type == ImGuiDataType_Float || data_type == ImGuiDataType_Double {
flags = ImGuiInputTextFlags_CharsScientific
}
var value_changed = false
if TempInputText(bb, id, label, &data_buf, flags) {
// Backup old value
var data_backup = reflect.ValueOf(p_data).Elem().Interface()
// Apply new value (or operations) then clamp
DataTypeApplyOpFromText(string(data_buf), string(g.InputTextState.InitialTextA), data_type, p_data, "")
if p_clamp_min != nil || p_clamp_max != nil {
if p_clamp_min != nil && p_clamp_max != nil && DataTypeCompare(data_type, p_clamp_min, p_clamp_max) > 0 {
p_clamp_min, p_clamp_max = p_clamp_max, p_clamp_min
}
DataTypeClamp(data_type, p_data, p_clamp_min, p_clamp_max)
}
// Only mark as edited if new value is different
value_changed = reflect.ValueOf(p_data).Elem().Interface() != data_backup
if value_changed {
MarkItemEdited(id)
}
}
return value_changed
}
func TempInputIsActive(id ImGuiID) bool {
var g = GImGui
return (g.ActiveId == id && g.TempInputId == id)
}
func GetInputTextState(id ImGuiID) *ImGuiInputTextState {
var g = GImGui
if g.InputTextState.ID == id {
return &g.InputTextState
}
return nil
} // Get input text state if active
func InputText(label string, char *[]byte, flags ImGuiInputTextFlags, callback ImGuiInputTextCallback /*= L*/, user_data any) bool {
IM_ASSERT((flags & ImGuiInputTextFlags_Multiline) == 0) // call InputTextMultiline()
return InputTextEx(label, "", char, &ImVec2{}, flags, callback, user_data)
}
func InputTextMultiline(label string, buf *[]byte, size ImVec2, flags ImGuiInputTextFlags, callback ImGuiInputTextCallback /*= L*/, user_data any) bool {
return InputTextEx(label, "", buf, &size, flags|ImGuiInputTextFlags_Multiline, callback, user_data)
}
func InputTextWithHint(label string, hint string, char *[]byte, flags ImGuiInputTextFlags, callback ImGuiInputTextCallback /*= L*/, user_data any) bool {
IM_ASSERT((flags & ImGuiInputTextFlags_Multiline) == 0) // call InputTextMultiline()
return InputTextEx(label, hint, char, &ImVec2{}, flags, callback, user_data)
}
func InputFloat(label string, v *float, step, step_fast float, format string, flags ImGuiInputTextFlags) bool {
flags |= ImGuiInputTextFlags_CharsScientific
var step_arg *float
if step > 0.0 {
step_arg = &step
}
var step_fast_arg *float
if step_fast > 0.0 {
step_fast_arg = &step_fast
}
return InputScalarFloat32(label, v, step_arg, step_fast_arg, format, flags)
}
func InputFloat2(label string, v *[2]float, format string, flags ImGuiInputTextFlags) bool {
return InputScalarFloat32s(label, (*v)[:], nil, nil, format, flags)
}
func InputFloat3(label string, v *[3]float, format string /*= "%.3f"*/, flags ImGuiInputTextFlags) bool {
return InputScalarFloat32s(label, (*v)[:], nil, nil, format, flags)
}
func InputFloat4(label string, v *[4]float, format string /*= "%.3f"*/, flags ImGuiInputTextFlags) bool {
return InputScalarFloat32s(label, (*v)[:], nil, nil, format, flags)
}
func InputInt(label string, v *int, step int /*= 1*/, step_fast int /*= 100*/, flags ImGuiInputTextFlags) bool {
// Hexadecimal input provided as a convenience but the flag name is awkward. Typically you'd use InputText() to parse your own data, if you want to handle prefixes.
var format = "%v"
if (flags & ImGuiInputTextFlags_CharsHexadecimal) != 0 {
format = "%08X"
}
var step_arg *int
if step > 0.0 {
step_arg = &step
}
var step_fast_arg *int
if step_fast > 0.0 {
step_fast_arg = &step_fast
}
return InputScalarInt32(label, v, step_arg, step_fast_arg, format, flags)
}
func InputInt2(label string, v *[2]int, flags ImGuiInputTextFlags) bool {
return InputScalarInt32s(label, v[:], nil, nil, "%d", flags)
}
func InputInt3(label string, v *[3]int, flags ImGuiInputTextFlags) bool {
return InputScalarInt32s(label, v[:], nil, nil, "%d", flags)
}
func InputInt4(label string, v *[4]int, flags ImGuiInputTextFlags) bool {
return InputScalarInt32s(label, v[:], nil, nil, "%d", flags)
}
func InputDouble(label string, v *double, step double /*= 0*/, step_fast double /*= 0*/, format string /*= "%.6f"*/, flags ImGuiInputTextFlags) bool {
flags |= ImGuiInputTextFlags_CharsScientific
var step_arg *double
if step > 0.0 {
step_arg = &step
}
var step_fast_arg *double
if step_fast > 0.0 {
step_fast_arg = &step_fast
}
return InputScalarFloat64(label, v, step_arg, step_fast_arg, format, flags)
}
func InputTextCalcLineCount(text string) int {
return int(strings.Count(text, "\n"))
}
func InputTextCalcTextSizeW(text []ImWchar, remaining *[]ImWchar, out_offset *ImVec2, stop_on_new_line bool) ImVec2 {
var g = GImGui
var font = g.Font
var line_height = g.FontSize
var scale = line_height / font.FontSize
var text_size ImVec2
var line_width float
var s = text
for len(s) > 0 {
var c = s[0]
s = s[1:]
if c == '\n' {
text_size.x = ImMax(text_size.x, line_width)
text_size.y += line_height
line_width = 0.0
if stop_on_new_line {
break
}
continue
}
if c == '\r' {
continue
}
var char_width = font.GetCharAdvance(rune(c)) * scale
line_width += char_width
}
if text_size.x < line_width {
text_size.x = line_width
}
if out_offset != nil {
*out_offset = ImVec2{line_width, text_size.y + line_height} // offset allow for the possibility of sitting after a trailing \n
}
if line_width > 0 || text_size.y == 0.0 { // whereas size.y will ignore the trailing \n
text_size.y += line_height
}
if remaining != nil {
*remaining = s
}
return text_size
}
func resize(buf *[]byte, new_size int) {
if new_size > int(len(*buf)) {
var new_buf = make([]byte, new_size)
copy(new_buf, *buf)
*buf = new_buf
} else {
*buf = (*buf)[:new_size]
}
}
func resizeRune(buf *[]rune, new_size int) {
if new_size > int(len(*buf)) {
var new_buf = make([]rune, new_size)
copy(new_buf, *buf)
*buf = new_buf
} else {
*buf = (*buf)[:new_size]
}
}
func runesContains(runes []rune, c rune) bool {
for _, r := range runes {
if r == c {
return true
}
}
return false
}
// Return false to discard a character.
func InputTextFilterCharacter(p_char *rune, flags ImGuiInputTextFlags, callback ImGuiInputTextCallback, user_data any, input_source ImGuiInputSource) bool {
IM_ASSERT(input_source == ImGuiInputSource_Keyboard || input_source == ImGuiInputSource_Clipboard)
var c = *p_char
// Filter non-printable (NB: isprint is unreliable! see #2467)
var apply_named_filters = true
if c < 0x20 {
var pass = false
pass = pass || (c == '\n' && (flags&ImGuiInputTextFlags_Multiline != 0))
pass = pass || (c == '\t' && (flags&ImGuiInputTextFlags_AllowTabInput != 0))
if !pass {
return false
}
apply_named_filters = false // Override named filters below so newline and tabs can still be inserted.
}
if input_source != ImGuiInputSource_Clipboard {
// We ignore Ascii representation of delete (emitted from Backspace on OSX, see #2578, #2817)
if c == 127 {
return false
}
// Filter private Unicode range. GLFW on OSX seems to send private characters for special keys like arrow keys (FIXME)
if c >= 0xE000 && c <= 0xF8FF {
return false
}
}
// Filter Unicode ranges we are not handling in this build
if c > IM_UNICODE_CODEPOINT_MAX {
return false
}
// Generic named filters
if apply_named_filters && (flags&(ImGuiInputTextFlags_CharsDecimal|ImGuiInputTextFlags_CharsHexadecimal|ImGuiInputTextFlags_CharsUppercase|ImGuiInputTextFlags_CharsNoBlank|ImGuiInputTextFlags_CharsScientific) != 0) {
// The libc allows overriding locale, with e.g. 'setlocale(LC_NUMERIC, "de_DE.UTF-8");' which affect the output/input of printf/scanf.
// The standard mandate that programs starts in the "C" locale where the decimal point is '.'.
// We don't really intend to provide widespread support for it, but out of empathy for people stuck with using odd API, we support the bare minimum aka overriding the decimal point.
// Change the default decimal_point with:
// ImGui::GetCurrentContext()->PlatformLocaleDecimalPoint = *localeconv()->decimal_point;
var g = GImGui
var c_decimal_point = (rune)(g.PlatformLocaleDecimalPoint)
// Allow 0-9 . - + * /
if flags&ImGuiInputTextFlags_CharsDecimal != 0 {
if !(c >= '0' && c <= '9') && (c != c_decimal_point) && (c != '-') && (c != '+') && (c != '*') && (c != '/') {
return false
}
}
// Allow 0-9 . - + * / e E
if flags&ImGuiInputTextFlags_CharsScientific != 0 {
if !(c >= '0' && c <= '9') && (c != c_decimal_point) && (c != '-') && (c != '+') && (c != '*') && (c != '/') && (c != 'e') && (c != 'E') {
return false
}
}
// Allow 0-9 a-F A-F
if flags&ImGuiInputTextFlags_CharsHexadecimal != 0 {
if !(c >= '0' && c <= '9') && !(c >= 'a' && c <= 'f') && !(c >= 'A' && c <= 'F') {
return false
}
}
// Turn a-z into A-Z
if flags&ImGuiInputTextFlags_CharsUppercase != 0 {
if c >= 'a' && c <= 'z' {
c += (rune)('A' - 'a')
*p_char = (c)
}
}
if flags&ImGuiInputTextFlags_CharsNoBlank != 0 {
if ImCharIsBlankW(c) {
return false
}
}
}
// Custom callback filter
if flags&ImGuiInputTextFlags_CallbackCharFilter != 0 {
var callback_data ImGuiInputTextCallbackData
callback_data.EventFlag = ImGuiInputTextFlags_CallbackCharFilter
callback_data.EventChar = (ImWchar)(c)
callback_data.Flags = flags
callback_data.UserData = user_data
if callback(&callback_data) != 0 {
return false
}
*p_char = callback_data.EventChar
if callback_data.EventChar == 0 {
return false
}
}
return true
}
func ImStrbolW(buf_mid_line []ImWchar, buf_begin []ImWchar) []ImWchar { // find beginning-of-line
// FIXME: this is probably wrong
/*
while (buf_mid_line > buf_begin && buf_mid_line[-1] != '\n')
buf_mid_line--;
return buf_mid_line;
*/
var i int
for i = int(len(buf_mid_line) - 1); i > 0 && buf_mid_line[i] != '\n'; i-- {
// noop
}
return []ImWchar{ImWchar(i)}
}
/*
ABSOLUTE MONSTER OF A FUNCTION AHEAD
VERY DIFFICULT TO PORT AND LIKELY DOESN'T WORK
IN THE CURRENT STATE
*/
// Edit a string of text
// - buf_size account for the zero-terminator, so a buf_size of 6 can hold "Hello" but not "Hello!".
// This is so we can easily call InputText() on static arrays using ARRAYSIZE() and to match
// Note that in std::string world, capacity() would omit 1 byte used by the zero-terminator.
// - When active, hold on a privately held copy of the text (and apply back to 'buf'). So changing 'buf' while the InputText is active has no effect.
// - If you want to use ImGui::InputText() with std::string, see misc/cpp/imgui_stdlib.h
//
// (FIXME: Rather confusing and messy function, among the worse part of our codebase, expecting to rewrite a V2 at some point.. Partly because we are
//
// doing UTF8 > U16 > UTF8 conversions on the go to easily interface with stb_textedit. Ideally should stay in UTF-8 all the time. See https://github.com/nothings/stb/issues/188)
func InputTextEx(label string, hint string, buf *[]byte, size_arg *ImVec2, flags ImGuiInputTextFlags, callback ImGuiInputTextCallback, callback_user_data any) bool {
var window = GetCurrentWindow()
if window.SkipItems {
return false
}
IM_ASSERT(buf != nil && int(len(*buf)) >= 0)
// TODO: check these asserts
IM_ASSERT(!((flags&ImGuiInputTextFlags_CallbackHistory) == 0 && (flags&ImGuiInputTextFlags_Multiline != 0))) // Can't use both together (they both use up/down keys)
IM_ASSERT(!((flags&ImGuiInputTextFlags_CallbackCompletion) == 0 && (flags&ImGuiInputTextFlags_AllowTabInput != 0))) // Can't use both together (they both use tab key)
var g = GImGui
var io = g.IO
var style = g.Style
var RENDER_SELECTION_WHEN_INACTIVE = false
var is_multiline = (flags & ImGuiInputTextFlags_Multiline) != 0
var is_readonly = (flags & ImGuiInputTextFlags_ReadOnly) != 0
var is_password = (flags & ImGuiInputTextFlags_Password) != 0
var is_undoable = (flags & ImGuiInputTextFlags_NoUndoRedo) == 0
var is_resizable = (flags & ImGuiInputTextFlags_CallbackResize) != 0
if is_resizable {
IM_ASSERT(callback != nil) // Must provide a callback if you set the ImGuiInputTextFlags_CallbackResize flag!
}
if is_multiline { // Open group before calling GetID() because groups tracks id created within their scope,
BeginGroup()
}
var id = window.GetIDs(label)
var label_size = CalcTextSize(label, true, -1)
var padding float
if label_size.x > 0.0 {
padding = style.ItemInnerSpacing.x + label_size.x
}
fontsize := label_size.y
if is_multiline {
fontsize = g.FontSize * 8.0
}
var frame_size = CalcItemSize(*size_arg, CalcItemWidth(), fontsize+style.FramePadding.y*2.0) // Arbitrary default of 8 lines high for multi-line
var total_size = ImVec2{frame_size.x + padding, frame_size.y}
var frame_bb = ImRect{window.DC.CursorPos, window.DC.CursorPos.Add(frame_size)}
var total_bb = ImRect{frame_bb.Min, frame_bb.Min.Add(total_size)}
var draw_window = window
var inner_size = frame_size
var item_status_flags ImGuiItemStatusFlags = 0
if is_multiline {
var backup_pos = window.DC.CursorPos
ItemSizeRect(&total_bb, style.FramePadding.y)
if !ItemAdd(&total_bb, id, &frame_bb, ImGuiItemFlags_Inputable) {
EndGroup()
return false
}
item_status_flags = g.LastItemData.StatusFlags
window.DC.CursorPos = backup_pos
// We reproduce the contents of BeginChildFrame() in order to provide 'label' so our window internal data are easier to read/debug.
PushStyleColorVec(ImGuiCol_ChildBg, &style.Colors[ImGuiCol_FrameBg])
PushStyleFloat(ImGuiStyleVar_ChildRounding, style.FrameRounding)
PushStyleFloat(ImGuiStyleVar_ChildBorderSize, style.FrameBorderSize)
size := frame_bb.GetSize()
var child_visible = BeginChildEx(label, id, &size, true, ImGuiWindowFlags_NoMove)
PopStyleVar(2)
PopStyleColor(1)
if !child_visible {
EndChild()
EndGroup()
return false
}
draw_window = g.CurrentWindow // Child window
draw_window.DC.NavLayersActiveMaskNext |= (1 << draw_window.DC.NavLayerCurrent) // This is to ensure that EndChild() will display a navigation highlight so we can "enter" into it.
draw_window.DC.CursorPos = draw_window.DC.CursorPos.Add(style.FramePadding)
inner_size.x -= draw_window.ScrollbarSizes.x
} else {
// Support for internal ImGuiInputTextFlags_MergedItem flag, which could be redesigned as an ItemFlags if needed (with test performed in ItemAdd)
ItemSizeRect(&total_bb, style.FramePadding.y)
if (flags & ImGuiInputTextFlags_MergedItem) == 0 {
if !ItemAdd(&total_bb, id, &frame_bb, ImGuiItemFlags_Inputable) {
return false
}
}
item_status_flags = g.LastItemData.StatusFlags
}
var hovered = ItemHoverable(&frame_bb, id)
if hovered {
g.MouseCursor = ImGuiMouseCursor_TextInput
}
// We are only allowed to access the state if we are already the active widget.
var state = GetInputTextState(id)
var focus_requested_by_code = (item_status_flags & ImGuiItemStatusFlags_FocusedByCode) != 0
var focus_requested_by_tabbing = (item_status_flags & ImGuiItemStatusFlags_FocusedByTabbing) != 0
var user_clicked = hovered && io.MouseClicked[0]
var user_nav_input_start = (g.ActiveId != id) && ((g.NavInputId == id) || (g.NavActivateId == id && g.NavInputSource == ImGuiInputSource_Keyboard))
var user_scroll_finish = is_multiline && state != nil && g.ActiveId == 0 && g.ActiveIdPreviousFrame == GetWindowScrollbarID(draw_window, ImGuiAxis_Y)
var user_scroll_active = is_multiline && state != nil && g.ActiveId == GetWindowScrollbarID(draw_window, ImGuiAxis_Y)
var clear_active_id = false
var select_all = (g.ActiveId != id) && ((flags&ImGuiInputTextFlags_AutoSelectAll) != 0 || user_nav_input_start) && (!is_multiline)
var scroll_y float = FLT_MAX
if is_multiline {
scroll_y = draw_window.Scroll.y
}
var init_changed_specs = (state != nil && (state.Stb.single_line != 0) != !is_multiline)
var init_make_active = (user_clicked || user_scroll_finish || user_nav_input_start || focus_requested_by_code || focus_requested_by_tabbing)
var init_state = (init_make_active || user_scroll_active)
if (init_state && g.ActiveId != id) || init_changed_specs {
// Access state even if we don't own it yet.
state = &g.InputTextState
state.CursorAnimReset()
// Take a copy of the initial buffer value (both in original UTF-8 format and converted to wchar)
// From the moment we focused we are ignoring the content of 'buf' (unless we are in read-only mode)
var buf_len = (int)(len(*buf))
// UTF-8. we use +1 to make sure that .Data is always pointing to at least an empty string.
resize(&state.InitialTextA, buf_len+1)
copy(state.InitialTextA, *buf)
// Start edition
var buf_end string
resizeRune(&state.TextW, int(len(*buf)+1))
resizeRune(&state.TextW, int(len(*buf)+1)) // wchar count <= UTF-8 count. we use +1 to make sure that .Data is always pointing to at least an empty string.
state.TextA = state.TextA[:0]
state.TextAIsValid = false // TextA is not valid yet (we will display buf until then)
state.CurLenW = ImTextStrFromUtf8(state.TextW, int(len(*buf)), string(*buf), &buf_end)
state.CurLenA = (int)(len(*buf) - len(buf_end)) // We can't get the result from ImStrncpy() above because it is not UTF-8 aware. Here we'll cut off malformed UTF-8.
// Preserve cursor position and undo/redo stack if we come back to same widget
// FIXME: For non-readonly widgets we might be able to require that TextAIsValid && TextA == buf ? (untested) and discard undo stack if user buffer has changed.
var recycle_state = (state.ID == id && !init_changed_specs)
if recycle_state {
// Recycle existing cursor/selection/undo stack but clamp position
// Note a single mouse click will override the cursor/position immediately by calling stb_textedit_click handler.
state.CursorClamp()
} else {
state.ID = id
state.ScrollX = 0.0
stb_textedit_initialize_state(&state.Stb, bool2int(!is_multiline))
if !is_multiline && focus_requested_by_code {
select_all = true
}
}
if (flags & ImGuiInputTextFlags_AlwaysOverwrite) != 0 {
state.Stb.insert_mode = 1 // stb field name is indeed incorrect (see #2863)
}
if !is_multiline && (focus_requested_by_tabbing || (user_clicked && io.KeyCtrl)) {
select_all = true
}
}
if g.ActiveId != id && init_make_active {
IM_ASSERT(state != nil && state.ID == id)
SetActiveID(id, window)
SetFocusID(id, window)
FocusWindow(window)
// Declare our inputs
IM_ASSERT(ImGuiNavInput_COUNT < 32)
g.ActiveIdUsingNavDirMask |= (1 << ImGuiDir_Left) | (1 << ImGuiDir_Right)
if is_multiline || (flags&ImGuiInputTextFlags_CallbackHistory != 0) {
g.ActiveIdUsingNavDirMask |= (1 << ImGuiDir_Up) | (1 << ImGuiDir_Down)
}
g.ActiveIdUsingNavInputMask |= (1 << ImGuiNavInput_Cancel)
g.ActiveIdUsingKeyInputMask |= ((ImU64)(1 << ImGuiKey_Home)) | ((ImU64)(1 << ImGuiKey_End))
if is_multiline {
g.ActiveIdUsingKeyInputMask |= ((ImU64)(1 << ImGuiKey_PageUp)) | ((ImU64)(1 << ImGuiKey_PageDown))
}
if flags&(ImGuiInputTextFlags_CallbackCompletion|ImGuiInputTextFlags_AllowTabInput) != 0 { // Disable keyboard tabbing out as we will use the \t character.
g.ActiveIdUsingKeyInputMask |= ((ImU64)(1 << ImGuiKey_Tab))
}
}
// We have an edge case if ActiveId was set through another widget (e.g. widget being swapped), clear id immediately (don't wait until the end of the function)
if g.ActiveId == id && state == nil {
ClearActiveID()
}
// Release focus when we click outside
if g.ActiveId == id && io.MouseClicked[0] && !init_state && !init_make_active { //-V560
clear_active_id = true
}
// Lock the decision of whether we are going to take the path displaying the cursor or selection
var render_cursor = (g.ActiveId == id) || (state != nil && user_scroll_active)
var render_selection = state != nil && state.HasSelection() && (RENDER_SELECTION_WHEN_INACTIVE || render_cursor)
var value_changed = false
var enter_pressed = false
// When read-only we always use the live data passed to the function
// FIXME-OPT: Because our selection/cursor code currently needs the wide text we need to convert it when active, which is not ideal :(
if is_readonly && state != nil && (render_cursor || render_selection) {
var buf_end string
resizeRune(&state.TextW, int(len(*buf))+1)
state.CurLenW = ImTextStrFromUtf8(state.TextW, int(len(state.TextW)), string(*buf), &buf_end)
state.CurLenA = (int)(len(*buf) - len(buf_end))
state.CursorClamp()
render_selection = render_selection && state.HasSelection()
}
// Select the buffer to render.
var buf_display_from_state = (render_cursor || render_selection || g.ActiveId == id) && !is_readonly && state != nil && state.TextAIsValid
b := *buf
if buf_display_from_state {
b = state.TextA
}
var is_displaying_hint = (hint != "" && b[0] == 0)
// Password pushes a temporary font with only a fallback glyph
if is_password && !is_displaying_hint {
var glyph = g.Font.FindGlyph('*')
var password_font = &g.InputTextPasswordFont
password_font.FontSize = g.Font.FontSize
password_font.Scale = g.Font.Scale
password_font.Ascent = g.Font.Ascent
password_font.Descent = g.Font.Descent
password_font.ContainerAtlas = g.Font.ContainerAtlas
password_font.FallbackGlyph = glyph
password_font.FallbackAdvanceX = glyph.AdvanceX
IM_ASSERT(len(password_font.Glyphs) == 0 && len(password_font.IndexAdvanceX) == 0 && len(password_font.IndexLookup) == 0)
PushFont(password_font)
}
// Process mouse inputs and character inputs
var backup_current_text_length int = 0
if g.ActiveId == id {
IM_ASSERT(state != nil)
backup_current_text_length = state.CurLenA
state.Edited = false
state.BufCapacityA = int(len(*buf))
state.Flags = flags
state.UserCallback = callback
state.UserCallbackData = callback_user_data
// Although we are active we don't prevent mouse from hovering other elements unless we are interacting right now with the widget.
// Down the line we should have a cleaner library-wide concept of Selected vs Active.
g.ActiveIdAllowOverlap = !io.MouseDown[0]
g.WantTextInputNextFrame = 1
// Edit in progress
var mouse_x = (io.MousePos.x - frame_bb.Min.x - style.FramePadding.x) + state.ScrollX
var mouse_y = g.FontSize * 0.5
if is_multiline {
mouse_y = io.MousePos.y - draw_window.DC.CursorPos.y
}
var is_osx = io.ConfigMacOSXBehaviors
if select_all || (hovered && !is_osx && io.MouseDoubleClicked[0]) {
state.SelectAll()
state.SelectedAllMouseLock = true
} else if hovered && is_osx && io.MouseDoubleClicked[0] {
// Double-click select a word only, OS X style (by simulating keystrokes)
state.OnKeyPressed(STB_TEXTEDIT_K_WORDLEFT)
state.OnKeyPressed(STB_TEXTEDIT_K_WORDRIGHT | STB_TEXTEDIT_K_SHIFT)
} else if io.MouseClicked[0] && !state.SelectedAllMouseLock {
if hovered {
stb_textedit_click(state, &state.Stb, mouse_x, mouse_y)
state.CursorAnimReset()
}
} else if io.MouseDown[0] && !state.SelectedAllMouseLock && (io.MouseDelta.x != 0.0 || io.MouseDelta.y != 0.0) {
stb_textedit_drag(state, &state.Stb, mouse_x, mouse_y)
state.CursorAnimReset()
state.CursorFollow = true
}
if state.SelectedAllMouseLock && !io.MouseDown[0] {
state.SelectedAllMouseLock = false
}
// It is ill-defined whether the backend needs to send a \t character when pressing the TAB keys.
// Win32 and GLFW naturally do it but not SDL.
var ignore_char_inputs = (io.KeyCtrl && !io.KeyAlt) || (is_osx && io.KeySuper)
if (flags&ImGuiInputTextFlags_AllowTabInput != 0) && IsKeyPressedMap(ImGuiKey_Tab, true) && !ignore_char_inputs && !io.KeyShift && !is_readonly {
if !runesContains(io.InputQueueCharacters, '\t') {
var c = '\t' // Insert TAB
if InputTextFilterCharacter(&c, flags, callback, callback_user_data, ImGuiInputSource_Keyboard) {
state.OnKeyPressed((int)(c))
}
}
}
// Process regular text input (before we check for Return because using some IME will effectively send a Return?)
// We ignore CTRL inputs, but need to allow ALT+CTRL as some keyboards (e.g. German) use AltGR (which _is_ Alt+Ctrl) to input certain characters.
if len(io.InputQueueCharacters) > 0 {
if !ignore_char_inputs && !is_readonly && !user_nav_input_start {
for n := range io.InputQueueCharacters {
// Insert character if they pass filtering
var c = (rune)(io.InputQueueCharacters[n])
if c == '\t' && io.KeyShift {
continue
}
if InputTextFilterCharacter(&c, flags, callback, callback_user_data, ImGuiInputSource_Keyboard) {
state.OnKeyPressed((int)(c))
}
}
}
// Consume characters
io.InputQueueCharacters = io.InputQueueCharacters[:0]
}
}
// Process other shortcuts/key-presses
var cancel_edit = false
if g.ActiveId == id && !g.ActiveIdIsJustActivated && !clear_active_id {
IM_ASSERT(state != nil)
IM_ASSERT_USER_ERROR(io.KeyMods == GetMergedKeyModFlags(), "Mismatching io.KeyCtrl/io.KeyShift/io.KeyAlt/io.KeySuper vs io.KeyMods") // We rarely do this check, but if anything let's do it here.
var row_count_per_page = ImMaxInt((int)((inner_size.y-style.FramePadding.y)/g.FontSize), 1)
state.Stb.row_count_per_page = row_count_per_page
var k_mask int
if io.KeyShift {
k_mask = STB_TEXTEDIT_K_SHIFT
}
var is_osx = io.ConfigMacOSXBehaviors
var is_osx_shift_shortcut = is_osx && (io.KeyMods == (ImGuiKeyModFlags_Super | ImGuiKeyModFlags_Shift))
var is_wordmove_key_down = io.KeyCtrl
var is_startend_key_down = is_osx && io.KeySuper && !io.KeyCtrl && !io.KeyAlt // OS X style: Line/Text Start and End using Cmd+Arrows instead of Home/End
if is_osx {
is_wordmove_key_down = io.KeyAlt // OS X style: Text editing cursor movement using Alt instead of Ctrl
}
var is_ctrl_key_only = (io.KeyMods == ImGuiKeyModFlags_Ctrl)
var is_shift_key_only = (io.KeyMods == ImGuiKeyModFlags_Shift)
var is_shortcut_key = (io.KeyMods == ImGuiKeyModFlags_Ctrl)
if g.IO.ConfigMacOSXBehaviors {
is_shortcut_key = (io.KeyMods == ImGuiKeyModFlags_Super)
}
var is_cut = ((is_shortcut_key && IsKeyPressedMap(ImGuiKey_X, true)) || (is_shift_key_only && IsKeyPressedMap(ImGuiKey_Delete, true))) && !is_readonly && !is_password && (!is_multiline || state.HasSelection())
var is_copy = ((is_shortcut_key && IsKeyPressedMap(ImGuiKey_C, true)) || (is_ctrl_key_only && IsKeyPressedMap(ImGuiKey_Insert, true))) && !is_password && (!is_multiline || state.HasSelection())
var is_paste = ((is_shortcut_key && IsKeyPressedMap(ImGuiKey_V, true)) || (is_shift_key_only && IsKeyPressedMap(ImGuiKey_Insert, true))) && !is_readonly
var is_undo = ((is_shortcut_key && IsKeyPressedMap(ImGuiKey_Z, true)) && !is_readonly && is_undoable)
var is_redo = ((is_shortcut_key && IsKeyPressedMap(ImGuiKey_Y, true)) || (is_osx_shift_shortcut && IsKeyPressedMap(ImGuiKey_Z, true))) && !is_readonly && is_undoable
if IsKeyPressedMap(ImGuiKey_LeftArrow, true) {
var k int = STB_TEXTEDIT_K_LEFT
if is_startend_key_down {
k = STB_TEXTEDIT_K_LINESTART
} else {
if is_wordmove_key_down {
k = STB_TEXTEDIT_K_WORDLEFT
}
}
state.OnKeyPressed(k | k_mask)
} else if IsKeyPressedMap(ImGuiKey_RightArrow, true) {
var k int = STB_TEXTEDIT_K_RIGHT
if is_startend_key_down {
k = STB_TEXTEDIT_K_LINEEND
} else {
if is_wordmove_key_down {
k = STB_TEXTEDIT_K_WORDRIGHT
}
}
state.OnKeyPressed(k | k_mask)
} else if IsKeyPressedMap(ImGuiKey_UpArrow, true) && is_multiline {
if io.KeyCtrl {
setScrollY(draw_window, ImMax(draw_window.Scroll.y-g.FontSize, 0.0))
} else {
var k int = STB_TEXTEDIT_K_UP
if is_startend_key_down {
k = STB_TEXTEDIT_K_TEXTSTART
}
state.OnKeyPressed(k | k_mask)
}
} else if IsKeyPressedMap(ImGuiKey_DownArrow, true) && is_multiline {
if io.KeyCtrl {
setScrollY(draw_window, ImMin(draw_window.Scroll.y+g.FontSize, GetScrollMaxY()))
} else {
var k int = STB_TEXTEDIT_K_DOWN
if is_startend_key_down {
k = STB_TEXTEDIT_K_TEXTEND
}
state.OnKeyPressed(k | k_mask)
}
} else if IsKeyPressedMap(ImGuiKey_PageUp, true) && is_multiline {
state.OnKeyPressed(STB_TEXTEDIT_K_PGUP | k_mask)
scroll_y -= float(row_count_per_page) * g.FontSize
} else if IsKeyPressedMap(ImGuiKey_PageDown, true) && is_multiline {
state.OnKeyPressed(STB_TEXTEDIT_K_PGDOWN | k_mask)
scroll_y += float(row_count_per_page) * g.FontSize
} else if IsKeyPressedMap(ImGuiKey_Home, true) {
var k int = STB_TEXTEDIT_K_LINESTART
if is_startend_key_down {
k = STB_TEXTEDIT_K_TEXTSTART
}
state.OnKeyPressed(k | k_mask)
} else if IsKeyPressedMap(ImGuiKey_End, true) {
var k int = STB_TEXTEDIT_K_LINEEND
if is_startend_key_down {
k = STB_TEXTEDIT_K_TEXTEND
}
state.OnKeyPressed(k | k_mask)
} else if IsKeyPressedMap(ImGuiKey_Delete, true) && !is_readonly {
state.OnKeyPressed(STB_TEXTEDIT_K_DELETE | k_mask)
} else if IsKeyPressedMap(ImGuiKey_Backspace, true) && !is_readonly {
if !state.HasSelection() {
if is_wordmove_key_down {
state.OnKeyPressed(STB_TEXTEDIT_K_WORDLEFT | STB_TEXTEDIT_K_SHIFT)
} else if is_osx && io.KeySuper && !io.KeyAlt && !io.KeyCtrl {
state.OnKeyPressed(STB_TEXTEDIT_K_LINESTART | STB_TEXTEDIT_K_SHIFT)
}
}
state.OnKeyPressed(STB_TEXTEDIT_K_BACKSPACE | k_mask)
} else if IsKeyPressedMap(ImGuiKey_Enter, true) || IsKeyPressedMap(ImGuiKey_KeyPadEnter, true) {
var ctrl_enter_for_new_line = (flags & ImGuiInputTextFlags_CtrlEnterForNewLine) != 0
if !is_multiline || (ctrl_enter_for_new_line && !io.KeyCtrl) || (!ctrl_enter_for_new_line && io.KeyCtrl) {
enter_pressed = true
clear_active_id = true
} else if !is_readonly {
var c = '\n' // Insert new line
if InputTextFilterCharacter(&c, flags, callback, callback_user_data, ImGuiInputSource_Keyboard) {
state.OnKeyPressed((int)(c))
}
}
} else if IsKeyPressedMap(ImGuiKey_Escape, true) {
clear_active_id = true
cancel_edit = true
} else if is_undo || is_redo {
var action int = STB_TEXTEDIT_K_REDO
if is_undo {
action = STB_TEXTEDIT_K_UNDO
}
state.OnKeyPressed(action)
state.ClearSelection()
} else if is_shortcut_key && IsKeyPressedMap(ImGuiKey_A, true) {
state.SelectAll()
state.CursorFollow = true
} else if is_cut || is_copy {
// Cut, Copy
if io.SetClipboardTextFn != nil {
var ib int = 0
if state.HasSelection() {
ib = ImMinInt(state.Stb.select_start, state.Stb.select_end)
}
var ie int
if state.HasSelection() {
ie = ImMaxInt(state.Stb.select_start, state.Stb.select_end)
} else {
ie = state.CurLenW
}
var clipboard_data_len = ImTextCountUtf8BytesFromStr(state.TextW[ib:], state.TextW[ie:]) + 1
var clipboard_data = make([]byte, clipboard_data_len)
ImTextStrToUtf8(clipboard_data, clipboard_data_len, state.TextW[ib:], state.TextW[ie:])
SetClipboardText(string(clipboard_data))
}
if is_cut {
if !state.HasSelection() {
state.SelectAll()
}
state.CursorFollow = true
stb_textedit_cut(state, &state.Stb)
}
} else if is_paste {
if clipboard := GetClipboardText(); clipboard != "" {
// Filter pasted buffer
var clipboard_len = (int)(len(clipboard))
var clipboard_filtered = make([]ImWchar, clipboard_len+1)
var clipboard_filtered_len int = 0
for s := clipboard; len(s) > 0; {
var c rune
s = s[ImTextCharFromUtf8(&c, s):]
if c == 0 {
break
}
if !InputTextFilterCharacter(&c, flags, callback, callback_user_data, ImGuiInputSource_Clipboard) {
continue
}
clipboard_filtered[clipboard_filtered_len] = (ImWchar)(c)
clipboard_filtered_len++
}
clipboard_filtered[clipboard_filtered_len] = 0
if clipboard_filtered_len > 0 { // If everything was filtered, ignore the pasting operation
stb_textedit_paste(state, &state.Stb, clipboard_filtered, clipboard_filtered_len)
state.CursorFollow = true
}
}
}
// Update render selection flag after events have been handled, so selection highlight can be displayed during the same frame.
render_selection = render_selection || (state.HasSelection() && (RENDER_SELECTION_WHEN_INACTIVE || render_cursor))
}
// Process callbacks and apply result back to user's buffer.
if g.ActiveId == id {
IM_ASSERT(state != nil)
var apply_new_text []byte
var apply_new_text_length int
if cancel_edit {
// Restore initial value. Only return true if restoring to the initial value changes the current buffer contents.
if !is_readonly && !bytes.Equal(*buf, state.InitialTextA) {
// Push records into the undo stack so we can CTRL+Z the revert operation itself
apply_new_text = state.InitialTextA
apply_new_text_length = int(len(state.InitialTextA) - 1)
var w_text []ImWchar
if apply_new_text_length > 0 {
resizeRune(&w_text, ImTextCountCharsFromUtf8(string(apply_new_text[:apply_new_text_length]))+1)
remaining := string(apply_new_text[apply_new_text_length:])
ImTextStrFromUtf8(w_text, int(len(w_text)), string(apply_new_text), &remaining)
}
var l int
if apply_new_text_length > 0 {
l = int(len(w_text)) - 1
}
stb_textedit_replace(state, &state.Stb, w_text, l)
}
}
// When using 'ImGuiInputTextFlags_EnterReturnsTrue' as a special case we reapply the live buffer back to the input buffer before clearing ActiveId, even though strictly speaking it wasn't modified on this frame.
// If we didn't do that, code like InputInt() with ImGuiInputTextFlags_EnterReturnsTrue would fail.
// This also allows the user to use InputText() with ImGuiInputTextFlags_EnterReturnsTrue without maintaining any user-side storage (please note that if you use this property along ImGuiInputTextFlags_CallbackResize you can end up with your temporary string object unnecessarily allocating once a frame, either store your string data, either if you don't then don't use ImGuiInputTextFlags_CallbackResize).
var apply_edit_back_to_user_buffer = !cancel_edit || (enter_pressed && (flags&ImGuiInputTextFlags_EnterReturnsTrue) != 0)
if apply_edit_back_to_user_buffer {
// Apply new value immediately - copy modified buffer back
// Note that as soon as the input box is active, the in-widget value gets priority over any underlying modification of the input buffer
// FIXME: We actually always render 'buf' when calling DrawList.AddText, making the comment above incorrect.
// FIXME-OPT: CPU waste to do this every time the widget is active, should mark dirty state from the stb_textedit callbacks.
if !is_readonly {
state.TextAIsValid = true
resize(&state.TextA, int(len(state.TextW)*4+1))
ImTextStrToUtf8(state.TextA, int(len(state.TextA)), state.TextW, nil)
}
// User callback
if (flags & (ImGuiInputTextFlags_CallbackCompletion | ImGuiInputTextFlags_CallbackHistory | ImGuiInputTextFlags_CallbackEdit | ImGuiInputTextFlags_CallbackAlways)) != 0 {
IM_ASSERT(callback != nil)
// The reason we specify the usage semantic (Completion/History) is that Completion needs to disable keyboard TABBING at the moment.
var event_flag ImGuiInputTextFlags = 0
var event_key = ImGuiKey_COUNT
if (flags&ImGuiInputTextFlags_CallbackCompletion) != 0 && IsKeyPressedMap(ImGuiKey_Tab, true) {
event_flag = ImGuiInputTextFlags_CallbackCompletion
event_key = ImGuiKey_Tab
} else if (flags&ImGuiInputTextFlags_CallbackHistory) != 0 && IsKeyPressedMap(ImGuiKey_UpArrow, true) {
event_flag = ImGuiInputTextFlags_CallbackHistory
event_key = ImGuiKey_UpArrow
} else if (flags&ImGuiInputTextFlags_CallbackHistory) != 0 && IsKeyPressedMap(ImGuiKey_DownArrow, true) {
event_flag = ImGuiInputTextFlags_CallbackHistory
event_key = ImGuiKey_DownArrow
} else if (flags&ImGuiInputTextFlags_CallbackEdit != 0) && state.Edited {
event_flag = ImGuiInputTextFlags_CallbackEdit
} else if flags&ImGuiInputTextFlags_CallbackAlways != 0 {
event_flag = ImGuiInputTextFlags_CallbackAlways
}
if event_flag != 0 {
var callback_data ImGuiInputTextCallbackData
callback_data.EventFlag = event_flag
callback_data.Flags = flags
callback_data.UserData = callback_user_data
callback_data.EventKey = event_key
callback_data.Buf = state.TextA
callback_data.BufTextLen = state.CurLenA
callback_data.BufSize = state.BufCapacityA
callback_data.BufDirty = false
// We have to convert from wchar-positions to UTF-8-positions, which can be pretty slow (an incentive to ditch the ImWchar buffer, see https://github.com/nothings/stb/issues/188)
var text = state.TextW
var utf8_cursor_pos = ImTextCountUtf8BytesFromStr(text, text[state.Stb.cursor:])
callback_data.CursorPos = utf8_cursor_pos
var utf8_selection_start = ImTextCountUtf8BytesFromStr(text, text[state.Stb.select_start:])
callback_data.SelectionStart = utf8_selection_start
var utf8_selection_end = ImTextCountUtf8BytesFromStr(text, text[state.Stb.select_end:])
callback_data.SelectionEnd = utf8_selection_end
// Call user code
callback(&callback_data)
// Read back what user may have modified
IM_ASSERT(bytes.Equal(callback_data.Buf, state.TextA)) // Invalid to modify those fields
IM_ASSERT(callback_data.BufSize == state.BufCapacityA)
IM_ASSERT(callback_data.Flags == flags)
var buf_dirty = callback_data.BufDirty
if callback_data.CursorPos != utf8_cursor_pos || buf_dirty {
// TODO (port): check if ImTextCountCharsFromUtf8 works correctly, and the line below
// state->Stb.cursor = ImTextCountCharsFromUtf8(callback_data.Buf, callback_data.Buf + callback_data.CursorPos)
state.Stb.cursor = ImTextCountCharsFromUtf8(string(callback_data.Buf[:callback_data.CursorPos]))
state.CursorFollow = true
}
if callback_data.SelectionStart != utf8_selection_start || buf_dirty {
if callback_data.SelectionStart == callback_data.CursorPos {
state.Stb.select_start = state.Stb.cursor
} else {
state.Stb.select_start = ImTextCountCharsFromUtf8(string(callback_data.Buf[:callback_data.SelectionStart]))
}
}
if callback_data.SelectionEnd != utf8_selection_end || buf_dirty {
if callback_data.SelectionEnd == callback_data.SelectionStart {
state.Stb.select_end = state.Stb.select_start
} else {
state.Stb.select_end = ImTextCountCharsFromUtf8(string(callback_data.Buf[:callback_data.SelectionEnd]))
}
}
if buf_dirty {