Nuklear: A single-header ANSI C GUI library
github.com
github.com
I get some of the reasons that you would initially start out with a header-only implementation, but when your library grows, you probably want to split it at some point. For me personally, that point would be some time before the header reached 25k (!!) lines.
- you can build simple tools contained in a single .c file, and you dont't need a build system for this (e.g. just call "cc tool.c -o tool" instead of mucking around with Makefiles or cmake)
- the library can be configured with macros before including the implementation (e.g. provide your own malloc, assert, etc...), with the implementation in a .c file, these config macros must be provided via command line args, which implies a build system
- you can put the implementations for all header-only libs used in a project into a single .c file instead of compiling the multiple .c implementation files, this might speed up compilation
Single-header libs have some of the advantages that a proper module system would bring to C and C++ (e.g. extremely simple integration into own project and increased compile speed), but without all the complexity of implanting a module system into C or C++.
For #3: the #1 applies here too (although beware for static stuff conflicts) but in practice C code compiles fast enough for this to not be a problem
I can see #2 being an advantage, but TBH i think the case where you both need a custom malloc, assert, etc and not need a build tool where you can pass the configuration macros is kinda rare.
make tool
Et voilà.
(I have no idea whether this will successfully run a C compiler for you on Windows, though. I mostly use it to run Makefiles with phony targets as a cross-platform replacement for shell scripts.)
Why not build a more recent version? This thing is 15 years old!
However I have better things to do than try to figure out how to build GNUstuff myself on Windows.
2) Which version of make? I have detailed instructions at work about which make to use with which makefiles such that all the stars align and the OS vars and other gnu tools line up such that our upstream Makefiles work without modifications (well, usually.)
3) Given the complexity of 2, what I'm actually going to do is automatically invoke make from whatever build system we're using internally.
4) Now that I have two sets of build configuration, I have a continual maintenance burden as conflicting dependencies and incompatible build settings need to be resolved. Since I deal with C++ libs and tools, sneezing too hard will cause incompatible libs. In practice, the Makefile will hardcode CCFLAGs, sometimes CC itself, and things like building with/without RTTI will cause incompatible libs I can't link without more Makefile tweaks.
5) When I get sufficiently fed up with the state of affairs in point 4, I'll integrate the tool into our own build system such that we have a single unified build system again and I don't have to make the same change in 5 places (our codebase + a measly 4 dependencies in this example.)
At this point I'm no longer using make and wondering why I didn't just skip directly to step 5 in the first place. Make is simple - so simple it doesn't address my needs nor solve my problems.
That part was justified by deploying libraries for Windows. Going for one file only was justified by this:
"You don't need to zip or tar the files up, you don't have to remember to attach two files, etc."
--
Unrelated, and probably colored by the fact I first learned to program on Windows, but I don't get the problem. Windows applications usually bundle DLLs with them and keep them locally, unlike Linux applications which typically install dependencies globally through a package manager. I don't think I've ever had a big DLL problem developing on Windows, whereas on Linux I've been occasionally bit by the "oh this software requires X <= v2.1 but you can't have that since something else is already using X v2.3 and that would be downgrade".
If your software needs library foo.so.x while other application needs library foo.so.y, just put both into /usr/lib. Problem solved.
The first versions of dynamic linking on UNIX basically required patching a.out files, before ELF was designed.
So of course the expressetion regarding compatibily issues with dynamic libraries came to be "DLL hell", there weren't .so to talk about.
Why not two files, one a header and one an implementation? The difference between 10 files and 9 files is not a big deal, but the difference between 2 files and 1 file is a big deal. You don't need to zip or tar the files up, you don't have to remember to attach two files, etc.
I'm still not convinced. I am convinced about a .c and .h -- that's how sqlite does it. Going to just a .h seems to provide negligible benefit and confuses the implementation and interface. It probably confuses a lot of tools too, e.g. source navigation tools, code coverage, code instrumentation, etc.
Are we bikeshedding about this?
that's why the keyword "inline" exists.
Normally you have the implementation inside an "#ifdef IMPLEMENTATION" block, and the API interface (public structs and functions) outside of the implementation block, and all private functions inside the implementation block are defined as 'static' so they are not visible outside the special implementation source file. In the places where you include the header for normal use, only the public interface is accessible.
Nuklear was developed as multiple files and hastily merged at the last minute by its author before being tagged 1.0. I think it was a mistake especially as he copied the entire stb_xxx files inside. And unlike libraries like stb_image.h, if you want to use Nuklear you need to setup or copy a non-trivial backend anyway, so it's not a matter of include and ready-to-use.
(Dear ImGui which Nuklear is based on is 7 files including 4 headers as you know.)
To me the core value of those libs is that they are highly portable, designed to compile everywhere without any specific build system, and designed to be compiled from source code. Because they are designed as such, problems (such as error/warnings on some setup) are caught easily and fast.
Whereas for bigger libraries you either get the headache of binaries, either get the headache of figuring out their build systems and building from source which frequently fails. And as people don't frequently build themselves, portability problems aren't caught as often.
But what about preprocessing times? If you're including a library from many of your source files, then even if it always hits the #if 0 case, the preprocessor still has to parse the implementation. It matters for distributed compilation too -- more preprocessed bytes have to be sent over the network.
I'm sure there are cases where this overhead is negligible. But I'm just as sure there are some where it's not. Not caring about how much text is in your headers seems like a bad habit to get into. Build times are the main reason I don't use C and C++ more.
Are you sure your implementation is correct? How does it handle this:
#if 0
"\
#endif \
"
#endif
I'm not saying gcc's and clang's preprocessors are not really fast, but preprocessing is trickier than most people expect. In particular (as you can see from my example), while skipping over an "#if 0" you still have to split the source into tokens and discard the tokens.I don't trust anyone who thinks it's simple without actually having implemented it -- and tested it on real code.
One older thread: https://news.ycombinator.com/item?id=10945552
What's the C preprocessor speed of GCC/Clang/MSVC? Any benches?
$ for file in /usr/include/*.{h,hpp} ; do echo $file | awk '{ print "#include \"" $1 "\"" }' >> /tmp/test.cpp ; done
$ (laborious step consisting in removing headers that don't play nice with others... in the end there are still more than 1425 base headers, which end up including most of Qt4 & Qt5, boost, etc)
$ time g++ -E /tmp/test.cpp -fPIC -std=c++1z -I/usr/include/glib-2.0 -I/usr/include/qt -I/usr/include/glibmm-2.4 -I/usr/include/glib-2.0/glib/ -I/usr/lib/glib-2.0/include -I/usr/include/gdkmm-2.4 -I/usr/include/gdkmm-3.0 -I/usr/include/gtk-3.0 -I/usr/include/pango-1.0 -I/usr/include/cairo -I/usr/include/gdk-pixbuf-2.0 -I/usr/include/atk-1.0 -I/usr/include/qt4/ -I/usr/include/qt/QtCore -I/tmp -I/usr/include/qt/QtWidgets -I/usr/include/qt/QtGui -I/usr/include/KDE -D_FILE_OFFSET_BITS=64 -DPACKAGE=1 -DPACKAGE_VERSION=1 -I /usr/include/raptor2 -w -I/usr/include/rasqal -I/usr/include/lirc/include -Wno-fatal-errors -I/usr/include/lirc -I/usr/include/gegl-0.3 -I/usr/include/libavcodec -I/usr/include/freetype2 -DPCRE2_CODE_UNIT_WIDTH=8 -I/usr/include/python3.6m -I/usr/include/libusb-1.0 > out.txt
1,82s user 0,18s system 99% cpu 2,013 total
out.txt (the whole preprocessed output) is 710kloc and 23 megabytes.With clang:
0,72s user 0,08s system 91% cpu 0,875 total
so I'd say that in general, preprocessing time is fairly negligible. A few template instantiations will take much more time to compile.Also, I like the point made in the sibling comment by moefh. Are you sure you don't need to tokenize?
Can I see your implementation? I have looked at a few implementations of C preprocessors, and they're not simple.
I can see it for C++ which supports placing code in a header as a language, but C requires jumping over awkward preprocessor hoops that can be avoided by using a .c file.
But with .c files, when you have multiplatform project, you need to put it in other "3rd party" tools. Think about the whole zoo: Visual Studio, XCode, Code::Blocks, make, NMake, etc.
No you don't: just act as if you've written the damn thing yourself. Your tools don't have to know. How being multiplatform changes anything?
Also, it shows that building C and C++ is a sad affair.
But libzip is made of .c files. How you would include it in your multiplatform project ? Visual Studio, XCode, etc...
In general it would be great if C has `#include source "some.c"` feature.
So you would add single file zip-lib.c that will contain `#include source ...` references to all needed .c files.
But C/C++ has no modules as other languages so we need palliatives like make files, etc.
It really feels strange that young devs find it so hard to use a linker.
So it appears like a little convenience just for not having to deal with installing binary libraries, at the expense of wasting everyone's time.
The time I waste installing a binary library?
Once per platform for the longevity of the project.
The time I waste building software with header only library?
Every single time I do make all.
For a start there are 4 incompatible standard libraries (dll vs static, debug vs optimized) and that's already a big headache. Also to a mix, a bunch of Visual Studio versions with enough difference to make it frequent that one compiled for e.g. 2010 doesn't work with 2015 or vice-versa.
To me the nature of those small libraries (regardless of the number of source files) is that they are highly portable and designed to always be compiled from source code to prevent those issues.
Though I agree a single header and single .c/cpp file is another good option and solves the compile time issues of throwing everything in the header.
Just for context, in C++ including std::string (after preprocessing) will pull in 25k LoC, std::vector around 20k.
Programming in C++ by the book will average around 35k LoC per every compilation unit. And it will compile significantly slower than C code.
Having that in mind, and considering that Nuklear doesn't pull in any dependencies, not even C stdlib parts. I think author has done a good job at being lean and fairly straightforward.
UI libs are usually a huge dependency and a hairy mess. This is not really the case. It's just one ~1mb 20k line header file.
There is some overhead on preprocessing over having a .c/.h combo, but that's it.
You can put those reimplentations in a different file. You don't have to expose the consumer of the library to them by jamming the whole thing, both interface and implementation, in a single header.
This is like library design by the folks who mistakenly say that what they do with '#include <stdio.h>' is they are "including a library". Eg. Compile and link are the same thing in a lot of people's minds, I think increasingly these days as C expertise is diminishing.
However, having used a few of these "single-header" libraries my main concern is navigating a 9000 sloc header file as opposed to a neatly re-factored version...
An explanation of how undefined behaviour is possible would be welcome.
Short summary: compilers that enable strict aliasing assumptions by default can introduce bugs into otherwise-working code by assuming that a pointer to one type will never refer to a variable of another type. This assumption is perilous but considered worthwhile by many, since it allows the compiler to take advantage of early-out optimizations and CPU pipeline scheduling in ways that would otherwise be unavailable at compile time.
More specifically, compilers may make different decisions about the appropriateness of aliasing optimizations depending on the information they have available. If a function that accepts potentially-aliased pointers exists in its own C file, its visibility to the compiler is limited to what the linker can see. So the compiler may not be as aggressive about its aliasing-safety assumptions as it would be if the function and all of its callers were all present in the same file. Under these conditions, a single-header library can result in "riskier" optimizations that the compiler wouldn't attempt if the same code resided in its own translation unit.
My understanding of Sean's position is that this breaches an implicit contract between the programmer and the compiler of a systems-level language. I agree, and I think the standard should have included a keyword -- or the compiler authors a flag -- to allow people to opt in to these sorts of optimizations rather than requiring them to opt out. It is way too late to change the way C works by default by doing stuff like this.
This is an oversimplification but I think it's what the Musl author is getting at. My guess is that if he knew Sean, he'd be a lot slower to accuse him of being ignorant of any particular aspect of what he's doing. Still, while his dismissal amounts to FUD in the absence of any specific examples of "undefined behavior," there are some good points on both sides of the argument.
My own take, which is unfortunately all too easy to back up with specific historical examples, is that it's inappropriate to do anything that makes C/C++ programming more difficult, more error-prone, or less secure than it already is.
> [...] the standard should have included a keyword -- or the compiler authors a flag -- to allow people to opt in to these sorts of optimizations rather than requiring them to opt out.
As an amateur Linux kernel hacker, I see hacks in the kernel that circumvent compiler bugs and unexpected behavior because of compiler defiance of standards. The rants on lkml seem to assign most of blame to the compiler authors of gcc. Here is one of Linus' (many) denunciations of gcc:
https://lkml.org/lkml/2003/2/26/158
But also Andrew T: https://stackoverflow.com/a/2771041 who claims - if I understand correctly - that strict-aliasing was already part of the C89/C90 standard but that compiler authors didn't implement the standard correctly.
> It is way too late to change the way C works by default by doing stuff like this.
One thing that I am confused about in your explanation is this:
> Under these conditions, a single-header library can result in "riskier" optimizations that the compiler wouldn't attempt if the same code resided in its own translation unit.
How exactly does a compiler generate "riskier" optimizations from a single-header as opposed to separate translation units? I fail to understand how after the pre-processing phase, this would be less safe.
For instance, the optimizer might conclude that it's safe to either elide an inline function call or compile it very differently if it sees that you're referring to the same object in two separate parameters.
If the function is implemented in a separate file, the optimizer has to assume that the function does something it doesn't know about.
1. In some libraries (SQLLite for instance, and libev too I think) the authors have a script that "amalgamates" all sources into a single translation unit.Their reasoning being that a compiler with full-visibility of the source can do global / interprodecural optimization that would not be possible otherwise. Is there any sense in this if it practical to do so for a small to moderate size library?
2. Please tell me what I should read so I can reach the same level of understanding that you have. <not a question>
2. Read a lot of C code written by people like Sean Barrett. You would just pick up a lot of bad habits from mine. :)
Fun rant by the owner of the company Sean Barrett works for: http://web.archive.org/web/20160309163927/http://robertoconc... (and while we're on the subject, additional UB links: https://news.ycombinator.com/item?id=14170585)
As for how you navigate these files, you use a text editor that understands that you're working with code and not just a list of chars. Example post about this from last time Nuklear was discussed: https://news.ycombinator.com/item?id=11532001
Personally, for Visual Studio I use DPack (http://www.usysware.com/dpack/CodeBrowser.aspx); in Xcode, I use its methods dropdown (Ctrl+6); in VSCode, I use its methods dropdown (Ctrl+2); and in Emacs I use a thing I wrote ages ago that grabs the imenu names list and presents it in an ido menu in the minibuffer. And there are other options for other editors. The key thing is just to unshackle yourself from PgUp/PgDn/Find/mouse wheel.
And before you know it, you're reinvented Qt. NTTAWWT, I guess, but if you set out to build a lightweight set of GUI controls for a specific purpose, you probably had a different goal in mind, and that's not the way to accomplish it.
In my own work I frequently include stb libraries in separate .c(pp) files that provide a somewhat higher-level C or C++ interface to the features I need, which are then linked in the traditional way with .obj files and minimal headers. This approach works well, maintaining encapsulation and keeping compile times down while avoiding dragging in a ton of random dependencies.
> Dependency management is a big part of software development today but the “header files” of languages in the C tradition are antithetical to clean dependency analysis—and fast compilation.
That might be a reason to limit the use of header files to single file. In addition, I think that it is meant to make the adoption of the library easier.
[1]: https://en.wikipedia.org/wiki/Ken_Thompson [2]: https://golang.org/doc/faq#Origins
I mean, we've been nicely organising our sources in files, modules and libraries for 30 years, and that's been fine: very easy, logical and giving us many benefits (such as quick partial builds, splitting different operations, isolating problems for a much easier resolution, and so on) but suddenly they show up and it's too difficult for them and they prefer to dump thousands and thousands of lines in a single compilation unit and have all things interfere with each other...
Making this about "young" people who come from "web dev" is flamebait, which predictably takes the discussion into inner circles of hell. That's just what we don't want on HN.
You've done this kind of thing more than once before. That's bad. If you'd (re-)read https://news.ycombinator.com/newsguidelines.html and take the spirit of this site to heart—intellectual curiosity and thoughtful conversation—we'd appreciate it.
1. Why would a web dev suddenly decide to write in such a spartan way an algorithm heavy library in C or C++? This makes no sense except to somehow tie modern web dev failures like left pad with this style of libraries to portray them in negative light...
2. In gamedev neat organization is/was not some golden long standing standard, i.e. in comments under[0] you can find information that with 3D graphics it was customary to use a single file to let the early compilers do a better job since they didn't optimize across translation units and many old codebases have no problem with thousands of lines of spaghetti per file.
3. These libraries are meant to be compiled once ever per project (except when you clean out all your object files) with a define in a single source file (which any modern compiler should handle), otherwise most of their text is thrown away in a single ifdef which should be trivial and speedy on a modern compiler. The resulting header shouldn't be a problem for any modern compiler and is as light or lighter than an entire include tree would be. Most of these libraries are also single purpose and have very small API and C is light compared to template heavy C++ anyway. If you develop such a library you can keep it split up and ship merged files, i.e. SQLite does that. If anything this might speed up the execution (optimization in single TU), compilation (a single TU for entire library) and linking (no dynamic nor static linking but a single object) but to me that tangential compared to convenience itself.
4. I've never seen such libraries to have been badly received - quite the contrary, SQLite might be the most widely deployed piece of software actually and it's recommended consumable form is a single pair of files (plus an ext file, for a total or 3 in the official 'amalgamation').
5. I've personally yet to see a 'young web dev' create such a library. If anything it's the grizzled veterans of gamedev, runtimes, compression, middelware, cross platformness, systems programming, people with university education that includes CS heavy stuff, etc.
Libraries I use or like that use this (single file or single header + source) style are:
- very popular PD libraries by Sean T Barret, thanked in this linked repo's README. He arguably popularized this style, maintains a list of such libraries and has a FAQ and a guide about this style of libraries. He is over 50 (or maybe over 60, he was a teen in the 80s), is a gamedev veteran with software and hardware graphics and runtimes - Thief 1, Iggy (Flash runtime) at RAD, etc. His website is also hardly something that a modern web dev would make[1].
- pugixml by Arseny Kapoulkine - gamedev veteran: PS3, physics, rendering, etc. not a web dev, not young (definitely over 30, unless you happen to redefine young to 'under 40' which makes 0 sense in industry as young as ours but considering your other claims is totally likely).
- SQLite which is shipped and recommended to be used like that (and I think Python embedds it like that) - D. Richard Hipp, Tcl, over 50 now, total veteran of world class between Fossil, Tcl, SQLite, etc., educated and programming since 90s, his personal website is also hardly modern web dev.
- ClipperLib by Angus Johnson, a hardcore clipping 2D library, it has single/two file versions in C#, Delphi and C++ (are these the languages young web devs know well now too?) - another person who was a programme rin the 90s and is from very Enterprise-y background (Delphi Object Pascal, yikes), not a web dev, nor with a web dev worthy web page[3] (I don't mind it, I even find it nice to look at, but it's another hint of the lack of supposed web dev blood in him).
Honorable mentions are the two people related to imgui concepts and also thanked in this linked repo's README:
- Casey Muratori - programmer in the 90s already, over 30 or 40 years old, another (along STB) RAD tools related person, worked on Granny (animation for games) and Bink (video compression for games), total veteran in low level stuff, not a young web dev.
- Omar Cornut - maker of Dear IMGUI, was an intern in the 90s, has a very non web dev website[4].
All in all I'm truly baffled about where you got the 'mostly young people who come from web dev' bit, especially since it's no secret many people are inspired to write them by STB himself (and mention that in their READMEs, as seen here) who is none of that. Your comment seems very hearsay, dishonest disingenuous.
If anything I feel like this is a set up of a Yerevan (communist, post communist, Eastern Europe, Eastern Bloc, [5], etc.) joke I happen to know in Polish (translation mine): "Dear Radio Yerevan, is it true they give out free cars on the Red Square in Moscow?" - "That's mostly true but it's bikes, not cars, they are stolen, not given out, and not in the Red Square in Moscow but in the Prague district of Warsaw".
[0] - http://fabiensanglard.net/duke3d/
[1] - http://nothings.org
[2] - http://www.hwaci.com/drh/
[3] - http://www.angusj.com/delphi/clipper.php
1. young people
2. coming from Javascript
3. coming from the land of IDEs which blur the difference in nature between files, between external and internal files, between copy inside the project and inclusion/linking from outside, etc.
4. who take a try at C, because they have heard C is hardcore or something like that and they want to taste low-level to become real programmers, not just web devs
5. who offer or request single header libraries because they have no idea it could be otherwise, or because they think linking or writing a basic makefile is an issue an enormous amount of work, because they come from the land of language-dependant automatic package managers.
That's somehow the same horrible movement that ends up giving us projects which ship with all the dependencies inside the package. Well, not all dependencies, just almost all, because there will always be a random couple of libraries you'll have to install yourself, without knowing why... And then those 'internalised' libraries will never get patched, never get updated, never get fixed, that's wonderful.
End of story. I do not even know why, along your text wall, you go on fighting things you pretend I have said, when I haven't. I haven't spoken of this library in particular, I haven't spoken of gamedev (I would not touch that with a 10 feet pole), I have spoken of what I witnessed. And that's a true phenomenon that started only 2 or 3 years ago.
> I really do not understand why 'single header' is considered a good thing, but I see this more and more often on libraries. What is the reason all the code is put in the header file?
With:
> I have mostly seen it done by (young) people who come from web dev. They bring their bad habits together with them. And they usually don't get a very warm welcome for that. (...)
And now you ask why I've listed all these libraries to prove otherwise? This is what I'm disproving with this list of old, experienced, non web devs writing good libraries that are well received and reliable.
You can't make a sweeping statement about an entire paradigm of writing libraries and then claim you just happened to ignore all of the facts that contradicted it like existence of SQLite, STB and the entire gamedev industry. Your logic is on the level of saying that "most nuclear power plants go boom" and claiming you just don't care about any except Chernobyl personally.
Where are your examples of these bad libraries made by young web devs that are not well received? Between few examples I gave you should have at least 10 by now but instead you tell me that some people learn C because it's 'hardcore' and that new C programmers struggle with C linking (both of which are obvious facts to any C or C++ programmer).
And there's nothing special with single file libraries about avoiding building or lack of updates, people can (and do) dump entire source trees into their projects to avoid having to build a separate library and it works just as well most of the time. People also do things like ship prebuilt binaries (especially C ones, which have a very stable ABI even on Windows, unlike C++) with their C or C++ project which is just as bad from the update standpoint as embedded source is or it's even worse since it tasks anyone trying to replace these with finding out how to build that library (which between CMake, premake, Scons, even changing compiler defaults - which once bit me when building a project with GCC - and God knows what else might be quite an adventure compared to just dumping the source into the project itself and rebuilding that).
Nothing about C is easy or logical. C only makes sense in 1965 on PDP7. All these hacks just work around the inherent shittyness of C/C++.
It is not funny and yet it is wrong on all accounts.
Do I even need to discuss that C doesn't have libraries???
And a module == a compilation unit.
Never used it myself, but looks interesting considering the current state of go's UI programming options (i.e. fairly limited at the moment)
https://github.com/borodust/bodge-nuklear
They are purpose-built for a particular engine though, so I'm not sure how generally usable they are.
Does that mean that one can detach an input field, save its state somewhere, create a new one and attach it somewhere else again and have it appear exactly as it was before (i.e. cursor position, selection, focus, etc)?
That's one of the pain points with VDOM frameworks (i.e. need to diff around UI components which shouldn't lose state), so I'm curious if this library gets it right.
This is immediate mode UI. It handles inputs and renders simultaneously. Consider a line from the example in the README:
nk_slider_float(&ctx, 0, &value, 1.0f, 0.1f);
That line is responsible for both drawing the slider (at a place determined by value, with min = 0, max = 1, step = 0.1) and setting the variable `value` to whatever position user is currently dragging the slider to.Or:
if (nk_button_label(&ctx, "button")) {
/* event handling */
}
That simultaneously draws the button and executes the conditional if the button was just pressed.Immediate mode GUIs are used in games, where you think in terms of drawing frames, each visible for a fraction of a second. When using such GUI, you usually "clear" it at the beginning of the frame, feed it with input from current frame, then build up the UI at the same time as you're drawing it[0].
So basically, you're in total control of the state; there is no persistent "input field", you recreate it each frame with a function call, and it's up to you whether or not it's meaningfully the same input field as it was the last frame.
--
[0] - most times, like in this case, "drawing" means you "build up" a list of display commands which then you feed to the renderer at the end - this lets an immediate mode GUI be independent of whatever graphics library you're using.
I'm not sure that's the point. I think it's a good idea to decouple input and output (render).
The point of IMGUI vs traditional toolkits is, I think, rather that you don't have to communicate through an API to manipulate and sync state with the framework/library. The latter imposes lots of communication overhead and second guessing, and can't offer the same level of control.
The imgui framework handles the previous frames events when drawing the current frame.
This implies that the only concept you really need to know using an immediate mode GUI framework, is that internally to the framework it needs some mechanism to identify objects between frames.
So why not allocate the necessary structures on the caller-side, and hand them as arguments to update and render calls? I think that's cleaner. It's the way I've started going forward for my own experiments.
I briefly looked into Dear Imgui before, and I was thinking it had quite a few hacks to find the objects from last frame. Lots of best effort guessing and workaround schemes to keep the API calls "concise".
But as far as I can tell, nuklear is designed to be simple to program the implementation, and simple to program against, at the cost of some features.
https://github.com/vurtun/nuklear/issues/50
Dear imgui may have some hacks as you mention to achieve features such as using tab to switch the active component, (I've never looked at the source), but it's still simple as can be to use.
I'm not quite sure what you're advocating to do, but if you make a better graphical user interface framework than those other two projects, then I can't wait to see it.
I've only begun making some UI elements. At the moment I have a keyboard-driven menu, and a slider box and an unused button with misplaced caption that react to mouse clicks. There's a lot more infrastructure I've experimented with and the UI just barely works -- but I'll soon need and work on more 2D UI functionality. One problem is that I'm not sure how to structure 2D rendering, having to deal with modern OpenGL. But I need this experience to inform my UI architecture. I will probably study vurtun a bit.
So if you're interested check back in two weeks (and I'm happy to receive feedback).
https://github.com/jstimpfle/learn-opengl/blob/master/ui.c
https://github.com/jstimpfle/learn-opengl/blob/master/meshvi...
A button being clicked doesn't really have anything to do with whether it's been drawn or not. The relevant information is whether the user has hovered the button with their mouse and (mouse position), and whether the mouse button is pressed (mouse button state), both of which are usually provided to the system on a per-frame basis. I've not used Nuklear, but this is how dear imgui works. If the GUI system knows where you are clicking, and must know the position of the button it's drawing (in order to render it), then it should be able to tell if it's being pressed.
Buttons don't fly around the screen over time. If a user clicked on the screen 1.0/144 seconds ago (assuming a 144 htz monitor), the user likely clicked on the button which is about to be redrawn.
> The imgui framework handles the previous frames events when drawing the current frame.
I'm not terribly familiar with the inner workings of these libraries, though, so I could be wrong. Since you pass in the input state for each frame, it's my understanding that they just process the current frame's "events" during that frame. It would be a little weird if you passed some mouse click event into the system and your Button() call returned true the NEXT frame (I think that's what you're suggesting).
Before I used immediate mode GUIs, I was familiar with using Qt, wx, and gtk. Never did X windows. So I never handled events directly, just indirectly programmed what would happen upon the event occuring.
I had been under the likely incorrect assumption that those frameworks, in order to correctly associate say a mouse click to a button, had a mapping of the previous frame's screen-space coordinates of all widgets, and when a click happened, they associated the click with the previously drawn widget. Because that is what was on the screen when the mouse was clicked.
Immediate mode GUIs baffled me. How could the drawing of a button, which has not yet been flushed to the monitor, return true or false depending upon whether or not it was clicked? The button is not yet on the monitor!!! After thinking about it for 10 minutes, buttons don't fly all over the screen. Whatever the bounding box for the button for this frame is likely the bounding box it had last frame. So the association is easy, assuming the imgui framework can keep track of object identity across frames.
Perhaps, I am just ignorant of how qt, gtk, and wx, actually work.
Is it just about detecting whether an input button is currently down, or is there some way to handle a gesture happening over time?
Help/patches are welcome, as I only have limited free time to implement this.
The lack of callbacks in particular, means that I do not need to worry about memory management very much. Or, I should say, I don't have to defer memory management to later.
I own books on wxwidgets and on qt. To be honest, although I've read them fully, I still don't quite know how to use them well, nor how would I teach teammates how to even if I did.
Using a tool like dearimgui or nuklear, I have no books, but 95% of the time I can just copy example code from the demo into my app, modify it slightly for my needs, and call it a day.
These accessibility APIs expose a tree of UI objects, which the client (e.g. screen reader) can freely navigate and query. This basically assumes that there's a tree of UI objects in memory, which is the case for all mainstream toolkits as far as I know. But that's not the case for an immediate-mode toolkit. At least with Nuklear, the content and state of the UI (e.g. the label and current checked state of a checkbox) aren't stored anywhere in the toolkit's data structures. So I guess applications would have to play a very active role in implementing accessibility APIs, much more than they would with, say, Qt or even Win32.
This is also the reason why they are a pain in the butt to use, at least from the perspective of an IMGUI advocate. Those people usually have a game development background, so they're used writing bespoke solutions for everything.
Statelessness is a strength but also a big weakness, I doubt IMGUI will ever catch on its pure form. However, tools like React provide a fairly similar experience and they do produce object trees usable for screen readers. The missing piece here is something like a lightweight DOM for C/C++ projects.
Accessibility for games is definitely a much lower priority than accessibility for applications that are used in business, education, and everyday communication/social networking. Still, a blind person can feel excluded if their friends are playing a game that they can't play. Sometimes a given game can't be made accessible without changing the whole game mechanic. But that's not always the case. So, something to consider.
Again, thanks for reminding everybody. People need to think about it. Your advice could be toned down a bit to “please think about your audience, if you expect people with disabilities to be part of it think twice when making your own UI toolkit, or at least factor in the costs to build in accessibility features.”
Can you explain in more detail? As a person ignorant of these types of issues, I'm having a hard time understanding the connection between the lack of accessibility in an app to the firing of a blind employee.
Here's what happened as I remember it. The blind employee, Darrell, was providing technical support for a client company on behalf of his employer. At some point after he had taken on this role, the client added a requirement that anyone who worked with them had to use the Seibel CRM system in "high interactivity" mode. If I recall correctly from talking to Darrell, the high interactivity version of Seibel at this time (2006) used the Microsoft JVM. That, combined with the fact that the app implemented some custom controls with no regard for accessibility, made it completely unusable to him. So his employer laid him off. Luckily for him, they rehired him shortly after that, but only because they found a whole new role for him. One can question whether the real problem was the app developer, the client, or the employer. But the inaccessibility of the app was a critical factor.
If this application is important to people in general, it wouldn't stop being important just because you happen to be blind or otherwise disabled. If everyone uses a particular walled-garden service for some daily task, blind people need to use it too, just the same as everyone else.
I was talking about app developers that build applications for jobs that require sight because of the nature of the job. Say a medical diagnostic application or airplane (simulator). You have to develop for the color blind, but for the completely blind, not so much. When selecting an UI toolkit I do not have to put that requirement up top.
For all the others, I wonder if a command line interface wouldn't just be better—sometimes not just for the blind.
I’m the last to say blind people are an afterthought which is why I thanked the original commenter for his reminder. I merely want to bring some nuance that there are always exceptions.
But we have to deal with reality here. Some tasks simply require normal sight. Believe me, as a legally blind person myself, I wish I could drive, but I understand why I can't and accept it. The same applies for some software applications. So all I can ask is that developers pay attention to accessibility unless there's some reason why it just won't work for their application. I think that's what your interlocutor is getting at as well.
In my ideal world, all user interfaces would run on the user's main portable computer (currently a smartphone), controlling devices remotely over Bluetooth, NFC, or the like. Embedded UIs would then be a thing of the past, and accessibility would simply be a matter of following the recommended practices for mainstream general-purpose platforms.
However, I must admit that the incidents where UC Berkeley and other universities removed public access to many thousands of hours of videos of online undergraduate lectures severely reduced the extent of my sympathy for accessibility advocates.
It sounds like a no-brainer to be sympathetic to the needs of disabled minorities, but in fact I would suggest that if the community of accessibility advocates wants to be successful in their aims, then they need to absolutely avoid being associated with the intellectual vandalism we saw at UC Berkeley and elsewhere.
I'm sorry but I disagree strongly with your blind friend. E.g.
> this continuing trope of "OMG I didn't realize we needed to make our content accessible! Woe, woe are we!" passed through the realm of disingenuous and into that of pure bullshit a long, long time ago.
They are just videos of a lecture. They can be put on the internet.
EDIT: That sounds like I don't understand his point. I think I do: we've had decades to get used to the idea that content should/must be accessible, and of all institutions, a large public university should certainly be doing that now, in 2017.
However, I just can't make myself accept it. They are just videos of a lecture. They can be put on the internet. Certainly they would be better if accessible, but once up it is vandalism to take them down.
I'll argue that it's stupid that the law requires Berkeley to make its content accessible. If accessibility is something society has determined is worthwhile (I think we can all agree that it is), then it should be funded by society. Unfunded mandates are no way to run society.
Now if someone can make technology to make that accessible, post-hoc, awesome. And if someone can make it easy for the content to be born accessible, even better! But I do not agree with making accessibility required.
And not just accessibility for blind people, of course. And here I have to be honest and admit that I haven't lived up to this ideal myself. Several months ago, I gave a presentation to a local Meetup group for software developers on making software accessible to blind people. It's on YouTube. But I haven't yet had it captioned so a deaf person can benefit from it. Nobody's coming after me with a lawsuit in hand. But now that I've thought of this, I should get that video captioned.
Or maybe the presentation or lecture itself is an archaic way to share information. You can see it in the word "lecture". It comes from reading, as in reading out loud. But the people who attend today's lectures can read for themselves. Maybe the best way for content to be born accessible is just to stick to electronic text, that everyone can read in their own way and at their own pace.
How about writing a command line version of the program? Wouldn't that be much more accessible? I'm not even thinking ncurses, I'm thinking about a REPL, or even non-interactive like grep or awk.
I suspected as much, which is why I was thinking purely REPL/batch stuff. On the REPL end, I was thinking programming languages (Python, Lua, Lisp, OCaml…), and stuff like gdb. On the batch end, I was thinking utilities like grep or awk, compilers, or ImageMagick. In other words, stuff that would work well with a printer.
I personally have a concrete use case in mind: file encryption, and password manager. I intend to write a first version for the command line, because it will be smaller and easier to write. Then I may do GUI versions. I was wondering to what extent the accessibility of the GUI version even matters, considering how simple the command line version would be.
I have not had a chance to try it out for myself yet, but I have recently learned that webkit includes a direct-to-opengl renderer such that one can skip the Qt or GTK backend and write HTML/CSS/JS that outputs to the framebuffer directly.
- AssortedWidgets: https://shi-yan.github.io/AssortedWidgets/
- Dear ImGUI: https://pbrfrat.com/post/imgui_in_browser.html
Also, what would the performance be like as compared to native OS GUI controls?
Hopefully this does not come across as rude, but from the readme
"It was designed as a simple embeddable user interface for application and does not have any dependencies, a default renderbackend or OS window and input handling but instead provides a very modular library approach by using simple input state for input and draw commands describing primitive shapes as output. So instead of providing a layered library that tries to abstract over a number of platform and render backends it only focuses on the actual UI."
Under the demo folder, there are examples.
Nuklear works with opengl 2 and 3 both with sdl2 and glfw (both of which support windows) it works with direct 3d 9 and 11, with gdi (windows specific libraries)
So I cloned the repository and opened up nuklear.h. If this library supports Windows, then CERTAINLY this file has to include "Windows.h", somewhere, right? Uhm.. no, it does not. Bad sign, I thought.
So, still confused, I thought "so let's look into this folder called "example". I look into canvas.c and I notice "#include <GL/glew.h>". So I thought "Oh dear, this is gonna be a bother to compile on Windows, forget it". As luck would have it, I never looked into the folder called "demo".
So at that point I honestly believed that this library was only sort of "semi-supported" on Windows, as often happens with these kind of projects. You know, the usual "if you're a bit of a masochist and you're willing to muck around with mingw and download glib.tar.gz stuff, you may get this to work". The fact that the GUI has a definite linux look reinforced that impression.
What I'm saying is, all my confusion could have been solved by just one line in the readme explicitely stating "this builds on Microsoft Windows using Visual Studio and a direct X backend, etc...". A little bit of redundancy, it helps sometimes.
It doesn't reference any external libraries (maybe C runtime). IIRC it does not assume you have a malloc() function by default; you provide one or use a #define before include to tell the header it can rely on standard malloc.