MIT License
We use hg-git plugin to interoperate between Mercurial & Git.
It's simple: I prefer Mercurial over Git a lot, but I recognize Github's tools are superior (community, ticket system, pull requests)-
Any Pull Request or Ticket must be submitted to Github repo and must not break hg-git interoperability (you have to do very weird git branch stuff to break it).
Originally this project was called "Crystal Gui", however googling around revealed this name was already taken by something completely unrelated. To prevent clashing, it was thus renamed to Colibri Gui, which doesn't seem to clash with anything at the time of the rebrand.
This repo contains subrepos. Thus to clone it you'll need:
git clone --recurse-submodules --shallow-submodules https://github.com/darksylinc/colibrigui/
If you've already cloned then do:
git clone https://github.com/darksylinc/colibrigui/
git submodule update --init --recursive
If you're using hg-git, then make sure your hgrc file contains these lines:
[subrepos]
allowed = True
git:allowed = True
You'll need:
sudo apt install doxygen
Create the CMake script and type: ninja doxygen
We stream font files from disk (rather than load them directly on memory) because CJK font files are huge.
However random AAsset_seek
is very slow for compressed files.
To fix this problem disable compression for font files. In your build.gradle file add:
aaptOptions {
noCompress 'ttf'
}
See Android Integration page
I've always wanted a GUI meant for video games that would fit the following criteria:
None of the libraries I knew or tried met all of the criteria, so I decided to write one
Aside from international support, which is very important for me (Colibri supports arabic and hebrew RTL layouts, RTL text, chinese and japanese top-to-down-right-to-left text, and of course... CJK font support), with Colibri I wrote the shader with performance in mind and a layout engine which works very similar to wxWidgets.
In fact I can design my layouts in wxFormBuilder and then write them on Colibri's.
There were other reasons (one of my clients is VERY picky about UI alignment so the layout engine solved me a lot of arguments with him. I wrote the layout engine with him in mind).
Regarding performance: Aside that we try to use as fewer draw calls and state changes as possible using modern rendering practices, we have a breadth-first mode to render efficiently a common UI pattern in games:
In games it's quite common to render a button, then a child image, then a button, another child image, and so on. Imagine an inventory menu.
Typically the parent button is always the same, but the child is different (uses different picture, material, or may be text which requires a different shader). An UV atlas helps at this, but in my experience it doesn't help too much in real world cases.
Breadth first potentially sacrifices drawing correctness for speed, as it will draw all parents first (e.g. all buttons first), then all children (first the images, then the text or vice versa). This lets us draw everything in roughly 3 draw calls (1 for the buttons, 1 for the icons assuming they share similar materials and the same texture array, and 1 for the text.).
This works fine as long as the widgets rendered in breadth first are not overlapping.
I had a bone with most Text rendering solutions: Most solutions lack sharpness (due to poor use of font atlases), and also lack rich text features like bolding, italic, colouring, adding a shadow, adding an outline (ok Colibri doesn't support outline either). In many cases you can do colour and shadow by hand using multiple text elements (for shadows, create another text in black behind and displace it). But Colibri lets you support all of this with just one ColibriLabel.
There's so many games with ilegible text out there. And then when customers complain they can't increase the font size because it breaks the whole UI. In Colibri that (mostly) doesn't happen thanks to the layout engine, which automatically resizes everything. The function "sizeToFit" from Labels have been inspired by Apple's UILabel. In fact ColibriLabel was inspired by UILabel. It's such a good class.
One more element I needed is that I planned to use Colibri in a threaded environment (render-split model to be exact. Logic thread needs to communicate with render thread about UI state, render thread manages and owns the UI). It gets very hard to do that right with a 3rd party library that sneakily decided to use a global variable, TLS, or something else that would break in this environment. Writing it myself ensures it will work.
Last but not least, I like pretty and glossy skins, and most UIs don't look pretty (IMO) out of the box. This is purely on the artistic side. Windows 10 feels like a massive step backwards from Windows 7 in that regard.
While that is intended use, you don't have to. Any multimedia equipment that requires basic navigation with a remote control could be suited for this. For example my old TV has a built-in file explorer for opening movies and pictures in an USB drive.
This interface could use Colibri, as it was developed to be very fast and lightweight displaying lots of big buttons with thumbnails on it and a text description.
Pango is LGPL-licensed software, which means it cannot be used in closed platforms such as consoles or the App Store (iOS)
While we'd love to see these platforms opening up, the reality is that is very likely not gonna happen and our core target market uses these platforms.
Colibri targets C++11 as minimum, but tries to keep "modern C++" to a minimum.
We mostly use override
and final
keywords.
No. Some C and hardcore C++ programmers may be in shock that we use the STL. We try to minimize STL usage to the basics:
GUI libraries are difficult to write, hence we use the STL for common operations to speed up development.
We do not do dumb stuff like creating a temporary std::vector on the stack on functions that are called every frame, as this would lead to allocs and deallocs every frame. If such container is needed, we keep it persistently so that its memory can be reused.
If a container or snippet of code is allocating and deallocating memory every frame this is a bug and feel free to report it.
I'm afraid we do not provide custom allocators.
I'm also afraid that each Widget/Control created is done via new, i.e.
Colibri::Button *retVal = new Colibri::Button( m_colibriManager );
This means each widget may not be contiguous. We may fix this in the future and provide custom allocators for handling widgets, because after all, all widgets are created either via:
So adding a custom allocator to handle the widgets & glyph rendering should be rather easy.
Note however the widgets inside may allocate further memory e.g. via std::vector living inside of them.
Text management is probably the biggest offender in terms of memory usage. In extreme cases we need to convert the std::string (UTF8) into an icu::UnicodeString (UTF16).
The more widgets with text (or worst, editable text) the worst it gets. If this text changes every frame, then it's possible you end up with memory allocations every frame.
Colibri Gui not rely on C++ exceptions. However our Ogre3D backend does, and currently that's the only backend.
Aside from stl library, we only rely on templates when it makes sense to do so, and that means almost never.
There are several known ways to render text:
For more information see Lengyel's GPU Font Rendering Current State of the Art
Colibri uses the last one i.e. a dynamic atlas. The difference with most implementations is that they often use a 2D texture. This is troublesome because unless all glyphs have the same width and height (spoiler alert: they don't) you run into the "sprite packing" problem of packing all glyphs as tightly as possible.
This is a hard problem i.e. we either spend considerable time analyzing the optimal way to place all the glyphs in the 2D atlas, or we use simple algorithms that will waste a lot of RAM.
Colibri does not do that. We use a 1D buffer and store all glyphs contiguously. This turns a 2D fragmentation problem into a 1D problem, which is much easier to handle and resembles regular memory fragmentation which is well understood.
The glyph is then fetch from the pixel shader using the following code:
//GLSL
#define OGRE_BufferFetch1( buffer, intLocation ) texelFetch( buffer, intLocation ).x
//HLSL
#define OGRE_BufferFetch1( buffer, intLocation ) buffer.Load( intLocation ).x
//Metal
#define OGRE_BufferFetch1( buffer, intLocation ) buffer[(intLocation)]
glyphCol = OGRE_BufferFetch1( glyphAtlas, int( inPs.glyphOffsetStart +
uint(floor(inPs.uvText.y) *
float(inPs.pixelsPerRow) +
floor(inPs.uvText.x)) ) );
This prevents HW bilinear filtering, but we do not care because we render sharp pixel-perfect fonts for the given font size / DPI selected, that means for example our dynamic atlas may contain the letter 'e' twice, one with font size 16 another with font size 20.
Slug is not free.
I was tempted by the extreme simplicity of stb_truetype over FreeType (largely on disk size of both code and compiled library) but I was very limited in time and did not want to spend time analyzing too much.
A quick google around showed that:
Admitedly these are all preconceptions rather than hard evidence (except the performance benchmark, however it may be outdated), but I did not want to spend considerable time comparing the two, and chosing stb_truetype was too risky if it ended up not suitable for my needs.
Unicode is hard. But it's harder than it should be because there's a lot of misconceptions and misinformation. To make it even harder, the technical reports from unicode.org are far from user friendly.
This mini-intro is aimed at preventing you from making the common mistakes.
Asuming we're dealing with UTF-8, Unicode has several things you need to take in mind:
So what really counts as a "character" or "letter" is more accurately a grapheme cluster, not a codepoint. We store the beginning of the cluster (i.e. the first codepoint) in a string in ShapedGlyph::clusterStart
Please note that:
More information: