Skip to content

Commit e9731e3

Browse files
committed
- reverted applyFromText to ByteArray to propagate upstream the modifications
- fixed bug in `setActiveID`, `markItemEdited`, `Char::isSeparator`, `itemAdd` and `memchr` - layout
1 parent c455a62 commit e9731e3

File tree

9 files changed

+104
-91
lines changed

9 files changed

+104
-91
lines changed

core/src/main/kotlin/imgui/api/widgetsInputWithKeyboard.kt

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -147,15 +147,15 @@ interface widgetsInputWithKeyboard {
147147
var valueChanged = false
148148
if (step == null) {
149149
if (inputText(label, buf, flags))
150-
valueChanged = pData.applyFromText(buf.cStr, format)
150+
valueChanged = pData.applyFromText(buf, format)
151151
} else {
152152
val buttonSize = frameHeight
153153

154154
beginGroup() // The only purpose of the group here is to allow the caller to query item data e.g. IsItemActive()
155155
pushID(label)
156156
setNextItemWidth(1f max (calcItemWidth() - (buttonSize + style.itemInnerSpacing.x) * 2))
157157
if (inputText("", buf, flags)) // PushId(label) + "" gives us the expected ID from outside point of view
158-
valueChanged = pData.applyFromText(buf.cStr, format)
158+
valueChanged = pData.applyFromText(buf, format)
159159
IMGUI_TEST_ENGINE_ITEM_INFO(g.lastItemData.id, label, g.lastItemData.statusFlags / ItemStatusFlag.Inputable)
160160

161161
// Step buttons
@@ -188,7 +188,8 @@ interface widgetsInputWithKeyboard {
188188
endGroup()
189189
}
190190

191-
if (valueChanged) markItemEdited(g.lastItemData.id)
191+
if (valueChanged)
192+
markItemEdited(g.lastItemData.id)
192193

193194
return valueChanged
194195
}

core/src/main/kotlin/imgui/genericMath.kt

Lines changed: 49 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package imgui
22

33
import glm_.*
44
import imgui.internal.addClampOverflow
5+
import imgui.internal.charIsBlankA
56
import imgui.internal.parseFormatSanitizeForScanning
67
import imgui.internal.subClampOverflow
78
import uno.kotlin.NUL
@@ -39,29 +40,42 @@ sealed interface NumberOps<N> where N : Number, N : Comparable<N> {
3940
val dataType: DataType
4041
val Number.coerced: N
4142

42-
/** return true if modified */
43-
fun KMutableProperty0<N>.applyFromText(buf: String, format: String): Boolean {
44-
val initial = get()
43+
/** User can input math operators (e.g. +100) to edit a numerical values.
44+
* NB: This is _not_ a full expression evaluator. We should probably add one and replace this dumb mess..
45+
*
46+
* ~DataTypeApplyFromText
47+
* @return true if modified */
48+
fun KMutableProperty0<N>.applyFromText(buf: ByteArray, format: String): Boolean {
49+
50+
var ptr = 0
51+
while (charIsBlankA(buf[ptr].i))
52+
ptr++
53+
if (buf[ptr] == 0.b)
54+
return false
55+
56+
// Copy the value in an opaque buffer so we can compare at the end of the function if it changed at all.
57+
val backupData = get()
58+
4559
val v = parse(buf, format)
4660
return v?.let {
4761
this.set(v)
48-
get() != initial
62+
get() != backupData
4963
} == true
5064
}
5165

52-
fun parse(buf: String, format: String, radix: Int): N
66+
fun parse(buf: ByteArray, format: String, radix: Int): N
5367
val String.parsed: N
5468

5569
fun N.format(format: String): String {
5670
// [JVM] we have to filter `.`, since `%.03d` causes `IllegalFormatPrecisionException`, but `%03d` doesn't
5771
return format.replace(".", "").format(when (this) {
58-
// we need to intervene since java printf cant handle %u
59-
is Ubyte -> i
60-
is Ushort -> i
61-
is Uint -> L
62-
is Ulong -> toBigInt()
63-
else -> this
64-
})
72+
// we need to intervene since java printf cant handle %u
73+
is Ubyte -> i
74+
is Ushort -> i
75+
is Uint -> L
76+
is Ulong -> toBigInt()
77+
else -> this
78+
})
6579
}
6680

6781
operator fun N.plus(other: N): N
@@ -73,9 +87,13 @@ sealed interface NumberOps<N> where N : Number, N : Comparable<N> {
7387
private fun <N, FP> NumberFpOps<N, FP>.nTimesN(n: N, other: N): N where N : Number, N : Comparable<N>, FP : Number, FP : Comparable<FP> = (n.fp * other.fp).n
7488
private fun <N, FP> NumberFpOps<N, FP>.nDivN(n: N, other: N): N where N : Number, N : Comparable<N>, FP : Number, FP : Comparable<FP> = (n.fp / other.fp).n
7589

76-
fun <N> NumberOps<N>.parse(buf: String, format: String): N? where N : Number, N : Comparable<N> {
90+
fun <N> NumberOps<N>.parse(buf: ByteArray, format: String): N? where N : Number, N : Comparable<N> {
7791
// ImCharIsBlankA
78-
@Suppress("NAME_SHADOWING") val buf = buf.replace(Regex("\\s+"), "").replace("\t", "").removeSuffix("\u0000")
92+
buf.cStr
93+
.replace(Regex("\\s+"), "")
94+
.replace("\t", "")
95+
.removeSuffix("\u0000")
96+
.toByteArray(buf)
7997

8098
if (buf.isEmpty()) return null
8199

@@ -92,6 +110,7 @@ fun NumberOps<*>.defaultInputCharsFilter(format: String): InputTextFlag.Single {
92110
else -> InputTextFlag.CharsDecimal
93111
}
94112
}
113+
95114
val NumberOps<*>.defaultFormat: String
96115
get() = when (dataType) {
97116
DataType.Float, DataType.Double -> "%.3f"
@@ -138,9 +157,11 @@ sealed interface FloatingPointOps<FP> where FP : Number, FP : Comparable<FP> {
138157
@Suppress("INAPPLICABLE_JVM_NAME")
139158
@JvmName("fpMinus")
140159
operator fun FP.minus(other: FP): FP
160+
141161
@Suppress("INAPPLICABLE_JVM_NAME")
142162
@JvmName("fpTimes")
143163
operator fun FP.times(other: FP): FP
164+
144165
@Suppress("INAPPLICABLE_JVM_NAME")
145166
@JvmName("fpDiv")
146167
operator fun FP.div(other: FP): FP
@@ -169,7 +190,7 @@ object ByteOps : NumberFpOps<Byte, Float>, FloatingPointOps<Float> by FloatOps {
169190
override val one: Byte = 1
170191
override val Number.coerced get() = b
171192
override val dataType: DataType = DataType.Byte
172-
override fun parse(buf: String, format: String, radix: Int): Byte = format.format(buf.parseInt(radix)).toByte(radix)
193+
override fun parse(buf: ByteArray, format: String, radix: Int): Byte = format.format(buf.cStr.parseInt(radix)).toByte(radix)
173194
override val String.parsed: Byte get() = b
174195
override fun Byte.plus(other: Byte): Byte = addClampOverflow(i, other.i, min.i, max.i).b
175196
override fun Byte.minus(other: Byte): Byte = subClampOverflow(i, other.i, min.i, max.i).b
@@ -183,7 +204,7 @@ object UbyteOps : NumberFpOps<Ubyte, Float>, FloatingPointOps<Float> by FloatOps
183204
override val Ubyte.isNegative: Boolean get() = false
184205
override val Number.coerced get() = ub
185206
override val dataType: DataType = DataType.Ubyte
186-
override fun parse(buf: String, format: String, radix: Int): Ubyte = format.format(buf.parseInt(radix)).toInt(radix).ub
207+
override fun parse(buf: ByteArray, format: String, radix: Int): Ubyte = format.format(buf.cStr.parseInt(radix)).toInt(radix).ub
187208
override val String.parsed: Ubyte get() = ub
188209
override fun Ubyte.plus(other: Ubyte): Ubyte = addClampOverflow(i, other.i, min.i, max.i).ub
189210
override fun Ubyte.minus(other: Ubyte): Ubyte = subClampOverflow(i, other.i, min.i, max.i).ub
@@ -196,7 +217,7 @@ object ShortOps : NumberFpOps<Short, Float>, FloatingPointOps<Float> by FloatOps
196217
override val one: Short = 1
197218
override val Number.coerced get() = s
198219
override val dataType: DataType = DataType.Short
199-
override fun parse(buf: String, format: String, radix: Int): Short = format.format(buf.parseInt(radix)).toShort(radix)
220+
override fun parse(buf: ByteArray, format: String, radix: Int): Short = format.format(buf.cStr.parseInt(radix)).toShort(radix)
200221
override val String.parsed: Short get() = s
201222
override fun Short.plus(other: Short): Short = addClampOverflow(i, other.i, min.i, max.i).s
202223
override fun Short.minus(other: Short): Short = subClampOverflow(i, other.i, min.i, max.i).s
@@ -210,7 +231,7 @@ object UshortOps : NumberFpOps<Ushort, Float>, FloatingPointOps<Float> by FloatO
210231
override val Ushort.isNegative: Boolean get() = false
211232
override val Number.coerced get() = us
212233
override val dataType: DataType = DataType.Ushort
213-
override fun parse(buf: String, format: String, radix: Int): Ushort = format.format(buf.parseInt(radix)).toInt(radix).us
234+
override fun parse(buf: ByteArray, format: String, radix: Int): Ushort = format.format(buf.cStr.parseInt(radix)).toInt(radix).us
214235
override val String.parsed: Ushort get() = us
215236
override fun Ushort.plus(other: Ushort): Ushort = addClampOverflow(i, other.i, min.i, max.i).us
216237
override fun Ushort.minus(other: Ushort): Ushort = subClampOverflow(i, other.i, min.i, max.i).us
@@ -223,7 +244,7 @@ object IntOps : NumberFpOps<Int, Float>, FloatingPointOps<Float> by FloatOps {
223244
override val one = 1
224245
override val Number.coerced get() = i
225246
override val dataType: DataType = DataType.Int
226-
override fun parse(buf: String, format: String, radix: Int): Int = format.format(buf.parseInt(radix)).toInt(radix)
247+
override fun parse(buf: ByteArray, format: String, radix: Int): Int = format.format(buf.cStr.parseInt(radix)).toInt(radix)
227248
override val String.parsed: Int get() = i
228249
override fun Int.plus(other: Int): Int = addClampOverflow(this, other, min, max)
229250
override fun Int.minus(other: Int): Int = subClampOverflow(this, other, min, max)
@@ -237,7 +258,7 @@ object UintOps : NumberFpOps<Uint, Float>, FloatingPointOps<Float> by FloatOps {
237258
override val Uint.isNegative: Boolean get() = false
238259
override val Number.coerced get() = ui
239260
override val dataType: DataType = DataType.Uint
240-
override fun parse(buf: String, format: String, radix: Int): Uint = format.format(buf.parseLong(radix)).toLong(radix).ui
261+
override fun parse(buf: ByteArray, format: String, radix: Int): Uint = format.format(buf.cStr.parseLong(radix)).toLong(radix).ui
241262
override val String.parsed: Uint get() = ui
242263
override fun Uint.plus(other: Uint): Uint = addClampOverflow(L, other.L, min.L, max.L).ui
243264
override fun Uint.minus(other: Uint): Uint = subClampOverflow(L, other.L, min.L, max.L).ui
@@ -250,7 +271,7 @@ object LongOps : NumberFpOps<Long, Double>, FloatingPointOps<Double> by DoubleOp
250271
override val one = 1L
251272
override val Number.coerced get() = L
252273
override val dataType: DataType = DataType.Long
253-
override fun parse(buf: String, format: String, radix: Int): Long = format.format(buf.parseUnsignedLong(radix)).toLong(radix)
274+
override fun parse(buf: ByteArray, format: String, radix: Int): Long = format.format(buf.cStr.parseUnsignedLong(radix)).toLong(radix)
254275
override val String.parsed: Long get() = L
255276
override fun Long.plus(other: Long): Long = addClampOverflow(this, other, min, max)
256277
override fun Long.minus(other: Long): Long = subClampOverflow(this, other, min, max)
@@ -264,7 +285,7 @@ object UlongOps : NumberFpOps<Ulong, Double>, FloatingPointOps<Double> by Double
264285
override val Ulong.isNegative: Boolean get() = false
265286
override val Number.coerced get() = ul
266287
override val dataType: DataType = DataType.Ulong
267-
override fun parse(buf: String, format: String, radix: Int): Ulong = format.format(buf.parseUnsignedLong(radix)).toBigInteger(radix).ul
288+
override fun parse(buf: ByteArray, format: String, radix: Int): Ulong = format.format(buf.cStr.parseUnsignedLong(radix)).toBigInteger(radix).ul
268289
override val String.parsed: Ulong get() = ul
269290
override fun Ulong.plus(other: Ulong): Ulong = addClampOverflow(toBigInt(), other.toBigInt(), min.toBigInt(), max.toBigInt()).ul
270291
override fun Ulong.minus(other: Ulong): Ulong = subClampOverflow(toBigInt(), other.toBigInt(), min.toBigInt(), max.toBigInt()).ul
@@ -280,7 +301,7 @@ object FloatOps : NumberFpOps<Float, Float> {
280301
override val Number.coercedFp: Float get() = f
281302
override val dataType: DataType = DataType.Float
282303
override fun Float.format(format: String): String = format.format(this)
283-
override fun parse(buf: String, format: String, radix: Int): Float = "%f".format(buf.parseFloat).f
304+
override fun parse(buf: ByteArray, format: String, radix: Int): Float = "%f".format(buf.cStr.parseFloat).f
284305
override val String.parsed: Float get() = f
285306
override fun Float.unaryMinus(): Float = -this
286307

@@ -291,9 +312,11 @@ object FloatOps : NumberFpOps<Float, Float> {
291312
@Suppress("INAPPLICABLE_JVM_NAME")
292313
@JvmName("fpMinus")
293314
override fun Float.minus(other: Float): Float = this - other
315+
294316
@Suppress("INAPPLICABLE_JVM_NAME")
295317
@JvmName("fpTimes")
296318
override fun Float.times(other: Float): Float = this * other
319+
297320
@Suppress("INAPPLICABLE_JVM_NAME")
298321
@JvmName("fpDiv")
299322
override fun Float.div(other: Float): Float = this / other
@@ -310,7 +333,7 @@ object DoubleOps : NumberFpOps<Double, Double> {
310333
override val Number.coercedFp: Double get() = d
311334
override val dataType: DataType = DataType.Double
312335
override fun Double.format(format: String): String = format.format(this)
313-
override fun parse(buf: String, format: String, radix: Int): Double = "%f".format(buf.parseDouble).d
336+
override fun parse(buf: ByteArray, format: String, radix: Int): Double = "%f".format(buf.cStr.parseDouble).d
314337
override val String.parsed: Double get() = d
315338
override fun Double.unaryMinus(): Double = -this
316339

@@ -321,9 +344,11 @@ object DoubleOps : NumberFpOps<Double, Double> {
321344
@Suppress("INAPPLICABLE_JVM_NAME")
322345
@JvmName("fpMinus")
323346
override fun Double.minus(other: Double): Double = this - other
347+
324348
@Suppress("INAPPLICABLE_JVM_NAME")
325349
@JvmName("fpTimes")
326350
override fun Double.times(other: Double): Double = this * other
351+
327352
@Suppress("INAPPLICABLE_JVM_NAME")
328353
@JvmName("fpDiv")
329354
override fun Double.div(other: Double): Double = this / other

core/src/main/kotlin/imgui/helpers.kt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,9 +180,12 @@ var imeInProgress = false
180180

181181
fun ByteArray.memchr(startIdx: Int, c: Char, num: Int = size - startIdx): Int {
182182
val char = c.b
183-
for (i in startIdx until startIdx + num)
183+
for (i in startIdx until startIdx + num) {
184+
if (this[i] == 0.b)
185+
return -1
184186
if (this[i] == char)
185187
return i
188+
}
186189
return -1
187190
}
188191

core/src/main/kotlin/imgui/internal/api/basicAccessors.kt

Lines changed: 36 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,12 @@ package imgui.internal.api
33
import imgui.DataType
44
import imgui.ID
55
import imgui.ImGui.debugHookIdInfo
6+
import imgui.ImGui.inputTextDeactivateHook
67
import imgui.ImGui.rectAbsToRel
78
import imgui.ImGui.setNavWindow
89
import imgui.MouseButton
910
import imgui.api.g
11+
import imgui.api.gImGui
1012
import imgui.div
1113
import imgui.internal.classes.Window
1214
import imgui.internal.hashData
@@ -33,19 +35,29 @@ internal interface basicAccessors {
3335

3436
fun setActiveID(id: ID, window: Window?) {
3537

36-
// While most behaved code would make an effort to not steal active id during window move/drag operations,
37-
// we at least need to be resilient to it. Cancelling the move is rather aggressive and users of 'master' branch
38-
// may prefer the weird ill-defined half working situation ('docking' did assert), so may need to rework that.
39-
if (g.movingWindow != null && g.activeId == g.movingWindow!!.moveId) {
40-
IMGUI_DEBUG_LOG_ACTIVEID("SetActiveID() cancel MovingWindow")
41-
g.movingWindow = null
38+
val g = gImGui
39+
40+
// Clear previous active id
41+
if (g.activeId != 0) {
42+
// While most behaved code would make an effort to not steal active id during window move/drag operations,
43+
// we at least need to be resilient to it. Canceling the move is rather aggressive and users of 'master' branch
44+
// may prefer the weird ill-defined half working situation ('docking' did assert), so may need to rework that.
45+
if (g.movingWindow != null && g.activeId == g.movingWindow!!.moveId) {
46+
IMGUI_DEBUG_LOG_ACTIVEID("SetActiveID() cancel MovingWindow\n")
47+
g.movingWindow = null
48+
}
49+
50+
// This could be written in a more general way (e.g associate a hook to ActiveId),
51+
// but since this is currently quite an exception we'll leave it as is.
52+
// One common scenario leading to this is: pressing Key ->NavMoveRequestApplyResult() -> ClearActiveId()
53+
if (g.inputTextState.id == g.activeId)
54+
inputTextDeactivateHook(g.activeId)
4255
}
4356

4457
// Set active id
45-
g.activeIdIsJustActivated = g.activeId != id
58+
g.activeIdIsJustActivated = (g.activeId != id)
4659
if (g.activeIdIsJustActivated) {
47-
IMGUI_DEBUG_LOG_ACTIVEID("SetActiveID() old:0x%08X (window \"${g.activeIdWindow?.name ?: ""}\") -> new:0x%08X (window \"${window?.name ?: ""}\")",
48-
g.activeId, id)
60+
IMGUI_DEBUG_LOG_ACTIVEID("SetActiveID() old:0x%08X (window \"${g.activeIdWindow?.name ?: ""}\") -> new:0x%08X (window \"${window?.name ?: ""}\")\n", g.activeId, id)
4961
g.activeIdTimer = 0f
5062
g.activeIdHasBeenPressedBefore = false
5163
g.activeIdHasBeenEditedBefore = false
@@ -62,14 +74,17 @@ internal interface basicAccessors {
6274
g.activeIdHasBeenEditedThisFrame = false
6375
if (id != 0) {
6476
g.activeIdIsAlive = id
65-
g.activeIdSource = if (g.navActivateId == id || g.navJustMovedToId == id) g.navInputSource else InputSource.Mouse
77+
g.activeIdSource = if(g.navActivateId == id || g.navJustMovedToId == id) g.navInputSource else InputSource.Mouse
6678
assert(g.activeIdSource != InputSource.None)
6779
}
6880

6981
// Clear declaration of inputs claimed by the widget
7082
// (Please note that this is WIP and not all keys/inputs are thoroughly declared by all widgets yet)
7183
g.activeIdUsingNavDirMask = 0x00
7284
g.activeIdUsingAllKeyboardKeys = false
85+
// #ifndef IMGUI_DISABLE_OBSOLETE_KEYIO
86+
// g.ActiveIdUsingNavInputMask = 0x00
87+
// #endif
7388
}
7489

7590
/** FIXME-NAV: The existence of SetNavID/SetNavIDWithRectRel/SetFocusID is incredibly messy and confusing and needs some explanation or refactoring. */
@@ -124,10 +139,17 @@ internal interface basicAccessors {
124139
fun markItemEdited(id: ID) {
125140
// This marking is solely to be able to provide info for IsItemDeactivatedAfterEdit().
126141
// ActiveId might have been released by the time we call this (as in the typical press/release button behavior) but still need to fill the data.
127-
assert(g.activeId == id || g.activeId == 0 || g.dragDropActive)
128-
//IM_ASSERT(g.CurrentWindow->DC.LastItemId == id)
129-
g.activeIdHasBeenEditedThisFrame = true
130-
g.activeIdHasBeenEditedBefore = true
142+
val g = gImGui
143+
if (g.activeId == id || g.activeId == 0) {
144+
g.activeIdHasBeenEditedThisFrame = true
145+
g.activeIdHasBeenEditedBefore = true
146+
}
147+
148+
// We accept a MarkItemEdited() on drag and drop targets (see https://github.com/ocornut/imgui/issues/1875#issuecomment-978243343)
149+
// We accept 'ActiveIdPreviousFrame == id' for InputText() returning an edit after it has been taken ActiveId away (#4714)
150+
assert(g.dragDropActive || g.activeId == id || g.activeId == 0 || g.activeIdPreviousFrame == id)
151+
152+
//IM_ASSERT(g.CurrentWindow->DC.LastItemId == id);
131153
g.lastItemData.statusFlags /= ItemStatusFlag.Edited
132154
}
133155

core/src/main/kotlin/imgui/internal/api/basicHelpersForWidgetCode.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ internal interface basicHelpersForWidgetCode {
132132
// return false;
133133
val isRectVisible = bb overlaps window.clipRect
134134
if (!isRectVisible)
135-
if (id == 0 || (id != g.activeId && id != g.navId))
135+
if (id == 0 || (id != g.activeId && id != g.activeIdPreviousFrame && id != g.navId))
136136
if (!g.logEnabled)
137137
return false
138138

0 commit comments

Comments
 (0)