Wednesday, July 30, 2014

Creating A GUI in Inkscape

Long time no post eh? Busy busy times. The roof of my house is naked and I'm trying to get it covered up, so not much free time. But I was trying this while waiting for a build at work. I still owe you dedicated reader(s) a discussion on some of the production techniques on my new song, but it will be a while.

Anyhow, I fiddled for a year or more with creating QT widgets that display svg drawings of primitives I draw in inkscape. I was trying to do this for rakarrack and at the time it was the best way I could reconcile my infantile GUI knowledge with my infantile art skills (inkscape is manageable for me. You just draw shapes. Its geometry!). I had moderate success, the widgets are functional, (and can be found in my infamous repository) but lacking some significant features, namely, due to QT, no floating point representation in dials. Also I have to manually create a file to dictate what primitive files are used and where they are placed in the window. The svg files are loaded at run time after this descriptor file is parsed allowing for anyone to customize the look, which is kinda cool, but not really necessary. In the end, it was over complicated, and I didn't really like working on it (why I haven't released a single program with a fancy stylized gui yet).

Enter AVTK.

Wait. AVTK is just a set of NTK widgets that get drawn with cairo; NTK in turn is an FLTK fork that renders the FL widget drawing with cairo. And cairo doesn't have tools for drawing svg content anyway. So how will that help? Somehow I need to draw svg files!

Enter svg2cairo.

Oh, ok. That makes sense.

I'm losing my runtime loading/skinning ability, but I think the ease of programming will make it so I ACTUALLY release something with a GUI. Its something I've wanted to have done for quite some time, without actually doing it.

Anyhow, we'll talk about specific implementations later, for now lets have an almost tutorial on how to do it.

I assume you already have drawn an svg that you want to use so first lets install the tools (on ubuntu) **WARNING: if you aren't careful, you could make it so that the xserver doesn't start on your system. It happened to me. Its fixable, but a pain. If you follow these instructions in this order you will be ok:

sudo apt-get install librsvg2-dev
git clone ntk-source
cd ntk-source
./waf configure
sudo ./waf install

Cairo unfortunately has to be built with a special flag for the converter to work, so download the source of a release:
mkdir cairo
cd cairo
tar xf cairo-1.12.16.tar.xz
cd cairo-1.12.16
./configure --enable-xml=yes

sudo make install

IMPORTANT UPDATE: I found that installing NTK from source always breaks my X setup so that I can't login with the display manager (read: bad). The easiest workaround is after installing (before rebooting!) run the commands:
sudo apt-get install --reinstall libcairomm-1.0-1
rm ~/.Xauthority

This doesn't replace what you just installed, it just reconfigures some stuff or something. If you can explain it to me please do. I suppose an easier workaround is just use NTK from the kxstudio ppas.

I then had to make sure building the program looks at this local library rather than the one installed from the distribution (don't copy over the distro version, it breaks a lot of stuff). Run the command:
export LD_LIBRARY_PATH=/usr/local/lib

Once you're done converting you'll want to set the LD_LIBRARY_PATH back to usr/lib or it will break some stuff.
Now you can download and install svg2cairo
git clone
cd svg2cairo

After all that work I'm hungry.

Enter lunch.
Exit lunch.

Now lets convert an svg. For the first example here's a vintage dial I drew:


If you view the source of this page and copy the svg code into a file, you can repeat this almost-tutorial yourself. Now lets convert it:
./svg2cairoxml vintagedial.svg vintagedial.xml
lua cairoxml2cairo.lua -f c vintagedial.xml draw_vintagedial.c

This creates a c source file that has 3 functions:
cairo_code_draw_vintagedial_render(cairo_t * cr);

It always names the functions to match the name of the file. You can test it out and see the result by editing the test-c.c file to include the generated c (which is terrible code style, never include a .c) and function names then compiling and running it:
sed -i s/<name of the file>/draw_vintagedial/ test-c.c
gcc test-c.c -I /usr/include/cairo -L /usr/lib -lcairo

This generates a nice PNG:

 I'm impressed. Theoretically you can convert to some other formats that might make previewing easier, but the conversion from cairo xml throws more errors (warnings?). In the end I really just want a c function anyway. One major setback is that there is no gaussian blur so make sure you take any blurs out of the svg before converting. This makes highlights and shadows more difficult. They sometimes can be done with gradients, but only if you have a very simple shape. But blur in the file makes the generated c uncompilable.

If you are following along it may not have worked. Make sure your LD_LIBRARY_PATH variable is set correctly as mentioned above. With all these details I made a script that would batch convert everything in a local directory:
export LD_LIBRARY_PATH=/usr/local/lib
if [[ -z $1 ]]
cd svgfiles
mkdir formats
cp ../formats/* formats/ for file in *.svg; do echo "converting $file" ./../svg2cairoxml $file ${file}.xml lua ../cairoxml2cairo.lua -f c ${file}.xml draw_${file/.svg/}.h file=draw_${file/.svg/}.h size=$(file $file | grep "empty") if [[ -z "$size" ]]; then echo success sed -i s/CAIRO_DEFAULT/CAIRO_ANTIALIAS_DEFAULT/ $file sed -i "1s,^,inline ," $file sed -i "2s,^,inline ," $file sed -i "3s,^,inline ," $file caps=`echo ${file^^}` caps=`echo $caps | sed s/[.]/_/` sed -i "1s,^,#ifndef $caps\n#define $caps\n," $file sed -i -e "\$a#endif" $file else rm draw_${file/.svg/}.c fi done
rm -rf formats/
rm *.xml cd ..  
 echo "converting $file" 
 ./svg2cairoxml $1 ${1}.xml
 lua cairoxml2cairo.lua -f c ${1}.xml draw.c

export LD_LIBRARY_PATH=/usr/lib


Once you have the source file, you can draw the thing to your hearts delight, but we want to make it an interactive widget. So the important thing you'll have to do is animate the movement (more or less). There are a couple of approaches. One (that I took before in my QT attempts) is make separate svg files for the primitives and draw them separately, i.e. one for the round part of the knob that doesn't move and one for the needle that spins. The conversions will make 2 separate files with 2 separate functions that you'll need to call. Before calling the function that draws the needle do some transforms that will turn the drawing. Something like:
cairo_rotate(cr,3*PI/2*value - 3*PI/4);

I haven't tested this, so no copy pasting but hopefully that gets the idea across. I however am opting for making a single drawing, then editing the render function to rotate it. To do so you'll have to figure out whats going on in the generated function. I don't know cairo hardly at all, but I was able to figure it out, so its not that bad. More or less it just draws each object in the svg from bottom layer to top. It draws the fill then the outline. And each part is conveniently separated with a comment line.

So edit draw_vintagedial.c to become draw_vintagedial.h with the following changes:

diff --git a/draw_vintagedial.c b/draw_vintagedial.h
index 44f6b35..8246911 100644
--- a/draw_
+++ b/draw_vintagedial.h
@@ -1,6 +1,7 @@

+#define PI 3.1415926535897932384626433832795
 int cairo_code_draw_vintagedial_get_width() { return 91; }
 int cairo_code_draw_
vintagedial_get_height() { return 91; }
-void cairo_code_draw_
vintagedial_render(cairo_t *cr) {
+void cairo_code_draw_
vintagedial_render(cairo_t *cr, float val) {
 cairo_surface_t *temp_surface;
 cairo_t *old_cr;
 cairo_pattern_t *pattern;
@@ -59,6 +60,11 @@ cairo_pattern_set_extend(pattern, CAIRO_EXTEND_PAD);
 cairo_pattern_set_filter(pattern, CAIRO_FILTER_GOOD);
 cairo_set_source(cr, pattern);
+cairo_rotate(cr,3*PI/2*val - 3*PI/4);
 cairo_move_to(cr, 57.8125, 84.710938);
 cairo_curve_to(cr, 56.824219, 91.421875, 34.003906, 91.601562, 33.007812, 84.902344);

If you haven't seen a git diff before, its showing what lines get removed (-) and what gets added (+) with some unchanged lines above and below.

So I actually lied a little. We won't be using AVTK at all, we'll be using FFFFLTK: the forked-forked-forked-fast and light toolkit! Which I invented. Just now. Its, (you couldn't guess?) a fork of AVTK. The primary difference is rather than a built in draw function (which is used as a fallback default)  it just calls a function pointer you pass to it.

So in the end (for a dial or slider) you just need some function that takes a value from 0-1 (min to max) as the argument and draws the drawing accordingly and pass a pointer to it into the ffffltk widget. Alternatively you can just copy and paste the guts of the render function into the draw function of the AVTK widget, but I'm going to do it with function pointers. This is because I want my plugins to each be customized, so they'll be using lots of different looking knobs, and I don't want to copy all the other stuff over and over. Instead I'll just repeat this process for each drawing and generate a bunch of headers that I can include at will.

So get a copy of the ffffltk dial widget. There's a button in the link above to download a snapshot. Do that, open the archive and extract src/ffffltk/ffffltk_dial.h to the folder we're using for this little project.

So lets look at how to make a dial and assign the drawing function:
ffffltk::Dial *dial = new ffffltk::Dial;
dial->drawing_h = cairo_code_draw_vintagedial_height();
dial->drawing_w = cairo_code_draw_vintagedial_width();
dial->drawing_f = &cairo_code_draw_vintagedial_render;

and thats it. With various drawing functions you can have loads of different looking knobs on the same GUI.

So lets make a test. One of the programs installed when you installed NTK is ntk-fluid which is the NTK version of the fast-light-user-interface-designer. This makes designing the gui a breeze because much of it is drag and drop and the rest is fill in the blank. It might help to review the official fluid tutorial on that page linked above. I'm just going to breeze through it.

First make a new class, name it "FancyUI" and add a function and window named whatever the default is. In the C++ tab add in an extra code box:

Add a single dial to the window and we'll edit its properties. Label it "Fancyness" and move it and resize it however you like. In the C++ tab type in the class box "ffffltk::Dial" in an extra code box type 
#include "ffffltk_dial.h" #include "draw_vintagedial.h"

and finally in another extra code box type:
o->drawing_h = cairo_code_draw_vintagedial_get_height(); o->drawing_w = cairo_code_draw_vintagedial_get_width(); o->drawing_f = &cairo_code_draw_vintagedial_render;

Save it as test.fl then go to file>write code and it will spit out  test.h and test.cxx that you can include in any project. For the test, make a main.cxx file with the following:
#include <FL/Fl.H>
#include <FL/Fl_Double_Window.H>
#include "test.h"

int main()
  FancyUI *a = new FancyUI;
  return 0;

Now compile and run it:
g++ -fpermissive *.cxx -lGL -lntk `pkg-config --cflags --libs cairo ntk` -o test_ui

And you should see something like this:
Congratulations! You are on the bleeding edge of technology. I am literally writing ffffltk as I write this, but I think the dial is more or less ready. Eventually I'll make a background, button, slider and whatever other widgets I'll need. Feel free to pitch in. I ought to mention too, big thanks to Harry Haaren of openAV productions for helping me out and making AVTK so simple to fork. And really to the creators of all these many tools I'm using. Thats the beauty of FLOSS: you really get to stand on the shoulders of giants.

Some issues:
One thing is that drawings don't actually scale well. At some point as it shrinks you need to remove details to keep it clear, and as it grows you should add detail. This method simply scales everything. It works ok within limits. If you know you are going to need very small or very large then you can change your drawing function accordingly. Or fork it and add height and width arguments to the draw function call. Its hard to keep it simple and make it flexible at the same time. The point was to be able to just use an svg with as little coding as possible. And there you have it.

Any questions?

No comments: