Monday, August 11, 2014

Drawing a GUI in Inkscape

So I got all excited and posted that last article about a lot of the technical stuff to make a GUI in Inkscape, but after posting it I realized it makes quite a few assumptions, so I'm going to add a few follow on posts, some of which (mostly this one) should have been written first. So its a prequel. Or a Part 0. Something like that.

Anyway the biggest assumption I made is that you are proficient in inkscape. Perhaps you are, but I wasn't until I started trying to draw GUIs in it. So basically all my skills are honed to this purpose. I'm not qualified to give a real inkscape tutorial so you should probably stop reading right now, but just in case I can help someone out there make a cool GUI I'll press onward.



I think I originally found inkscape when trying to draw a cover for my bands forthcoming album (which has been forthcoming for several years). I started with GIMP, but I couldn't really draw very well with it. I also really liked the idea of scalable drawings rather than pixmaps, so I stumbled upon inkscape. Not long after I read the rakarrack team was looking for new GUIs so I volunteered that I didn't know what I was doing, but I would try anyway. I drew some 30 svg drawings in inkscape of "stompboxes" for the various rakarrack effects and wrote the qsvgwidget library thing that I mentioned before. That faded as the programming got complex, but I have since used inkscape for my masters thesis diagrams, work documents, and even for sketching up layouts for remodeling my kitchen.

I'm about to give a bunch of information that may not even be helpful, but its what I use all the time when drawing stuff, but first (before you get scared) lets have an overview. You can make it pretty simple and have some good looking results. You are drawing lines and shapes. Shapes are just lines that connect together to form a boundary (so really you are just drawing lines). These lines and shapes become objects you can copy and paste, move around, shape, layer and do all sorts of things to make them look how you want, but in the end you are just defining lines and shapes.

I'd read an watched some tutorials that helped a lot and you'll do better with those, but here's a crash course (more like list) of basic Inkscape "moves" that help a lot in drawing GUI elements.
Need to know skills:

Ctl+D - duplicate, creates a copy exactly over the original. Use it then the arrow keys to make identical copies that are aligned, i.e. a grid of dials

(arrow key) - move object
Alt+(arrow key) - move object small step
Shift+(arrow key) - move object large step

Ctl+g - turn objects into a group. Nothing is worse than making an intricate object then accidentally missing one piece when selecting it to move to its place. Ctl+z fixes it by undoing, starting you over, but its much nicer just to have one object (group really) that you just click and drag. You can have groups of groups, groups of mixed groups and objects etc.
Ctl+Shift+g - separate the topmost group back into individual objects/groups, sometimes you need to go change a detail, so you ungroup, change, regroup

Ctl+(rotate, resize, etc) - holding control usually makes things work in nice increments, i.e. when drawing a rectangle, holding ctl forces it to a square, rotations while holding ctl go in 30 degree increments (or something like that).

Ctl+Shift+C - converts the object/shape/text to a path so you can manipulate its little details. For text especially this matters because if the target doesn't have the correct font installed it won't appear correctly, but once its a path, it just draws that shape. Make sure your text is exactly how you want it first though.

Rectangle drawing tool, circle drawing tool, gradient tool, and node editing tool; align and distribute menu, fill and stroke menu; etc. etc. etc.

Ok there's probably too much to mention. But these are the ones I use the most. You can see there are a lot of things you can do with inkscape and this doesn't scratch the surface.

The node editing tool is especially powerful. In this almost-tutorial I'm going to make a GUI and I went through the pain of visually documenting each step for one of the shapes:

You see first it was a rectangle, then I converted it to a path, and used the node editing tool. I added some nodes, deleted the ones I didn't want, then changed the node curvature properties so the middle 2 nodes on the sides are corners, and the top one is a symmetrical curve. In the bottom row I drew a line, centered it, duplicated the lines and moved them to one side, duplicated the 3 lines, mirrored it horizontally and moved them to the other side. For the screenshot I selected everything with the node adjust tool so you can hopefully see what I was doing. Also take note how nicely aligned the steps are are due to the duplicate/move-with-arrow  combo.

The node tool is pretty advanced. You don't have to go that far. Here's a simple dial:

So make a circle, add a radial gradient, edit the gradient stops (colors), then add (and align) a rectangle for a pointer. Easy as PI. You can even skip the gradient.

The point is its a little easier (because its WYSIWYG) than trying to edit style files or blindly using a drawing API like cairo. That is why I started ffffltk (and qsvgwidgets before that).

So I'm not going to describe how to draw every single widget, you can go learn more about drawing on your own (or just ask for specifics in the comments below). But here are some tips on how to make your life easier when you convert them to Cairo code.

Symmetry is nice, but some things are asymmetrical. For dials you will need to know what point you want to rotate around in the drawing so you can do it in cairo, typically its the middle so just keep the width and height the same and divide by 2. Also important is that the original drawing or "canvas" size will fit everywhere the drawing could be. For example this switch:

Once toggled the other way it's going to go off the other side of the canvas. Like this:

So instead, change the size of the canvas so that it doesn't. To get the right size of canvas: duplicate the whole switch (Ctl+D), flip the copy vertically (V), and carefully move it (using only up and down arrow keys) so that the circle parts are aligned. I find it helpful to temporarily change the opacity of the top one so you can see the one beneath. Zoom way in and use the Alt+Down key to make those fine detailed moves till it fits. Then press Ctl+Shift+D to change the canvas size. Click the resize page to drawing or selection button in the custom size box and it will resize it perfectly:


Now if you delete one copy you get the same thing, but it won't get cut off:

When using the ffffltk button widget to display this, you will convert to cairo and then you need to "animate" the drawing. With this one you could probably get away with doing a cairo_rotate(cr,PI) before drawing the top object (the switch), but I chose to leave both copies of the switch in the drawing and use an if statement to toggle which one will actually be drawn. Having the drawing like this also helps when prototyping the layout of the window in inkscape, so you can see if you are going to cover up some text or something once the switch is thrown (because the drawing shows both positions). If this paragraph is completely confusing, you probably should review my last blog post. Or I'm just a terrible writer.

The same sort of thing goes with dials. In the example "vintage" dial I used in the last post the canvas was made by duplicating and rotating the needle 90 degrees and resizing the canvas so the point doesn't get cut off when you turn it sideways. Hopefully that covers this point.

Another widget I'm using quite a bit in these skeuomorphic designs is an l.e.d.. Because you can't use Gaussian blur natively in cairo, I achieve it with a radial gradient. In this UI design there's a blue l.e.d. so I make a drawing of an unlit l.e.d. and then a lit version. Below you see them side by side. The "light" is just a lighter blue circle over top with a multi-stop radial gradient centered on the circle so that the edges are becoming transparent. It works. These are used in a toggle button draw function that just removes the "light" circle when passed a 0.


So once you've drawn a bunch of widgets and saved them as separate svgs, its probably worth throwing them all together into a layout. So for this example here are the pieces:

And I arrange them like so:


One trick I use is that I make a group of the 2 dials and center the group with respect to the background, then ungroup them so that they are equally spaced, then center their respective labels to the knobs.

This part is the most complicated, but can be bypassed by using simpler backgrounds, using labels on the widgets rather than fancy text drawn into the background, and just laying it out in ntk-fluid. We want to take note of the size of the background canvas and where each widget falls so that we can reproduce the inkscape drawing in ntk-fluid. The trouble is SVG/Inkscape defines the origin as the bottom left corner and fltk defines the origin as the top left corner with the y axis inverted. So to really reproduce the drawing we can't just copy the numbers, we have to take each widget and recalculate it. So for example the background is 128x200 width x height and in Inkscape the switch falls at a position (x,y,w,h) of about (20,71,11,36). So once that's switched to fltk the y changes to 200-(71+36) = 93.  Another tip is select all the objects, then in the bar above the drawing, lock the width and height and then type a nice round number into one of them (such as 200). This scales everything respectively. You could even move and scale each object to nice integers by directly entering numbers this way, but I just figure the nearest pixel is close enough.

Once you have a mockup and note all the important numbers, save each object that will become a widget separately (delete everything but the widget of interest, resize canvas, save, undo till the other widgets are back, repeat). So here I generate 4 files, the dial, the switch, the l.e.d. and everything else gets saved as the background file (remember to convert text to paths!). Convert those files to cairo code as described in the last post and make the switch and l.e.d. render functions have an additional argument for being on or off (dials were covered last time, follow that). Now, continue with that guide to make a UI in ntk-fluid.

I'm doing some things differently from the previous guide. One is setting all the widget styles such that they have no boxes. I was going to do the window with no box so that it was a "non-rectangular" window, but then it requires a window manager with compositing. In the extra code section for the window I include ffffltk.h and in another extra code box enter "using namespace ffffltk;" This way I don't have to declare each widget a ffffltk::Dial etc. only "Dial" (else it will look for a non-existent "ffffltk::Fl_Dial").

 Also not mentioned is that the ffffltk::Background widget has a property "stretch" that is set true by default; in this UI I would set it to false so that the background height and width are always scaled proportionally, preserving the shape. This good for decorations but not really the best for this UI because the other widgets scale and move with respect to the window not the drawing. To make up for this I created a new ffffltk widget: Aspect_Group. Clever name huh? Its a group that always preserves aspect ratio. So you assign the draw function to the background drawing and then put all the other widgets inside the group. They then scale and move proportionally to the background rather than the whole window. So if you want the window to be able to change shape (i.e. be tall and narrow or short and wide), use a Background widget and put your text labels as separate Backgrounds with stretch disabled. If you want it to maintain aspect ratio, use an Aspect_Group.

  The l.e.d. is an inactive button and gets its value set by the callback of the other button with "led->value(stickit->value());". I also set the buttons to toggle buttons using "o->type(FL_TOGGLE_BUTTON)" in initialization since there is no ffffltk::Toggle_button to instantiate.

This UI design is obviously impractical. More than half the window is wasted simply for decoration, when there are only 3 real widgets, and one redundant indicator. But this is going to a plugin that can have a host generated UI and be perfectly functional with that, so the only reason you would use this GUI is because you want eye-candy. So now you've got some:


There you have it. Overall this is another almost-tutorial that is probably more confusing than helpful. The last post I wrote to help myself remember, this one I kinda wrote for you readers. If you really exist. Hope it helps. Ask for clarification and I'll either elucidate or write a separate post detailing whatever it is. But also hang on for a follow up on how to apply this UI to an LV2 plugin. That post might be more thorough since I'll be writing for myself again. Sorry but it's true.

No comments: