Metamorphing Machine I rather be this walking metamorphosis
than having that old formed opinion about everything!

Nuklear melt

In my never-ending search for a cross-platform GUI that I can use in my put-on-hold transpiler, I stumbled upon Nuklear.
It says it is a single-header ANSI C file with no dependencies whatsoever.
As I couldn't figure out how to make it work, I've found up a derivative work called Nuklear+ (that's supposed to be called "Nuklear cross".)
Its home page explains why I failed to make Nuklear work: You are supposed to provide a native driver.

Nuklear+ comes with four of them: GDI+ (this is "plus", not "cross"...), GLFW, SDL, and Xlib.
It also has a plethora of bindings, but I didn't want to struggle with Java, D, or anything else. I'd just wanted to keep it simple and use C.

Usually, I don't fancy installing stuff on my computer because so often to have A, you have to download and install B. But to have B, you have to download and install C. Now, to have C...
And somewhere among all of this, one of the items always insists on not installing. Or working.

But Windows 10 has GDI+ natively.
And so it happened that I've downloaded a pretty thing called QB64 and it came with a simple extract-and-drop GCC compiler.
It seemed too good to be true.

One little experiment I would like to try is to build something like an InputBox.
When you invoke this function in VB, it displays a little form asking for user input.
Unlike MsgBox, InputBox does not have a handy Windows API function one can use. It seems to be a VB-only thing.

So I searched high and low in the interwebs for some Nuklear sample code I could modify but came out empty-handed.
All examples I could find were the same "one button, two radios, and a slider slapped in a form" that's on Nuklear's home page.
I've searched for fora. Found nothing. I've searched for questions in StackOverflow. Found only one. It does not have a Wikipedia page.
If Nuklear is being used, people don't like to talk about it.

OK. Let's try it anyway.

First thing is that, to add a control widget to a form window, you need to add a window to a window, and then add the widget to that second window. The first window acts like an MDI form.
I have no use to a window-inside-a-window, so after losing some hours and some hair, I came up with a hack:
I made the second window "fixed" (user is unable to resize it or drag it) and positioned it so its title bar - that was eating valuable real state space - becomes hidden behind the first window's title.

Now, to position the widgets the way I wanted, I resorted to a layout method called nk_layout_space_xxx. It allows one to specify the coordinates the widgets should be.

One thing that was bugging me was the color theme. Nuklear was intended to be used by game developers, so its default theme is dark.
By trial and error, I managed to change the colors to something good, but not great.

Nuklear+'s code to capture keyboard events in file nkc_gdip.h does not work.
I changed it to:

while (PeekMessageW(&msg, NULL, 0, 0, PM_REMOVE)) {
switch (msg.message) {
case WM_QUIT:
ne.type = NKC_EWINDOW;
ne.window.param = NKC_EQUIT;
break;

//case WM_KEYDOWN:
case WM_CHAR:
ne.type = NKC_EKEY;
ne.key.code = msg.wParam;
break;
}

TranslateMessage(&msg);
DispatchMessageW(&msg);
nkcHandle->needs_refresh = 1;
}

With that fixed, I made key ENTER acts as if the OK button has been pressed and ESC key acts as if the Cancel button has been pressed.
Now we have to talk about the text box edit.

As I said before, I could not find any example using an edit. So, I've lost a good amount of time trying to make it act like an actual edit.
I was using nk_edit_string function, but things started to work only when I changed it to nk_edit_string_zero_terminated.

Even so, it has a lot of quirks: In the end, I got a half-baked InputBox. I could not even remove the minimize button and the icon from the main window's title bar (although I did not try hard enough.)
Maybe it's me. Maybe it's not.

Anyway, the search for the "perfect" GUI continues.

The C file can be compiled with the following command line (for 64-bit Windows):

gcc inputbox.c -o inputbox.exe -DNKCD=NKC_GDIP -B C:\Progra~2\WI3CF2~1\10\Lib\10.0.18362.0\um\x64 -lgdiplus -lshlwapi -mwindows

I've found the correct argument to -B flag after searching for lgdiplus.lib file in my HD. I suppose it exists in my computer due to it having Visual Studio installed.
Here it is a screen shot of the GUI:

VB-style input box by Nuklear+

And here it is the code:

#define NKC_IMPLEMENTATION
#include "nuklear_cross-master/nuklear_cross.h"

struct my_nkc_app {
struct nkc* nkcHandle;
char value[151];
};

void quit(struct my_nkc_app* app, char* buffer) {
nk_memset(app->value, 0, 151);
int length = nk_strlen(buffer);
nk_memcopy(app->value, buffer, length);
nkc_stop_main_loop(app->nkcHandle);
}

void mainLoop(void* arg){
char * prompt = "What's your name?";
struct nk_color white = nk_rgb(255, 255, 255);
struct nk_color grey = nk_rgb(224, 224, 224);
struct nk_color dark_grey = nk_rgb(192, 192, 192);
struct nk_color yellow = nk_rgb(255, 255, 0);
struct nk_color black = nk_rgb(0, 0, 0);
struct nk_color light_blue = nk_rgb(30, 30, 255);

static char buffer[151] = {"John Doe"};
struct my_nkc_app* app = (struct my_nkc_app*)arg;
struct nk_context* ctx = nkc_get_ctx(app->nkcHandle);
union nkc_event ev = nkc_poll_events(app->nkcHandle);

switch (ev.type) {
case NKC_EWINDOW:
if (ev.window.param == NKC_EQUIT) nkc_stop_main_loop(app->nkcHandle);
break;

case NKC_EKEY:
switch (ev.key.code) {
case 13:
quit(app, buffer);
break;

case 27:
nkc_stop_main_loop(app->nkcHandle);
break;
}

break;
}

if (nk_begin(ctx, "", nk_rect(0, -30, 358, 148), NKC_WIN_FIXED)) {
ctx->style.window.fixed_background.data.color = grey;

nk_style_push_color(ctx, &ctx->style.button.normal.data.color, grey);
nk_style_push_color(ctx, &ctx->style.button.hover.data.color, dark_grey);
nk_style_push_color(ctx, &ctx->style.button.text_background, black);
nk_style_push_color(ctx, &ctx->style.button.text_normal, black);
nk_style_push_color(ctx, &ctx->style.button.text_hover, black);
nk_style_push_color(ctx, &ctx->style.button.text_active, black);

nk_layout_space_begin(ctx, NK_STATIC, 20, INT_MAX);

nk_layout_space_push(ctx, nk_rect(5, 0, 260, 80));
nk_label_wrap(ctx, prompt);
ctx->style.text.color = black;

nk_layout_space_push(ctx, nk_rect(280, 0, 60, 20));

if (nk_button_label(ctx, "OK")) {
quit(app, buffer);
}

nk_layout_space_push(ctx, nk_rect(280, 25, 60, 20));

if (nk_button_label(ctx, "Cancel")) {
nkc_stop_main_loop(app->nkcHandle);
}

nk_layout_space_push(ctx, nk_rect(5, 85, 335, 25));

nk_edit_string_zero_terminated(ctx, NK_EDIT_AUTO_SELECT | NK_EDIT_SELECTABLE | NK_EDIT_CLIPBOARD | NK_EDIT_ALWAYS_INSERT_MODE, buffer, 150, 0);

struct nk_style_edit* edt = &ctx->style.edit;
edt->normal.data.color = white;
edt->hover.data.color = white;
edt->active.data.color = white;
edt->selected_normal = yellow;
edt->selected_hover = yellow;
edt->text_normal = black;
edt->text_hover = black;
edt->text_active = black;

nk_style_pop_color(ctx);
nk_style_pop_color(ctx);
nk_style_pop_color(ctx);
nk_style_pop_color(ctx);
nk_style_pop_color(ctx);
nk_style_pop_color(ctx);
}

nk_end(ctx);
nkc_render(app->nkcHandle, white);
}

int main(){
struct my_nkc_app app;
struct nkc nkcx;
app.nkcHandle = &nkcx;

if (nkc_init(app.nkcHandle, "Example", 358, 118, NKC_WIN_FIXED)) {
nkc_set_main_loop(app.nkcHandle, mainLoop, (void*)&app);
}

nkc_shutdown(app.nkcHandle);
return 0;
}

Andrej Biasic
2020-05-27