-
Notifications
You must be signed in to change notification settings - Fork 619
Add SDL3 #779
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
base: master
Are you sure you want to change the base?
Conversation
Sure! I'll take a look in a few hours. Thanks |
Also made it thread-safe. There are a couple things that I think could improve. I'm not 100% sure it's cleaning up the memory correctly tho |
demo/sdl3/nuklear_sdl3_renderer.h
Outdated
char *str = 0; | ||
(void)usr; | ||
if (!len) return; | ||
str = (char*)SDL_malloc((size_t)len+1); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In case of multi-byte text, like SDL TextInput which is unicode, len
is count grapheme clusters or glyphs, not bytes.
return SDL_Fail(); | ||
} | ||
|
||
SDL_StartTextInput(appContext->window); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On desktop platforms, SDL_StartTextInput() is implicitly called on SDL window creation. On some, typically non desktop, platforms this call implicitly enables and shows virtual keyboard.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In SDL3, this needs to be explicitly called on Desktop platforms. I think adding a comment about the behavior of calling this on mobile is probably in order
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, is there a cleaner way we could accomplish this? Perhaps a callback when a textbox on focus/blur?
demo/sdl3/nuklear_sdl3_renderer.h
Outdated
|
||
case SDL_EVENT_MOUSE_MOTION: | ||
if (ctx->input.mouse.grabbed) { | ||
int x = (int)ctx->input.mouse.prev.x, y = (int)ctx->input.mouse.prev.y; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
SDL3 (mouse) movement events were changed from integer to float. Nuklear uses float internally too. Is the integer cast truncation intentional?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was a hack on my part, changing to float and removing the cast should be fine
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Know the significance of ctx->input.mouse.grabbed
? Looking at the other implementations, I'm not quite sure how they'd apply here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems as if while window is moved around by grabbing it with mouse, then absolute mouse pointer x,y cannot be trusted and thus relative x,y are being used.
The SDL documentation says in this scenario SDL_GetGlobalMouseState()
is supposed to be used.
But actually the situation is more complex. Typically a full screen or borderless window SDL application would force relative mouse movement, and locked mouse into window by SDL_SetRelativeMouseMode. This mode grabs the mouse pointer by the window and hides the system cursor or something like that. And when the application switches to windowed mode, then relative mouse mode is disabled and mouse is only locked otionally into window when window gets focus and mouse gets unlocked when window loses focus.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After some testing, I think what I've put in there seems to be working with or without window grabbing.
case SDL_EVENT_MOUSE_MOTION:
ctx->input.mouse.prev = ctx->input.mouse.pos;
ctx->input.mouse.delta.x = evt->motion.xrel;
ctx->input.mouse.delta.y = evt->motion.yrel;
ctx->input.mouse.pos.x = evt->motion.x;
ctx->input.mouse.pos.y = evt->motion.y;
return 1;
I played around with SetRelativeMouseMode too, and it didn't seem to have any real affect...
case SDL_EVENT_MOUSE_MOTION:
{
struct nk_sdl* sdl = (struct nk_sdl*)ctx->userdata.ptr;
ctx->input.mouse.prev = ctx->input.mouse.pos;
ctx->input.mouse.delta.x = evt->motion.xrel;
ctx->input.mouse.delta.y = evt->motion.yrel;
ctx->input.mouse.pos.x = evt->motion.x;
ctx->input.mouse.pos.y = evt->motion.y;
if (ctx->input.mouse.grabbed) {
SDL_SetWindowRelativeMouseMode(sdl->win, true);
ctx->input.mouse.pos.x += evt->motion.x;
ctx->input.mouse.pos.y += evt->motion.y;
}
else {
SDL_SetWindowRelativeMouseMode(sdl->win, false);
ctx->input.mouse.pos.x = evt->motion.x;
ctx->input.mouse.pos.y = evt->motion.y;
}
return 1;
}
Overall, I think what's in there is fine and don't want to over-complicate it 🤷 ... What do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree, it is fine as it is.
SDL_SetWindowRelativeMouseMode()
is best to use when the application is in full screen or borderless full screen modes. The mouse cursor is confined into the whole screen (so app window), the OS / HW mouse pointer is hidden (but software cursor could be used), and mouse motion events are rapidly reporting mouse motion position and delta position. This is fit for most action games where you control a player character. Or in case of emulators where cursor is also emulated, e.g. DOSBox.
The way you used that API is somewhat wrong as when relative mode is enabled, the current event does not reflect the changes yet. In relative mode we would only use evt->motion.xrel/yrel
. In normal mode we would use only the absolute position so evt->motion.x/y
.
case SDL_EVENT_TEXT_INPUT: | ||
{ | ||
nk_glyph glyph; | ||
SDL_memcpy(glyph, evt->text.text, NK_UTF_SIZE); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
evt->text.text
is UTF-8 encoded const char* nul terminated string. Based on limited research it is not guaranteed that a single glyph is reported by this event. Could it happen that text input gets cropped as the UTF-8 input is not passed per glyph to nk_input _glyph in a loop?
I don't have push access to this branch, but this patch fixes the casting klei mentioned: index 5a8c589..e819169 100644
--- a/demo/sdl3/nuklear_sdl3_renderer.h
+++ b/demo/sdl3/nuklear_sdl3_renderer.h
@@ -308,10 +308,10 @@ nk_sdl_handle_event(struct nk_context* ctx, SDL_Event *evt)
case SDL_EVENT_MOUSE_MOTION:
if (ctx->input.mouse.grabbed) {
- int x = (int)ctx->input.mouse.prev.x, y = (int)ctx->input.mouse.prev.y;
- nk_input_motion(ctx, x + evt->motion.xrel, y + evt->motion.yrel);
+ float x = ctx->input.mouse.prev.x, y = ctx->input.mouse.prev.y;
+ nk_input_motion(ctx, (int)(x + evt->motion.xrel), (int)(y + evt->motion.yrel));
}
- else nk_input_motion(ctx, evt->motion.x, evt->motion.y);
+ else nk_input_motion(ctx, (int)evt->motion.x, (int)evt->motion.y);
return 1;
case SDL_EVENT_TEXT_INPUT:
@@ -323,7 +323,7 @@ nk_sdl_handle_event(struct nk_context* ctx, SDL_Event *evt)
return 1;
case SDL_EVENT_MOUSE_WHEEL:
- nk_input_scroll(ctx,nk_vec2((float)evt->wheel.x,(float)evt->wheel.y));
+ nk_input_scroll(ctx, nk_vec2(evt->wheel.x, evt->wheel.y));
return 1;
}
return 0; The first casting change is technically unnecessary, but it allows adding the prev and rel values as floats before converting them back to ints. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added a few more changes.
demo/sdl3/nuklear_sdl3_renderer.h
Outdated
|
||
case SDL_EVENT_MOUSE_MOTION: | ||
if (ctx->input.mouse.grabbed) { | ||
int x = (int)ctx->input.mouse.prev.x, y = (int)ctx->input.mouse.prev.y; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After some testing, I think what I've put in there seems to be working with or without window grabbing.
case SDL_EVENT_MOUSE_MOTION:
ctx->input.mouse.prev = ctx->input.mouse.pos;
ctx->input.mouse.delta.x = evt->motion.xrel;
ctx->input.mouse.delta.y = evt->motion.yrel;
ctx->input.mouse.pos.x = evt->motion.x;
ctx->input.mouse.pos.y = evt->motion.y;
return 1;
I played around with SetRelativeMouseMode too, and it didn't seem to have any real affect...
case SDL_EVENT_MOUSE_MOTION:
{
struct nk_sdl* sdl = (struct nk_sdl*)ctx->userdata.ptr;
ctx->input.mouse.prev = ctx->input.mouse.pos;
ctx->input.mouse.delta.x = evt->motion.xrel;
ctx->input.mouse.delta.y = evt->motion.yrel;
ctx->input.mouse.pos.x = evt->motion.x;
ctx->input.mouse.pos.y = evt->motion.y;
if (ctx->input.mouse.grabbed) {
SDL_SetWindowRelativeMouseMode(sdl->win, true);
ctx->input.mouse.pos.x += evt->motion.x;
ctx->input.mouse.pos.y += evt->motion.y;
}
else {
SDL_SetWindowRelativeMouseMode(sdl->win, false);
ctx->input.mouse.pos.x = evt->motion.x;
ctx->input.mouse.pos.y = evt->motion.y;
}
return 1;
}
Overall, I think what's in there is fine and don't want to over-complicate it 🤷 ... What do you think?
return SDL_Fail(); | ||
} | ||
|
||
SDL_StartTextInput(appContext->window); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, is there a cleaner way we could accomplish this? Perhaps a callback when a textbox on focus/blur?
struct nk_sdl* sdl; | ||
NK_ASSERT(ctx); | ||
sdl = (struct nk_sdl*)ctx->userdata.ptr; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One worry I have about this strategy is that the SDL renderer is taking over the nk_context
userdata
to store information required by the renderer. If the application is making use of the userdata
, there will be a conflicts unless they change to use nk_sdl_set_userdata()
instead.
Perhaps we introduce another renderer-specific userdata
property? Something like void* renderer_data
or something? As an added benefit, that could be used in some of the other renderers too (glfw_opengl2, sfml_gl3, gdi, etc).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I assume the renderer backends are still examples only and not part of the UI library, which is a single header. E.g. I change nk_sdl_handle_event to add more keys, etc. As integrator, I am in full control over the userdata. E.g. I do not have nk_sdl sdl at all, I embed the stuff from it into my own context thingy.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So you use your own userdata elsewhere outside of the nuklear context?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Um, I do not use nuklear_sdl3_renderer.h
and APIs like nk_sdl_render(struct nk_context* ctx
at all. I use the demo as an example and adapt it to my needs. E.g. my own version of nk_sdl_render()
gets not a Nuklear context, it gets an application context that embeds the Nuklear context too. What I wanted to communicate above is that the life cycle of the Nuklear context is shorter then the application context in my case, and that I consider nuklear_sdl3_renderer.h
to be an example, not the official API.
So the answer to your question is actually no. I do not have a specific need for userdata enabled by NK_INCLUDE_COMMAND_USERDATA
, it can be used by nuklear_sdl3_renderer.h
and I will adapt that header content as I need.
-
Text processing: In case of text processing I use freetype2, different texture packer, custom font atlas, and custom cached glyph encoding library that are not exclusive to
nk_user_font
, and Nuklear. So the life cycle of my text rendering sub system is typically longer than the life cycle of the Nuklear context. The font atlas is based onSDL_surface
objects where I could do palette color manipulations, which is not a typical use case I admit, and when the Nuklear context is created and render backend is selected the font atlas layers are converted toSDL_textrure
objects in native texture format. I actually intend to use SDL3 GPU API not the SDL3 renderer. That is important as my whole design revolves around SDL3 GPU's ability to transparently select any backend that is available. I am not deciding which backend to use so my font atlas needs to remain backend agnostic (e.g. usesSDL_surface
not textures that have backend dependent pixel formats). -
GUI: my application context is not a sub feature of Nuklear, instead Nuklear's context is the sub feature of my application context. The same way as for text processing, I tried to shoot for a design which separates the GUI library and its render backend and the application core logic even if this is very difficult with the immediate mode pattern. Nuklear requires a lot of boilerplate and that boilerplate is interleaved into the core application logic which is difficult to maintain so I try to encapsulate, structure and generalize things for which I created an abstraction level above Nuklear similar to a c++ object oriented wrapper would do, but focusing on features (buttons, lists, hotkeys, sound effects, ...) instead of API coverage. I use a custom GUI layout manager that is loading parameters and asset or resource IDs from json files above and independent of Nuklear. Assets are in custom file formats using LZ4 compression and are loaded into renderer agnostic
SDL_surface
objects. Later on when the system specific render backend is known, D3D/OGL/etc, then these surfaces are converted and loaded into GPU memory intoSDL_texture
objects, using native texture formats, with assets packed as most optimal for particular screen content expected to be rendered. I only use Nuklear STATIC layout and position every top level (custom) widgets inside the application frontend or menu screens. Nuklear's render target is just anSDL_texture
not the swapchain texture or whatever. In short my application's context is one order or abstraction level higher than Nuklear's context. The Nuklear context is only created after the render backend is determined. I am sure there is a better way to do all of this, but this is what i came up with for my particular needs.
Co-authored-by: Rob Loach <[email protected]>
[SDL3] Use built-in motion handler to circumvent FPS-limit/VSync issue
What is the state of this PR? I'm happy to help with any remaining items |
I believe all that's really missing is...
|
Cool, for Sam told me in the SDL Discord to "Remember [that] this will bring up the on-screen keyboard on some platforms, so you should only do this when you're ready for text input". We could macro guard it or put a comment above it so people are aware of its behavior. I treat the source files for the backends as examples, but the header files for the backends as Nuklear vendored, so IMO it's not unreasonable to just put a disclaimer since it's "just an example". Alternatively, you could place the start and stop calls inside the focus/defocus events for text inputs or windows, but that might interfere with some edge cases. |
Actually, Sam also said that:
So it sounds like the latter is the best option. Any ideas on where we should hook into the input focus? I'm not too familiar on whether that should lie at a window focus or input focus boundary. Are there any cases of keyboard navigation that would make sense on desktop but not on other platforms? |
I've fully integrated this into my own project, and it's working nicely so far except for one minor issue. This is observable in Also, I started looking into the SDL TextInput stuff and it looks like the most trivial solution would be to somehow insert the calls into |
Agreed. Not an elegant solution currently. Are you proposing some kind of event callback system for them?
The other thing I'd like to see incorporated into this is a clearer implementation of detailing with the global object state. Or something like encorporating #799 into here. |
That's the first thing that comes to mind. I don't have deep knowledge of the Nuklear codebase, so I wasn't sure if that's done in other places, or what the best way to integrate something like that would be. Do you have any opinions?
This is interesting. Personally, I'm fine with the global state for my purposes. Does it make more sense to integrate these changes here, or have #799 tackle it for all of the SDL demos, once we have nailed down the API?
Did you have any thoughts on why this may be? |
Maybe a strict callback isn't necessary as that would incur a small but present overhead for all backends. I could see it being 2 (mutually inclusive) optional macros that are called if they exist at appropriate places in Nuklear. It wouldn't just be nk_edit_focus/nk_edit_unfocus, because that's just for the manually forcing focus/unfocus, not from the user actually clicking on an edit field or something else. I'm not sure what the best name is but something like NK_ON_EDIT_FOCUS()/UNFOCUS() might work. Not sure about any necessary parameters either. Just my two cents till I actually have the chance to review/test this and have a more informed opinion. |
@zoogies I've removed the sdl_gpu stuff, since it wasn't there yet, and cleaned up the CMake a bit. Mind a review?