-
-
Notifications
You must be signed in to change notification settings - Fork 138
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Feature Request] InputTextMultiline() needs text wrapping, for example via Wrapped(true) like labels. #434
Comments
well, I always tought that there is a flag for it, but it isn't... |
I've found a crazy workaround that illustrates the fantastic flexibility of Go and this framework. First I define the word wrap callback:
Now define the widget in advance instead of within the layout, and use the closure for defining its callback:
Later, I render it like this: The hack seems to work, although I've probably have missed some edge cases. It's hard-wrapping instead of soft-wrapping, but that's okay for my use case. Helper function:
|
@rasteric I'd suggest u to check/ask in https://github.com/ocornut/imgui if they have (or are planning to have) this feature, because IMO this feature should be in base repo instead implemented here. |
Quick update, in case someone is interested. I found a workaround. It's hard because imgui and therefore GIU does not render any other newline-like unicode character as a new paragraph, only NEWLINE character 13. Therefore, it's not easy to distinguish between user-entered newline (persistent) and new lines introduced by the word breaking algorithms. Here is how I managed to do it nevertheless:
This needs to be wrapped into the callback, e.g. like this:
You need to set both How it works: I first introduce some pivot char of exactly 2 bytes in UTF-8, I chose the first one \u07FF. Then, we replace these 2 bytes in the buffer with the sequence \r\n. We cannot to this in SetEventChar because it only allows one rune! The \n in the sequence \r\n is rendered as newline, whereas the \r is invisible (not a space). We then can zap all \n that are not part of a sequence \r\n and apply the greedy word breaking algorithm on each callback in The downside is that deleting user-input newline requires pressing backspace 2 times, which could be fixed by overriding the backspace functionality. It's barely tested and only on Linux so far. I feel like I've hacked into a Gibson (just kidding). |
@rasteric thanks for your code! it saved me now 😄 func WrapInputtextMultiline(widget *giu.InputTextMultilineWidget, data imgui.InputTextCallbackData) int32 {
switch data.EventFlag() {
case imgui.InputTextFlagsCallbackCharFilter:
c := data.EventChar()
if c == '\n' {
data.SetEventChar('\u07FF') // pivot character 2-bytes in UTF-8
}
case imgui.InputTextFlagsCallbackAlways:
// 0. turn every pivot byte sequence into \r\n
buff := data.Buffer()
buff2 := []byte(strings.ReplaceAll(string(buff), "\u07FF", "\r\n"))
for i := range buff {
buff[i] = buff2[i]
}
data.MarkBufferModified()
// 1. zap all newlines that are not preceeded by a CR (which was manually entered like above)
cr := false
for i, c := range buff {
if c == 10 && !cr {
buff[i] = 32
data.MarkBufferModified()
} else {
if c == 13 {
cr = true
} else {
cr = false
}
}
}
// 2. word break the whole buffer with the standard greedy algorithm
nl := 0
spc := 0
w := giu.GetWidgetWidth(widget)
for i, c := range buff {
if c == 10 {
nl = i
}
if c == 32 {
spc = i
}
if TextWidth(string(buff[nl:i])) > w {
if spc > 0 {
buff[spc] = 10
} else {
data.InsertBytes(len(buff)-1, []byte{10})
}
data.MarkBufferModified()
}
}
}
return 0
} I was manibulating a veeeeery long strings, and the string had to be force-splited if no spaces found |
I used the code to show a copyable and auto-wrap text(very long),but something wrong as follow
I guess error occurs in my code: // github.com/AllenDang/giu v0.7.0
// WrapInputtextMultiline
func CopyableText(s *string) giu.Widget {
ret := giu.InputTextMultiline(s).
Flags(giu.InputTextFlagsReadOnly | giu.InputTextFlagsCallbackAlways | giu.InputTextFlagsCallbackCharFilter)
cb := func(data imgui.InputTextCallbackData) int32 {
return WrapInputtextMultiline(ret, data)
}
ret.Callback(cb)
return ret
}
var tmp = strings.Repeat("a", 1000)
func loop() {
giu.SingleWindow().Layout(
CopyableText(&tmp),
)
}
func main() {
wnd := giu.NewMasterWindow("wechat viewer", 800, 600, 0)
wnd.Run(loop)
} |
@featherL after debugging, I found out whats the problem. If you remove all |
btw @featherL could you post ap-to-date version of |
Thanks. But I haven't solved this problem yet. And The |
turns out that IM_ASSERT(callback_data.Buf == callback_buf); // Invalid to modify those fields |
ok, @featherL I managed to fix this, here is the code compatible with latest giu: // WrapInputtextMultiline is a community-crafted function that allows to add auto-wrap to
// InputTextMultilineWidget in giu. It is a workaround for the lack of this feature in Dear ImGui.
// Credits:
// - @rasteric the original poster
// - @gucio321 modified for cimgui-go compatibility
// see: https://github.com/AllenDang/giu/issues/434
//
// [@gucio321] I try to figure out this code.
// ref:
// - ascii table: https://www.asciitable.com/
func WrapInputtextMultiline(widget *giu.InputTextMultilineWidget, data imgui.InputTextCallbackData) int {
const (
SPACE = ' '
CR = '\r'
NEWLINE = '\n'
)
switch data.EventFlag() {
// [@gucio321] This is expected to catch user-defined newlines and mark them with the following character
// to prevent it from being removed/modified in the code below.
case imgui.InputTextFlagsCallbackCharFilter:
c := data.EventChar()
if c == '\n' {
data.SetEventChar('\u07FF') // pivot character 2-bytes in UTF-8
}
case imgui.InputTextFlagsCallbackAlways:
// 0. turn every pivot byte sequence into \r\n
buff := []byte(data.Buf())
buff = []byte(strings.ReplaceAll(string(buff), "\u07FF", "\r\n"))
// remove all auto-added newlines
cr := false
for i, c := range buff {
switch c {
case CR:
cr = true // preserve \n following this \r
case NEWLINE:
if cr {
cr = false
break
}
buff[i] = SPACE
}
}
// 2. word break the whole buffer with the standard greedy algorithm
nl := 0
spc := 0
w := giu.GetWidgetWidth(widget)
for i, c := range buff {
switch c {
case NEWLINE:
nl = i
case SPACE:
spc = i
}
if TextWidth(string(buff[nl:i])) <= w {
continue
}
// this happens when the line should be wrapped
if spc > 0 {
buff[spc] = NEWLINE
} else {
data.InsertChars(int32(len(buff)-1), string([]byte{10}))
}
}
data.SetBufDirty(true)
// [@gucio321] since data.SetBuf is no longer a valid thing to be done since newer Dear ImGui,
// [@gucio321] we need to do this in this fancy way
data.DeleteChars(0, int32(len(buff)))
data.InsertChars(0, string(buff))
}
return 0
} |
@AllenDang should we add this as a built-in feature of InputTextMultilneWidget? Personally, I think this should remain "this magic code that you can copy from this issue on our github" as it is bugged as hell and not really oficial and supported by imgui. what do you think? |
@gucio321 Agree to leave it here as an example of how to implement wrapped in InputTextMultiline. |
Related problem
Currently,
InputTextMultiline()
does not seem to be able to wrap text (unless I've missed something). To automatically wrap text is pretty much standard for such widgets nowadays. Is this a limitation of imgui?Your request
InputTextMultiline().Wrapped(true)
should make a multiline text entry to soft-wrap text on unicode whitespace characters.Alternative solution
I'm trying to come up with a manual wrapping solution using
g.CalcTextSize(s)
but haven't gotten far yet. It's kind of complicated because of the editing, it shouldn't force the user to delete hard linefeeds when removing text.Additional context
No response
The text was updated successfully, but these errors were encountered: