Koui | User interface framework for Armory and Kha

Koui 2020.10 was released today! :partying_face:


Whats new?

  • Support for gradients, drop shadows and rounded corners. Koui now has its own painter classes with custom shaders
  • Massive theming improvements:
    • It is now possible to style indiviual sub-elements (e.g. a slider background and the button in the foreground) differently
    • Added “states” to theme definitions: Elements can now have different styles based on their state (_button!hover e.g.)
    • Support for global variables in the theme file (colors, etc.)
    • When adding small tweaks to existing themes it is no longer required to copy the entire theme. Instead you can now append another theme on top of an already existing one (all that happens during compile time!). However, you can still overwrite the entire default theme with the KOUI_THEME define, just as before.
    • New data type Asset for theme files that will automatically load the required assets when Koui initializes
  • Show more debug information to profile graphics performance when KOUI_DEBUG_DRAWINGTIME is set, you can now access the number of draw calls and the average drawing buffer size for Koui’s new painter (Kha’s g2/g4 calls are not taken into account)
  • Layouts that use anchor positions now have a default anchor. Added elements will use that anchor when element.anchor == Anchor.FollowLayout
  • Progressbars now can output their value as a percentage when using Haxe templates (::percentage::)
  • Faster window resize handling
  • Much better theme handling of disabled elements
  • Many bug fixes and smaller improvements
  • More unit tests to improve stability

Just to make sure: there are always smaller updates that are not part of those big updates with their own version number. Only big breaking changes get their own version number.


Example project:

Because the documentation is not yet updated, I decided to upload one of my small example projects that I use for debugging (this one doesn’t use Armory, it’s pure Kha). It has no functionality other than showing a few elements as you can see in the screenshot above. It started as a game options menu but now it’s just something weird^^

To run it, open the extracted zip folder in VSCode/Kode Studio and hit F5 (you might have to choose Electron from a dropdown, but it should work on any other target as well).

Download demo project (this forum doesn’t allow .zip uploads): https://www.dropbox.com/s/c6dll34p49c0doy/KouiDemo.zip?dl=1

Have fun :slight_smile:

4 Likes

@timodriaan, super!

Have a question: are all languages supported?

If Kha supports this, likely yes. It will of course depend on the font. The default font can be found here, if you scroll a bit down you can see the supported languages.

It is defined in the theme file here and is automatically loaded when the font file exists in the Assets directory,

1 Like

When I run KouiDemo I get:
kha/graphics4/PipelineState.hx: 96: Warning: Uniform projectionMatrix not found.

This font does not support my language. I tried another one (Alice), I still had to download it as a resource in the khafile.js file.
As a result, instead of text, just spaces. So Kha doesn’t support. Let’s figure it out :frowning_face:

You can totally ignore that warning, it’s some internal Kha stuff as far as I know. Armory had it too for a long time, I will do some research to find out how to get rid of it :slight_smile:

Edit: I found the problem: the matrix isn’t used and even if I add this to the shader, it will be optimized away from the compiler resulting in this warning. It’s probably something that needs to be either fixed in Kha or I have to use it (which I might do in the future, there are plans for it)

I still had to download it as a resource in the khafile.js file.

Yes, and there is no way around that I think. Koui just loads the font when you refer to it in the theme file. You don’t need to call kha.Assets.loadFont() anymore.

As a result, instead of text, just spaces

That’s not good. You use a .ttf font, right? Armorpaint has support for different types of character sets, it supports chinese fonts for example, maybe we can look how it’s done there. If you can make sure that it’s not a problem with your setup I will try to find out how to load non-latin fonts as well.

I’m using a .ttf font (Alice). I downloaded it and one field contains the text in Russian “Тест”. The result is gaps.
There have been reports from Armorpaint that the user cannot use the sai language (el). So I’m not sure.

Then you might be right and it’s Kha that’s missing support for those characters which would be sad. Maybe Lubos will push some commits to one of the Kode repos implementing this if there is an open issue. It also doesn’t help to use unicode escape strings so it isn’t a Haxe issue.

While testing the russian string I found a bug in Armory when parsing the trait file for properties to show in the Blender UI:

location: <unknown location>:-1
Error: Traceback (most recent call last):
  File "A:\Workspace\Documents\Projekte\Forks\ArmorySDKDEV//armory/blender\arm\props_ui.py", line 566, in execute
    make.play()
  File "A:\Workspace\Documents\Projekte\Forks\ArmorySDKDEV//armory/blender\arm\make.py", line 501, in play
    arm.utils.fetch_script_props(fn) # Trait props
  File "A:\Workspace\Documents\Projekte\Forks\ArmorySDKDEV//armory/blender\arm\utils.py", line 297, in fetch_script_props
    lines = f.read().splitlines()
  File "A:\Programs\Blender\Blender 2.83\2.83\python\lib\encodings\cp1252.py", line 23, in decode
    return codecs.charmap_decode(input,self.errors,decoding_table)[0]
UnicodeDecodeError: 'charmap' codec can't decode byte 0x81 in position 914: character maps to <undefined>

I will try to fix it tomorrow :slight_smile:



@ Everyone: I pushed another commit that improves how theme files can be overridden. Now, when you override a theme ID only those properties that are explicitly set are overriden, not the entire group of properties. This makes it possible to change some theme IDs in the overriding file without the need to create custom ones.

Lets look at the following example situation where the original theme file has an entry like this:

buttonA > _root:
	font:
		size: 18
	color:
		text: #ffffff

// buttonB has all properties from buttonA and overrides the text color
buttonB > buttonA:
	color:
		text: #000000

Lets create another theme file that will be applied on top of the theme file above with the following content:

buttonB > buttonA:
	font:
		size: 24

Previously, this would result in the buttonB having a white color (#ffffff) and a font size of 24px because buttonB from the original theme file would be completely overriden. Now, buttonB has a black color (#000000) and a font size of 24 as one would expect.

This makes it very easy for example to just set another default font without having to copy all contents from the _root ID.

3 Likes

Ok I got it working:

Button with russian textt

Kha loads only certain font glyphs but you can set the character range with kha.graphics2.Graphics.fontGlyphs. Now the question is how the API for this should look like (or should that be a thing each user has to do?).

There could be preconfigured glyph ranges for some languages that could be switched on or off (so that multiple laguages are possible at the same time) but there will always be cases where that preconfigured setup is wrong. Should it be possible to set a custom range then? Or even a custom range per locale? What do you think?

Maybe like this?

// or better setFontGlyphs()? That would be more correct maybe
FontUtil.setLocale("ru");
FontUtil.addLocale("en");
FontUtil.removeLocale("en");
FontUtil.setGlyphRange([for (i in 32...383) i]);
FontUtil.addGlyphRange([for (i in 1024...1119) i]);

// This allows to load in translation files and automatically register all glyphs.
// Of course this is not efficient for dynamic user input in text fields
FontUtil.addGlyphRangeFromString("äöüß");

Fixes for Armory are coming later today btw :slight_smile:

1 Like

Thank you so much :smiley: :+1:t2:

The default settings should be as general as possible.
But the main thing is that in case of a problem, the user will receive an answer: not just does not show the font, but a warning to the console/log, if possible. To immediately understand the problem. I think so.

Own range - I think it’s necessary, the user can use different fonts.
It is also necessary to apply the range to the locale, again for flexibility - another language, another font, different settings.
Maybe:

    FontUtil.addLocale("ru", [for (i in 32...383) i]); // if the second parameter is not specified, the default is
    FontUtil.addLocale("en", [for (i in 32...383) i]);
    FontUtil.setActiveLocale("ru"); // and the last one added will automatically become active
    FontUtil.getActiveLocale();
    FontUtil.setGlyphRange("ru", [for (i in 32...383) i]);
    FontUtil.addGlyphRange("ru", [for (i in 1024...1119) i]);
    FontUtil.removeLocale("en");
1 Like

Implemented: https://koui.gitlab.io/api/koui/utils/FontUtil.html

In your case, import koui.utils.FontUtil and call FontUtil.loadGlyphsFromLocale("ru") in the initialization callback and everything should work in the latest Git version (a little warning: I might move some of the utils classes to other places in the future).

I changed the API again to better adhere to the naming conventions (glyphs/codepoints etc.), maybe it will get some changes in the future if it isn’t as helpful as it should. For now it works and you can load/unload character sets which is helpful if there is a language setting in the game options menu for example.

3 Likes

Is it possible in KOUI to animate parameters
like alpha/transparency, color and scale?

Not yet, altough I recently implemented a few things that will help with animation support and that is definitely planned. Do you need animations that are triggered by events/in the code or should they be automated via the theme? The latter will take some time until getting implemented because it requires changes to the theme system.

3 Likes

Do you need animations that are triggered by events/in the code or should they be automated via the theme

Theme animation would be already quite nice.
Calling UI animation via event-triggers is obviously a beauty to have
but at least to have something happening in the UI would be very helpful.

2 Likes

So when doing this:

package arm;

import koui.Koui;
import koui.elements.*;
import koui.events.EventHandler;
import koui.events.Events;

class KouiTest extends iron.Trait {
	public function new() {
		super();

		notifyOnInit(function() {
			Koui.init(() -> {
                var button = new Button("Click me!");
                button.setPosition(400, 180);
			});
		});
	}
}

I get a completely red screen and the output is this:

 Trace: TypeError: Cannot read property 'length' of null
        at Function.koui_effects_Effect.initAll (<anonymous>:48440:17)
        at <anonymous>:48314:23
        at onLoaded (<anonymous>:23404:4)
        at kha_Assets.loadFont.fileName (<anonymous>:23376:5)
        at <anonymous>:23477:3
        at <anonymous>:24374:3
        at Function.kha_LoaderImpl.loadBlobFromDescription (<anonymous>:24370:2)
        at Function.kha_LoaderImpl.loadFontFromDescription (<anonymous>:24373:17)
        at Function.kha_Assets.loadFont (<anonymous>:23475:17)
        at loadFunc (<anonymous>:23375:15)

Any ideas?
(Blender 2.83, A3D latest official build.)

The whole testproject is here, I’d like to know if this works on your end.

1 Like

Hi, thanks for the report. I accidentally forgot to include a line in one of the last commits. Should be fixed now in the latest master branch :slight_smile:

Please note that you have to add the button to a layout before you can actually see it. If you want to use the default base layout, just write Koui.add(button) and everything should work.

6 Likes

Awesome! Yeah, I just went step by step to see where it breaks.
And so that the code is shorter for you to see. :wink:

4 Likes

So I am starting to customize stuff now.
When I understand .ksn correctly, (which I probably don’t :slight_smile: )
_button > _root: takes its settings from _root.
So changes to _root affects button. Right?

Now I can see changes made to, let’s say cornerRadius , however
I can’t figure out how to change the button color and size.
(for example: _root: bg: #118a8e does nothing. _root: size: width: 350 does nothing as well.)

Also changing _root!hover: border: color: @globals.COLOR_PRIMARY_LIGHTER
does nothing…

So I think I am doing something wrong here. Any hints?

1 Like

Yes, it should work like that. Do you have caching enabled in Armory? I recently found out that Koui only seem to re-parse the theme file for clean builds, so if you change something in the ksn you have to clear the build in Armory. I still have to find out why that happens but that could be the cause why the changes didn’t update for you. (Edit: probably Koui parses the file everytime but it parses the wrong, cached file)

One thing to know for ksn files (it’s not yet documented) is that if you override a property from a “parent element” that also is included in other states in the parent like !hover, you’ll sometimes have to reimplement the states on the child element as well. It’s ugly and I plan to someday improve this, it’s still not where I want it to be. The whole ksn thing is just a workaround to have more flexibility with styles which you wouldn’t have with json for example (it used json before). Unfortunately, including an own language usually is a lot of work and introduces more bugs than features but in this case I think it’s worth to have. Let me know of your experience with it :slight_smile:

2 Likes

Alright I got the size working. Yeah, it was a cache problem.
But I can’t change the darn button color… :smile_cat:

I get ThemeParserError: Referenced group does not exist! errors
when trying to override stuff on button.

Can you do me favour, take a look at the .ksn and change the button color for me?
Then I can see how it is done…

And another thing: I tested the Nodes now, can’t get 'em to work.

This is the error:

Uniform projectionMatrix not found.
Trace: TypeError: anchorPane.add is not a function
    at armory_logicnode_KouiAnchorPaneAddElementNode.run (<anonymous>:730:14)
    at armory_logicnode_KouiNewElementNode.runOutput (<anonymous>:639:8)
    at armory_logicnode_KouiNewElementNode.run (<anonymous>:880:8)
    at armory_logicnode_KouiInitNode.runOutput (<anonymous>:639:8)
    at <anonymous>:745:11
    at <anonymous>:46972:3
    at onLoaded (<anonymous>:22056:4)
    at kha_Assets.loadFont.fileName (<anonymous>:22028:5)
    at <anonymous>:22129:3
    at <anonymous>:23026:3

And this is how it looks:

(EDIT: I forgot to connect the AnchorPane on the pic, but I did that afterwards. It still doesn’t work (gives red screen) and it still spits the same error. From code all is fine. So I am not sure if I connected them wrong or if the nodes themselves have a problem.)