Skip to content

Commit 43698e0

Browse files
committed
🚧 up to Aug 22 half
1 parent 01b0869 commit 43698e0

File tree

11 files changed

+197
-96
lines changed

11 files changed

+197
-96
lines changed

src/main/kotlin/imgui/Context.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -386,9 +386,9 @@ class Context(sharedFontAtlas: FontAtlas? = null) {
386386
g.drawDataBuilder.clear()
387387
g.overlayDrawList.clearFreeMemory()
388388
g.privateClipboard = ""
389-
g.inputTextState.text = charArrayOf()
389+
g.inputTextState.textW = charArrayOf()
390390
g.inputTextState.initialText = charArrayOf()
391-
g.inputTextState.tempTextBuffer = charArrayOf()
391+
g.inputTextState.tempBuffer = charArrayOf()
392392

393393
// if (g.logFile != null && g.logFile != stdout) { TODO
394394
// fclose(g.LogFile)

src/main/kotlin/imgui/TextEditState.kt

Lines changed: 21 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,11 @@ class TextEditState {
1313
var id: ID = 0
1414
/** edit buffer, we need to persist but can't guarantee the persistence of the user-provided buffer.
1515
* So we copy into own buffer. */
16-
var text = CharArray(0)
16+
var textW = CharArray(0)
1717
/** backup of end-user buffer at the time of focus (in UTF-8, unaltered) */
1818
var initialText = CharArray(0)
19-
20-
var tempTextBuffer = CharArray(0)
19+
/** temporary buffer for callback and other other operations. size=capacity. */
20+
var tempBuffer = CharArray(0)
2121
/** we need to maintain our buffer length in both UTF-8 and wchar format. */
2222
var curLenA = 0
2323
/** we need to maintain our buffer length in both UTF-8 and wchar format. */
@@ -37,7 +37,7 @@ class TextEditState {
3737

3838
// Temporarily set when active
3939
var userFlags: InputTextFlags = 0
40-
var userCallback: TextEditCallback? = null
40+
var userCallback: InputTextCallback? = null
4141
var userCallbackData: Any? = null
4242

4343
/** After a user-input the cursor stays on for a while without blinking */
@@ -63,6 +63,7 @@ class TextEditState {
6363
state.hasPreferredX = false
6464
}
6565

66+
/** Cannot be inline because we call in code in stb_textedit.h implementation */
6667
fun onKeyPressed(key: Int) {
6768
key(key)
6869
cursorFollow = true
@@ -79,9 +80,9 @@ class TextEditState {
7980

8081
val stringLen get() = curLenW
8182

82-
fun getChar(idx: Int) = text[idx]
83+
fun getChar(idx: Int) = textW[idx]
8384
fun getWidth(lineStartIdx: Int, charIdx: Int): Float {
84-
val c = text[lineStartIdx + charIdx]
85+
val c = textW[lineStartIdx + charIdx]
8586
return if (c == '\n') -1f else g.font.getCharAdvance(c) * (g.fontSize / g.font.fontSize)
8687
}
8788

@@ -92,7 +93,7 @@ class TextEditState {
9293

9394
fun layout(r: Row, lineStartIdx: Int) {
9495

95-
val size = inputTextCalcTextSizeW(text, lineStartIdx, curLenW, ::textRemaining, null, true)
96+
val size = inputTextCalcTextSizeW(textW, lineStartIdx, curLenW, ::textRemaining, null, true)
9697
with(r) {
9798
r.x0 = 0f
9899
r.x1 = size.x
@@ -107,7 +108,7 @@ class TextEditState {
107108
get() = isBlankW || this == ',' || this == ';' || this == '(' || this == ')' ||
108109
this == '{' || this == '}' || this == '[' || this == ']' || this == '|'
109110

110-
fun isWordBoundaryFromRight(idx: Int) = if (idx > 0) text[idx - 1].isSeparator && !text[idx].isSeparator else true
111+
fun isWordBoundaryFromRight(idx: Int) = if (idx > 0) textW[idx - 1].isSeparator && !textW[idx].isSeparator else true
111112

112113
fun moveWordLeft(idx_: Int): Int {
113114
var idx = idx_ - 1
@@ -131,9 +132,9 @@ class TextEditState {
131132
curLenW -= n
132133

133134
// Offset remaining text
134-
for (c in pos + n until text.size)
135-
text[dst++] = text[c]
136-
text[dst] = NUL
135+
for (c in pos + n until textW.size)
136+
textW[dst++] = textW[c]
137+
textW[dst] = NUL
137138
}
138139

139140
fun insertChars(pos: Int, newText: CharArray, ptr: Int, newTextLen: Int): Boolean {
@@ -147,22 +148,22 @@ class TextEditState {
147148
if (!isResizable && newTextLenUtf8 + curLenA > bufCapacityA) return false
148149

149150
// Grow internal buffer if needed
150-
if (newTextLen + textLen > text.size) {
151+
if (newTextLen + textLen > textW.size) {
151152
if (!isResizable)
152153
return false
153-
assert(textLen < text.size)
154+
assert(textLen < textW.size)
154155
val tmp = CharArray(textLen + glm.clamp(newTextLen * 4, 32, max(256, newTextLen)))
155-
System.arraycopy(text, 0, tmp, 0, text.size)
156-
text = tmp
156+
System.arraycopy(textW, 0, tmp, 0, textW.size)
157+
textW = tmp
157158
}
158159

159160
if (pos != textLen)
160-
for (i in 0 until textLen - pos) text[textLen - 1 + newTextLen - i] = text[textLen - 1 - i]
161-
for (i in 0 until newTextLen) text[pos + i] = newText[ptr + i]
161+
for (i in 0 until textLen - pos) textW[textLen - 1 + newTextLen - i] = textW[textLen - 1 - i]
162+
for (i in 0 until newTextLen) textW[pos + i] = newText[ptr + i]
162163

163164
curLenW += newTextLen
164165
curLenA += newTextLenUtf8
165-
text[curLenW] = NUL
166+
textW[curLenW] = NUL
166167

167168
return true
168169
}
@@ -478,7 +479,7 @@ class TextEditState {
478479
}
479480

480481
// if the last character is a newline, return that. otherwise return 'after' the last character
481-
return if (text[i + r.numChars - 1] == '\n') i + r.numChars - 1 else i + r.numChars
482+
return if (textW[i + r.numChars - 1] == '\n') i + r.numChars - 1 else i + r.numChars
482483
}
483484

484485
/** API click: on mouse down, move the cursor to the clicked location, and reset the selection */
@@ -797,7 +798,7 @@ class TextEditState {
797798
clamp()
798799
deleteSelection()
799800
// try to insert the characters
800-
if (insertChars(state.cursor, text, 0, len)) {
801+
if (insertChars(state.cursor, textW, 0, len)) {
801802
makeundoInsert(state.cursor, len)
802803
state.cursor += len
803804
state.hasPreferredX = true

src/main/kotlin/imgui/enums.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ enum class InputTextFlag(val i: Int) {
127127
/** Allow 0123456789.+-* /eE (Scientific notation input) */
128128
CharsScientific(1 shl 17),
129129
/** Allow buffer capacity resize + notify when the string wants to be resized
130-
* (for string types which hold a cache of their Size) */
130+
* (for string types which hold a cache of their Size) (see misc/stl/imgui_stl.h for an example of using this) */
131131
CallbackResize(1 shl 18),
132132

133133
// [Internal]

src/main/kotlin/imgui/font.kt

Lines changed: 28 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1195,7 +1195,7 @@ class Font {
11951195
}
11961196

11971197
// const ImVec4& clipRect, const char* text, const char* textEnd, float wrapWidth = 0.0f, bool cpuFineClip = false) const;
1198-
fun renderText(drawList: DrawList, size: Float, pos: Vec2, col: Int, clipRect: Vec4, text: CharArray, textEnd: Int = text.size,
1198+
fun renderText(drawList: DrawList, size: Float, pos: Vec2, col: Int, clipRect: Vec4, text: CharArray, textEnd_: Int = text.size, // TODO return it also?
11991199
wrapWidth: Float = 0f, cpuFineClip: Boolean = false) {
12001200

12011201
// Align to be pixel perfect
@@ -1209,11 +1209,32 @@ class Font {
12091209
val wordWrapEnabled = wrapWidth > 0f
12101210
var wordWrapEol = 0
12111211

1212-
// Skip non-visible lines
1212+
// Fast-forward to first visible line
12131213
var s = 0
1214-
if (!wordWrapEnabled && y + lineHeight < clipRect.y)
1215-
while (s < textEnd && text[s] != '\n') // Fast-forward to next line
1216-
s++
1214+
if (y + lineHeight < clipRect.y && !wordWrapEnabled)
1215+
while (y + lineHeight < clipRect.y) {
1216+
while (s < textEnd_)
1217+
if (text[s++] == '\n')
1218+
break
1219+
y += lineHeight
1220+
}
1221+
1222+
/* For large text, scan for the last visible line in order to avoid over-reserving in the call to PrimReserve()
1223+
Note that very large horizontal line will still be affected by the issue (e.g. a one megabyte string buffer without a newline will likely crash atm) */
1224+
var textEnd = textEnd_
1225+
if (textEnd - s > 10000 && !wordWrapEnabled) {
1226+
var sEnd = s
1227+
var yEnd = y
1228+
while (yEnd < clipRect.w) {
1229+
while (sEnd < textEnd)
1230+
if (text[sEnd++] == '\n')
1231+
break
1232+
yEnd += lineHeight
1233+
}
1234+
textEnd = sEnd
1235+
}
1236+
1237+
12171238

12181239
// Reserve vertices for remaining worse case (over-reserving is useful and easily amortized)
12191240
val vtxCountMax = (textEnd - s) * 4
@@ -1274,12 +1295,8 @@ class Font {
12741295
if (c == '\n') {
12751296
x = pos.x
12761297
y += lineHeight
1277-
1278-
if (y > clipRect.w) break
1279-
1280-
if (!wordWrapEnabled && y + lineHeight < clipRect.y)
1281-
while (s < textEnd && text[s] != '\n') // Fast-forward to next line
1282-
s++
1298+
if (y > clipRect.w)
1299+
break // break out of main loop
12831300
continue
12841301
}
12851302
if (c == '\r') continue

src/main/kotlin/imgui/helpers.kt

Lines changed: 73 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@ import imgui.ImGui.inputText
1414
import imgui.ImGui.popItemWidth
1515
import imgui.ImGui.pushItemWidth
1616
import imgui.ImGui.style
17+
import imgui.internal.strlen
1718
import java.nio.ByteBuffer
19+
import kotlin.math.max
1820

1921
/** Helper: Execute a block of code at maximum once a frame. Convenient if you want to quickly create an UI within
2022
* deep-nested code that runs multiple times every frame.
@@ -139,14 +141,18 @@ class Storage {
139141
fun setAllInt(value: Int) = data.replaceAll { _, _ -> value }
140142
}
141143

142-
/** Shared state of InputText(), passed to callback when a ImGuiInputTextFlags_Callback* flag is used and the corresponding callback is triggered.
144+
/** Shared state of InputText(), passed as an argument to your callback when a ImGuiInputTextFlags_Callback* flag is used.
143145
* The callback function should return 0 by default.
144146
* Special processing:
145147
* - InputTextFlag.CallbackCharFilter: return 1 if the character is not allowed. You may also set 'EventChar=0'
146148
* as any character replacement are allowed.
147-
* - InputTextFlag.CallbackResize: BufTextLen is set to the new desired string length so you can allocate or update known size.
148-
* No need to initialize new characters or zero-terminator as InputText will do it. */
149-
class TextEditCallbackData {
149+
* - InputTextFlag.CallbackResize: notified by InputText() when the string is resized.
150+
* BufTextLen is set to the new desired string length so you can allocate or update known size.
151+
* No need to initialize new characters or zero-terminator as InputText will do it.
152+
*
153+
* Helper functions for text manipulation.
154+
* Use those function to benefit from the CallbackResize behaviors. Calling those function reset the selection. */
155+
class InputTextCallbackData {
150156

151157
/** One ImGuiInputTextFlags_Callback* // Read-only */
152158
var eventFlag: InputTextFlags = 0
@@ -156,19 +162,20 @@ class TextEditCallbackData {
156162
var userData: Any? = null
157163

158164
/* Arguments for the different callback events
159-
* (If you modify the 'buf' contents make sure you update 'BufTextLen' and set 'BufDirty' to true!) */
165+
* - To modify the text buffer in a callback, prefer using the InsertChars() / DeleteChars() function. InsertChars() will take care of calling the resize callback if necessary.
166+
* - If you know your edits are not going to resize the underlying buffer allocation, you may modify the contents of 'Buf[]' directly. You need to update 'BufTextLen' accordingly (0 <= BufTextLen < BufSize) and set 'BufDirty'' to true so InputText can update its internal state. */
160167

161168
/** Character input Read-write [CharFilter] Replace character or set to zero. return 1 is equivalent to setting EventChar=0; */
162169
var eventChar = NUL
163170
/** Key pressed (Up/Down/TAB) Read-only [Completion,History] */
164171
var eventKey = Key.Tab
165-
/** Current text buffer Read-write [Resize] Can replace pointer / [Completion,History,Always] Only write to pointed data, don't replace the actual pointer! */
172+
/** Text buffer Read-write [Resize] Can replace pointer / [Completion,History,Always] Only write to pointed data, don't replace the actual pointer! */
166173
var buf = CharArray(0)
167174
/** JVM custom, current buf pointer */
168175
var bufPtr = 0
169-
/** Current text length in bytes Read-write [Resize,Completion,History,Always] */
176+
/** Text length in bytes Read-write [Resize,Completion,History,Always] */
170177
var bufTextLen = 0
171-
/** Capacity + 1 (max text length + 1) Read-only [Resize,Completion,History,Always] */
178+
/** Buffer capacity in bytes Read-only [Resize,Completion,History,Always] */
172179
var bufSize = 0
173180
/** Set if you modify Buf/BufTextLen!! Write [Completion,History,Always] */
174181
var bufDirty = false
@@ -180,10 +187,65 @@ class TextEditCallbackData {
180187
var selectionEnd = 0
181188

182189

183-
// IMGUI_API void DeleteChars(int pos, int bytes_count);
184-
// IMGUI_API void InsertChars(int pos, const char* text, const char* text_end = NULL);
190+
/** Public API to manipulate UTF-8 text
191+
* We expose UTF-8 to the user (unlike the STB_TEXTEDIT_* functions which are manipulating wchar)
192+
* FIXME: The existence of this rarely exercised code path is a bit of a nuisance. */
193+
fun deleteChars(pos: Int, bytesCount: Int) {
194+
assert(pos + bytesCount <= bufTextLen)
195+
var dst = pos
196+
var src = pos + bytesCount
197+
var c = buf[src++]
198+
while (c != NUL) {
199+
buf[dst++] = c
200+
c = buf.getOrElse(src++) { NUL }
201+
}
202+
if (cursorPos + bytesCount >= pos)
203+
cursorPos -= bytesCount
204+
else if (cursorPos >= pos)
205+
cursorPos = pos
206+
selectionEnd = cursorPos
207+
selectionStart = cursorPos
208+
bufDirty = true
209+
bufTextLen -= bytesCount
210+
}
211+
212+
fun insertChars(pos: Int, newText: CharArray, ptr: Int, newTextEnd: Int? = null) {
213+
214+
val isResizable = flags has InputTextFlag.CallbackResize
215+
val newTextLen = newTextEnd ?: newText.strlen
216+
if (newTextLen + bufTextLen >= bufSize) {
217+
218+
if (!isResizable) return
219+
220+
// Contrary to STB_TEXTEDIT_INSERTCHARS() this is working in the UTF8 buffer, hence the midly similar code (until we remove the U16 buffer alltogether!)
221+
val editState = g.inputTextState
222+
assert(editState.id != 0 && g.activeId == editState.id)
223+
assert(buf === editState.tempBuffer)
224+
val newBufSize = bufTextLen + glm.clamp(newTextLen * 4, 32, max(256, newTextLen))
225+
val t = CharArray(newBufSize)
226+
System.arraycopy(editState.tempBuffer, 0, t, 0, editState.tempBuffer.size)
227+
editState.tempBuffer = t
228+
buf = editState.tempBuffer
229+
editState.bufCapacityA = newBufSize
230+
bufSize = newBufSize
231+
}
232+
233+
if (bufTextLen != pos)
234+
for (i in 0 until bufTextLen - pos)
235+
buf[pos + newTextLen + i] = buf[pos + i]
236+
for (i in 0 until newTextLen)
237+
buf[pos + i] = newText[i]
238+
239+
if (cursorPos >= pos)
240+
cursorPos += newTextLen
241+
selectionEnd = cursorPos
242+
selectionStart = cursorPos
243+
bufDirty = true
244+
bufTextLen += newTextLen
245+
}
246+
185247
val hasSelection: Boolean
186-
get() = selectionStart != selectionEnd
248+
get() = selectionStart != selectionEnd
187249
}
188250

189251
class SizeCallbackData(

src/main/kotlin/imgui/imgui.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ typealias TreeNodeFlags = Int
154154
/** flags: for Begin*() // enum WindowFlag */
155155
typealias WindowFlags = Int
156156

157-
typealias TextEditCallback = (TextEditCallbackData) -> Int
157+
typealias InputTextCallback = (InputTextCallbackData) -> Int
158158
typealias SizeCallback = (SizeCallbackData) -> Unit
159159

160160
// dummy main

src/main/kotlin/imgui/imgui/demo/showExampleApp/Console.kt

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -162,14 +162,15 @@ object Console {
162162
// bool reclaim_focus = false;
163163
// if (ImGui::InputText("Input", InputBuf, IM_ARRAYSIZE(InputBuf), ImGuiInputTextFlags_EnterReturnsTrue|ImGuiInputTextFlags_CallbackCompletion|ImGuiInputTextFlags_CallbackHistory, &TextEditCallbackStub, (void*)this))
164164
// {
165-
// Strtrim(InputBuf);
166-
// if (InputBuf[0])
167-
// ExecCommand(InputBuf);
168-
// strcpy(InputBuf, "");
165+
// char* s = InputBuf;
166+
// Strtrim(s);
167+
// if (s[0])
168+
// ExecCommand(s);
169+
// strcpy(s, "");
169170
// reclaim_focus = true;
170171
// }
171172
//
172-
// // Demonstrate keeping focus on the input box
173+
// // Auto-focus on window apparition
173174
// ImGui::SetItemDefaultFocus();
174175
// if (reclaim_focus)
175176
// ImGui::SetKeyboardFocusHere(-1); // Auto focus previous widget
@@ -218,10 +219,10 @@ object Console {
218219
// static int TextEditCallbackStub(ImGuiTextEditCallbackData* data) // In C++11 you are better off using lambdas for this sort of forwarding callbacks
219220
// {
220221
// ExampleAppConsole* console = (ExampleAppConsole*)data->UserData;
221-
// return console->TextEditCallback(data);
222+
// return console->InputTextCallback(data);
222223
// }
223224
//
224-
// int TextEditCallback(ImGuiTextEditCallbackData* data)
225+
// int InputTextCallback(ImGuiTextEditCallbackData* data)
225226
// {
226227
// //AddLog("cursor: %d, selection: %d-%d", data->CursorPos, data->SelectionStart, data->SelectionEnd);
227228
// switch (data->EventFlag)
@@ -312,9 +313,9 @@ object Console {
312313
// // A better implementation would preserve the data on the current input line along with cursor position.
313314
// if (prev_history_pos != HistoryPos)
314315
// {
315-
// int sz = (int)snprintf(data->Buf, (size_t)data->BufSize, "%s", (HistoryPos >= 0) ? History[HistoryPos] : "");
316-
// data->CursorPos = data->SelectionStart = data->SelectionEnd = data->BufTextLen = sz;
317-
// data->BufDirty = true;
316+
// const char* history_str = (HistoryPos >= 0) ? History[HistoryPos] : "";
317+
// data->DeleteChars(0, data->BufTextLen);
318+
// data->InsertChars(0, history_str);
318319
// }
319320
// }
320321
// }

0 commit comments

Comments
 (0)