Skip to content

Conversation

@PavelSharp
Copy link
Contributor

🎉🥳 I’m happy to propose a very small but useful API addition that allows Nuklear to provide complete information about the active text input area and cursor position. This data can be consumed by backends to enable functions such as SDL_SetTextInputArea

Mini FAQ
Q: Why SDL_SetTextInputArea is important?

Answer

SDL_SetTextInputArea informs the operating system where text is currently being entered.
This is crucial for IME (Input Method Editor) systems.

For example, on Windows with the Japanese IME enabled, the OS shows a small candidate list window near the cursor when typing. Without accurate text area information, this window appears in the wrong place. With this API, the IME candidate window is correctly positioned on top of the actual editing location.

This greatly improves the usability of Nuklear for logographic languages such as Japanese, Chinese, and Korean. And that's just one example we haven't even touched mobile devices yet!

Q: Why this architecture?

Answer

The chosen design is inspired by Dear ImGui.
ImGui demonstrates that callback-based control can be unnecessarily heavy, and that a much simpler pattern works better in immediate-mode GUI systems: the backend polls the desired text input state every frame.

The key idea:

  • Widgets don’t hold multi-frame state.
  • The GUI "proves" each frame that it needs the text keyboard
  • The backend simply reads this state(text_want) and useful data(text_area and text_cursor).

No persistent widget ownership logic is required, and the changes to Nuklear’s core remain extremely small.

Q: Why we believe this approach is robust

Answer

This design follows what I call a proof system:

  • On every nk_input_begin, text input is considered disabled.
  • If any editable widget reaches the stage where it needs text input, it explicitly sets a text_want flag.
  • The backend checks this flag during rendering and activates text input accordingly.

This approach guarantees that no stale state carries over between frames, and avoids the need for any complex widget-activation logic. The text area and cursor rectangles remain simple auxiliary metadata.

This also includes a patch (written quickly, so not yet C89 compatible) for the existing SDL3 PR. I highly recommend trying this patch, as it visualizes debug rectangles for the active text field and cursor. Japanese keyboard layout is recommended for testing on Windows.

CLICK ME
diff --git a/nuklear_sdl3_renderer_latest.h b/nuklear_sdl3_renderer.h
index 73b800b..74d2c8e 100644
--- a/nuklear_sdl3_renderer_latest.h
+++ b/nuklear_sdl3_renderer.h
@@ -164,6 +164,22 @@ nk_sdl_device_upload_atlas(struct nk_context* ctx, const void *image, int width,
     SDL_SetTextureBlendMode(sdl->ogl.font_tex, SDL_BLENDMODE_BLEND);
 }
 
+NK_INTERN SDL_FRect
+nk_sdl_render__rect_to_win(SDL_Renderer* renderer, float x, float y, float w, float h)
+{
+    SDL_FRect result;
+    float x1, y1, x2, y2;
+
+    SDL_RenderCoordinatesToWindow(renderer, x, y, &x1, &y1);
+    SDL_RenderCoordinatesToWindow(renderer, x + w, y + h, &x2, &y2);
+    result.x = x1;
+    result.y = y1;
+    result.w = x2 - x1;
+    result.h = y2 - y1;
+
+    return result;
+}
+
 NK_API void
 nk_sdl_render(struct nk_context* ctx, enum nk_anti_aliasing AA)
 {
@@ -180,8 +196,37 @@ nk_sdl_render(struct nk_context* ctx, enum nk_anti_aliasing AA)
         sdl->last_render = ticks;
     }
 
+    SDL_FRect dbg_arect = { 0,0,0,0 };
+    SDL_FRect dbg_crect = { 0,0,0,0 };
+    if (ctx->input.keyboard.text_want) {
+        struct nk_rect ta = ctx->input.keyboard.text_area;
+        struct nk_rect tc = ctx->input.keyboard.text_cursor;
+
+        dbg_arect = (SDL_FRect){ ta.x, ta.y, ta.w, ta.h };
+        dbg_crect = (SDL_FRect){ tc.x, tc.y, tc.w, tc.h };
+
+        if (ta.w > 0.0f && ta.h > 0.0f && tc.w > 0.0f && tc.h > 0.0f)
+        {
+            struct SDL_FRect wta = nk_sdl_render__rect_to_win(sdl->renderer, ta.x, ta.y, ta.w, ta.h);
+            struct SDL_FRect wtc = nk_sdl_render__rect_to_win(sdl->renderer, tc.x, tc.y, tc.w, tc.h);
+            struct nk_rect row = nk_rect(wta.x, wtc.y, wta.w, SDL_max(wtc.h, 16));
+            //SDL_Rect irow = { (int)SDL_floorf(row.x), (int)SDL_floorf(row.y), (int)SDL_ceilf(row.w), (int)SDL_ceilf(row.h) };
+            SDL_Rect irow = { (int)row.x, (int)row.y, (int)row.w, (int)row.h };
+            SDL_SetTextInputArea(sdl->win, &irow, (int)(wtc.x - row.x));
+        }
+        else {
+            SDL_Rect default_rect = { 0, 0, 0, 0 };
+            SDL_SetTextInputArea(sdl->win, &default_rect, 0);
+        }
+        if (!SDL_TextInputActive(sdl->win)) {
+            SDL_StartTextInput(sdl->win);
+        }
+    }
+    else {
+        SDL_StopTextInput(sdl->win);
+    }
     /* start/stop SDL text input events upon (de)activating any Edit */
-    active = nk_edit_is_any_active(ctx);
+    /*active = nk_edit_is_any_active(ctx);
     if (active != sdl->edit_was_active)
     {
         const bool sdl_active = SDL_TextInputActive(sdl->win);
@@ -190,7 +235,8 @@ nk_sdl_render(struct nk_context* ctx, enum nk_anti_aliasing AA)
         else if (sdl_active && sdl->edit_was_active && !active)
             SDL_StopTextInput(sdl->win);
         sdl->edit_was_active = active;
-    }
+    }*/
+
 
     {
         SDL_Rect saved_clip;
@@ -275,6 +321,14 @@ nk_sdl_render(struct nk_context* ctx, enum nk_anti_aliasing AA)
         nk_buffer_free(&vbuf);
         nk_buffer_free(&ebuf);
     }
+    if (ctx->input.keyboard.text_want) {
+
+        SDL_SetRenderDrawBlendMode(sdl->renderer, SDL_BLENDMODE_BLEND);
+        SDL_SetRenderDrawColor(sdl->renderer, 255, 0, 0, 100); 
+        SDL_RenderFillRect(sdl->renderer, &dbg_arect);
+        SDL_SetRenderDrawColor(sdl->renderer, 0, 0, 255, 100);
+        SDL_RenderFillRect(sdl->renderer, &dbg_crect);
+    }
 }
 
 NK_INTERN void
изображение

@PavelSharp PavelSharp force-pushed the feat-want_text_keyboard branch from 4c66e79 to 9fa27a6 Compare November 14, 2025 14:09
@PavelSharp PavelSharp force-pushed the feat-want_text_keyboard branch 4 times, most recently from b34ef39 to 688591b Compare November 14, 2025 14:44
@PavelSharp
Copy link
Contributor Author

PavelSharp commented Nov 15, 2025

This discussion started in #852, but it actually relates much more to this PR.

PART 1

CLICK TO SHOW

PavelSharp

News. In the current version of this PR, keyboard input for all nk_property_* is broken. Pressing any physical key is ignored except for Backspace.
The likely cause is nk_edit_is_any_active, or more specifically, that we don’t account for other widgets (including user-defined ones) that may also request text input.
We probably need a more sophisticated input handling system, which would be better implemented as part of the backend rather than the nuklear core itself. But...
[UPD]
Actually, I just got an idea. We could introduce a new callback into nk_do_edit that gets triggered when the widget reach the NK_EDIT_ACTIVATED (and/or NK_EDIT_DEACTIVATED) flag. As a nice bonus, this callback could also prepare the bounding rectangle, so we can call SDL_SetTextInputArea directly. This would also allow user-defined widgets to trigger the same callback.

What do you think about this approach?

sleeptightAnsiC

I'll take a look into it.

Callback would be perfect, but I don't want to break ABI/API accidentally. I suspect I simply missed some case for nk_edit_is_any_active, I already have some ideas.

When it comes to font scaling, this code is not mine, it comes from other demos and I didn't dare to touch it. I'll see what can be done here.

I also just realized that I did broke commit history. There should be two commits, not one... I probably smashed those during latest force-push.

PART 2

CLICK TO SHOW

PavelSharp

@sleeptightAnsiC, maybe I’m missing something, but after looking deeper into this issue, I’m convinced that the only truly safe and future-proof solution for handling text input and virtual keyboard behavior is to add a callback. Ideally behind a macro guard so it doesn’t break the ABI or API.

Here are my main arguments:

  1. Avoiding fragile internal changes
    Any other approach risks introducing major internal modifications to Nuklear’s core logic, something that could easily lead to regressions and new bugs down the line. Include some complex scenarios that we might not see when testing with the standard examples only
    A callback, on the other hand, keeps the logic contained and easy to reason about.

  2. Currently, there are already many bugs and fixing them may lead to new bugs
    2.1 As noted by @rswinkle in his comment
    2.2 As noted by me in [BUG] Previous edit will draw cursor after switching focus to another edit in a different window #856

  3. Respecting user-defined widgets
    A callback also gives us a clean and extensible way for custom widgets (not based on nk_do_edit) to request and release text input properly, something impossible to do safely under the current proposal.

  4. Safe and backward-compatible
    If implemented with a macro guard, this feature remains completely optional and non-breaking.
    Existing applications would behave exactly the same, while backends or advanced users could enable the callback when needed.

  5. Contextual data(mostly for SDL_SetTextInputArea)
    The callback could provide the input area rectangle and other stuff, allowing the backend to know:
    when to show the virtual keyboard (edit becomes active)
    and when to hide it (e.g. edit turns read-only in this frame or loses focus)

  6. Consistency with existing Nuklear design
    This is just my opinion, but adding a virtual keyboard callback is not a hacky workaround, it’s actually a clean and natural extension of Nuklear’s design. It fits perfectly into the existing family of callbacks (copy, paste, etc.) and keeps the logic modular, explicit, and easy to reason about, exactly in the spirit of Nuklear’s minimalistic philosophy.

I’d really like to hear what you think about this, @rswinkle and @RobLoach.

If nobody minds, I’ll go ahead and start working on it soon❤️

sleeptightAnsiC

maybe I’m missing something, but after looking deeper into this issue, I’m convinced that the only truly safe and future-proof solution for handling text input and virtual keyboard behavior is to add a callback.

I don't see how adding a callback would solve any issue here.

It could be a nice addition, but the callback needs to be call'ed by something.
That thing is a code that is currently broken. Inserting a callback would not fix it.
You need to fix the the underlying issue first, before doing anything else.

I'm not gonna introduce any callback as part of this PR and I won't change my mind about it.
You can do this as part of different PR.

PavelSharp

@sleeptightAnsiC

I don't see how adding a callback would solve any issue here.

I'm sure it's obvious. So, my plan is to introduce a callback that would be invoked inside nk_do_edit depending on the flags NK_EDIT_ACTIVATED and NK_EDIT_DEACTIVATED. The callback would be a function pointer stored in nk_context and set by the backend, which is fully consistent with the style of existing callbacks like copy and paste.

It’s also important to note that nk_do_edit is only called by text fields (e.g. nk_edit_string) and nk_property. This means the callback would directly address the exact issue we are discussing, without affecting unrelated parts of Nuklear.

I won't change my mind about it.

I believe I’ve given enough reasons why a new callback is preferable. Modifying nk_edit_is_any_active would require many changes in Nuklear, which almost equivalent to new bugs.

sleeptightAnsiC

So, my plan is to introduce a callback that would be invoked inside nk_do_edit
(...)
It"s also important to note that nk_do_edit is only called by text fields (e.g. nk_edit_string) and nk_property. This means the callback would directly address the exact issue we are discussing, without affecting unrelated parts of Nuklear.

If that's so simple, then just move the edit state de/activation to this one function, and the problem will be "fixed". The state changes are already here, the callback would be a new thing. One does not contradict the other, but the old implementation should be fixed.

DISCLAIMER: From now on I'm gonna ignore any text that has a sign of AI/ChatGPT use (sorry)

PART 3

CLICK TO SHOW

rswinkle

I’d really like to hear what you think about this, @rswinkle and @RobLoach.

I have to agree with @sleeptightAnsiC on this.

  1. Avoiding fragile internal changes
    Any other approach risks introducing major internal modifications to Nuklear’s core logic, something that could easily lead to regressions and new bugs down the line. Include some complex scenarios that we might not see when testing with the standard examples only
    A callback, on the other hand, keeps the logic contained and easy to reason about.

I would not call either my approach or the current approach fragile. As sleeptightAnsiC said, all approaches at their optimal/minimal state amount to the same thing, and in fact the callback method probably requires the most in actual lines changed. The logic is not/would not be in the callback. It is a matter of Nuklear internal state regardless. Knowing where/when to insert the callback is the same knowledge you would need for any other method.

  1. Currently, there are already many bugs and fixing them may lead to new bugs

Again, these are Nuklear bugs that need to be fixed in any case and the code changes are very small. It's a matter of discovery/precision.

  1. Respecting user-defined widgets
    A callback also gives us a clean and extensible way for custom widgets (not based on nk_do_edit) to request and release text input properly, something impossible to do safely under the current proposal.

The request and release does not happen at the widget level, that's part of the issue. It has to be aggregated over all the windows/widgets over the whole frame being rendered, and at that point, you know if either Start or Stop should be called based on whether it changed from the previous. Whether you use my method, adding a couple of members to try to simplify the test by collating as we go, or try to get the same result using a function like nk_is_any_active() combining hopefully all necessary checks (or both), the solution is the same. Ideally all widgets using text input should use the same method directly or (since that's obviously not the case) they should pass that state along to the text_edit state as the "master" state.

  1. Safe and backward-compatible
    If implemented with a macro guard, this feature remains completely optional and non-breaking.
    Existing applications would behave exactly the same, while backends or advanced users could enable the callback when needed.

There is no breaking change regardless. Nuklear is not used as a dynamic library that will be updated separately from the application(s) that use it. It is meant to be compiled statically. And the changes are not what I would consider "breaking": Either a couple added field to an internal structure, an extra utility function meant almost exclusively for backend implementers not average users, or both. No change to the existing API, and nothing removed that could break someone's code.

  1. Contextual data(mostly for SDL_SetTextInputArea)

I still don't really understand the point of this function but all the logic above applies here too afaict. The difficulty is in getting the information in the first place.

  1. Consistency with existing Nuklear design
    This is just my opinion, but adding a virtual keyboard callback is not a hacky workaround, it’s actually a clean and natural extension of Nuklear’s design. It fits perfectly into the existing family of callbacks (copy, paste, etc.) and keeps the logic modular, explicit, and easy to reason about, exactly in the spirit of Nuklear’s minimalistic philosophy.

None of them are hacky workarounds. As I said all would require roughly the same logic/changes, the callback would just require a bit more code for the macro stuff at the top. My first attempt at doing this was a macro based callback system a la NK_IS_WORD_BOUNDARY() which I worked on a while back. It was two optional macros NK_ON_EDIT_FOCUS()/NK_ON_EDIT_UNFOCUS(). I ended up deleting it and replacing it twice, ending up on my current version because I realized everything that I discussed above. Not only do we not want or need to call Start/StopText() within Nuklear (even indirectly), but everything boils down to correctly and efficiently tracking the boolean state so the backend/user can do it themselves if they want to.

What should be documented is all the knowledge about how that state is tracked which is what someone implementing a new text input widget not based on the nk_do_edit() family needs to know. As long as they remember the few lines to set the state appropriately then everything would just work.

It’s also important to note that nk_do_edit is only called by text fields (e.g. nk_edit_string) and nk_property. This means the callback would directly address the exact issue we are discussing, without affecting unrelated parts of Nuklear.
I believe I’ve given enough reasons why a new callback is preferable. Modifying nk_edit_is_any_active would require many changes in Nuklear, which almost equivalent to new bugs.

See above. It's not that many changes and you would have to touch the same pieces of the code for the callback method.

PART 4

CLICK TO SHOW

PavelSharp

At this point, I think I can add two fields to nk_edit_state

I believe that the nk_edit_state is not quite right place since we should take into account user-defined widgets which are not based on nk_do_edit and nk_edit_state

sleeptightAnsiC

At this point, I think I can add two fields to nk_edit_state

I believe that the nk_edit_state is not quite right place since we should take into account user-defined widgets which are not based on nk_do_edit and nk_edit_state

I mistaken struct nk_edit_state for struct nk_text_edit, so it wouldn't work anyway, since nk_do_edit only has access to the latter one. (or maybe I didn't, I'm not entirely sure where I got this assumption from)

However, I don't believe we can take user-defined text widgets into consideration, if those aren't being implemented via nk_do_edit (or something similar). Many primitives from nk_edit_* API will not work for those widgets anyway, so why would someone expect the new call (nk_edit_is_active/nk_edit_get_active) to work without it?

Your solution from #857 also triggers the callbacks updates from nk_do_edit, so how the "not based on nk_do_edit" case is being solved there? What do you mean by "user-defined widgets which are not based on nk_do_edit" ?

PavelSharp

Your solution from #857 also triggers the callbacks from nk_do_edit, so how the "not based on nk_do_edit" case is being solved there? What do you mean by "user-defined widgets which are not based on nk_do_edit" ?

Let's imagine a user-defined widget that wants to notify Nuklear's input system (and backend), that it is about to receive text keyboard input.

In this case, the widget is responsible for calling nk_input_want_text_keyboard, and if that succeeds, it must populate the text_cursor and text_area fields. In other words, the responsibility for requesting text keyboard input and filling these fields lies entirely with the widget.

A clear example of this is nk_do_edit, which already handles this, but any other widget could do the same.

The main point is that text_want, text_area, and text_cursor belong to the input system’s notification mechanism - a new concept which was introduced by my PR. We update nk_keyboard with these fields, to form the input notification system. Since the backend checks them before nk_input_begin, no callbacks are needed.

[NEW PART]

However, I don't believe we can take user-defined text widgets into consideration, if those aren't being implemented via nk_do_edit (or something similar).

In fact, the existence of such widgets is also assumed by @rswinkle in his message:

What should be documented is all the knowledge about how that state is tracked which is what someone implementing a new text input widget not based on the nk_do_edit() family needs to know. As long as they remember the few lines to set the state appropriately then everything would just work.

sleeptightAnsiC

I've taken a better look at #857 and I'm a bit confused.

the widget is responsible for calling nk_input_want_text_keyboard, and if that succeeds, it must populate the text_cursor and text_area fields [...] a new concept which was introduced by my PR

Why would people follow this very specific "concept", if they're not even following an already established solution like nk_do_edit() ? This reasoning contradicts itself and can be applied the other way around.

We update nk_keyboard with these fields, to form the input notification system. Since the backend checks them before nk_input_begin, no callbacks are needed.

This is not a notification, no one is getting notified about anything. The user of #857 would still have to pull that information manually from ctx->input.keyboard (or similar) which also expects that the widget must set and update it correctly.

This really feels like repeating the old mistake. We already proven that widgets are not to be trusted with setting the context state correctly, and against all reasoning, this new "concept" introduces yet another state that we have to track and care about. It does not create any convention that custom widgets could safely follow.

I don't get it. All your previous messages were about a "callback" system (I see 43 matches when I search for this word on this page). You've tried very hard to convince us to "calback" solution, so why this new thing ended up being another manual "state" update? I'm really confused here. Note that I only disagreed about callbacks being a part of this PR, I still thought you would go for it later in another PR (#857).

PavelSharp

This is not a notification, no one is getting notified about anything. The user of #857 would still have to pull that information manually from ctx->input.keyboard (or similar)

That’s not quite right. The backend is the one that needs to read this information, the user doesn’t have to worry about it.
I only used the word “notification” to help explain the idea. I often work with publisher-subscriber patterns, so the term just felt natural to me in this context.

also expects that the widget must set and update it correctly.

Yes, if a user wants to create a custom, non-standard text-input widget, they should use this new simple API. In this very rare situation, this behavior is completely normal, and it has already been mentioned here(quoting it for the second time):

What should be documented is all the knowledge about how that state is tracked which is what someone implementing a new text input widget not based on the nk_do_edit() family needs to know. As long as they remember the few lines to set the state appropriately then everything would just work.

Next

This really feels like repeating the old mistake. We already proven that widgets are not to be trusted with setting the context state correctly, and against all reasoning

It’s possible that the earlier arguments weren’t convincing, because #857 works very well and very stable. I haven’t been able to find any issues, even when testing different combinations of clicks and focus changes.
Also, #857 results in only about 30 lines of final changes and it’s important to recognize that these 30 lines are the conclusion of a long and difficult effort to properly integrate SDL3.

this new "concept" introduces yet another state that we have to track and care about. It does not create any convention that custom widgets could safely follow.

First, this new state exists only until the next call to nk_input_begin, which normally corresponds to a single frame. Because of that, introducing this state is harmless.

Second, please read my quote again:

calling nk_input_want_text_keyboard, and if that succeeds, it must populate the text_cursor and text_area fields.

So, the rule is very simple.

I don't get it. All your previous messages were about a "callback" system (I see 43 matches when I search for this word on this page). You've tried very hard to convince us to "calback" solution, so why this new thing ended up being another manual "state" update? I'm really confused here.

In the end, my opinion changed. You may want to look more closely at #857, where it is explicitly mentioned:

The chosen design is inspired by Dear ImGui. ImGui demonstrates that callback-based control can be unnecessarily heavy

as well as:

Since the backend checks them before nk_input_begin, no callbacks are needed.

@sleeptightAnsiC
Copy link
Contributor

sleeptightAnsiC commented Nov 15, 2025

Please, notice that I'm writing everything here in good-faith. This topic has a long and complicated story. No harm intended.

EDIT: hah, Pavel posted his message just few seconds before mine. See the CLICK TO SHOW above. There is also way more context to this in #852 (make sure to read it, especially hidden comments).

Due to many concerns, I did not want to introduce any complicated TextInput handling during #852 and decided to go with very conservative and minimal solution that would barely change the library code, just to make it possible to hook SDL_SetTextInputActive. We of course knew this won't be enough for hooking the other function (SDL_SetTextInputArea) but noone wanted co complicate the demo/backend any further, especially given the fact that this feature is highly advanced, and because it was taking soooo long to get the demo merged.

Pavel tried to convince us that the "callback" system is the only true solution to this problem, and probably it is, but back then I disagreed, because of the reasons mentioned in the previous paragraph, and because there is already a lot of existing functionality that we could try to reuse. However, I did agree that this can be solved later during different PR, and this implies that "callback" solution could work (just not as a part of #852).

Now, my current stance on this PR is as follows: It is NOT a "callback" system, but instead an another "state tracking" that introduces yet another widget convention. I believe that the solution should EITHER be a "callback" system OR some fixes to the OLD state tacking. I think this comment reflects my point of view well #852 (comment) (it's a reply to longer thread):

[...] Why would people follow this very specific "concept" [aka calling nk_input_want_text_keyboard() and so on], if they're not even following an already established solution like nk_do_edit() [and updating existing context states] [...] This really feels like repeating the old mistake. We already proven that widgets are not to be trusted with setting the context state correctly, and against all reasoning, this new "concept" introduces yet another state that we have to track and care about. [...]

This was later answered with some explanations, here #852 (comment) , but I don't think I agree with everything. There is a lot of information, so I'm sorry if I missed something.

Despite my current disagreement, I really really want this feature in, and I even removed any possible conflicts from #852 that could make it harder to introduce, just to ensure that Pavel (or someone else) will have a free hand when implementing this. I simply think the current approach in this PR is not entirely correct, and it may cause people to re-implement this whole convention YET AGAIN in the future, just like what we're trying to do right now.

I just wanted to make this whole situation and my reasoning clear. Again, I'm not trying to sabotage this change, I just want everyone to understand the complexity of this problem. You can see how much time was put into this during #852 and yet we decided to not fix it back then.

@sleeptightAnsiC
Copy link
Contributor

Implementation note: We probably also want to support something like SDL_StartTextInputWithProperties, where the caller needs to know a bit more about the kind of current text input. Nuklear already has something like this in a form of nk_filter_*, but we also need to consider user-provided filters.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants