Home
Download
Documentation
Tutorials
Contact
The Flow Layout System
Iffe implements a unique layout system called Flow which works in a largely different manner to existing systems you may have encountered so far. However it is intended to simplify the UI development process and provide a much smoother workflow for both initial development as well as future maintenance. For now, lets have a look at how it works and what kinds of functionality it provides.

The first step is to create a minimal program displaying a single window with a button. This can be done using the following code:
  #include <iffe/iffe.h>

  widget(MainWindow, Init)
  {
    int dummy; /* We have no members yet! */
  };

  void OnInit(struct InitEvent *ev)
  {
    ref(Widget) button = WidgetAdd(ev->sender, Button);
  }
This is an entire program and Iffe does not require you to create a main function. Instead any Widget given the name MainWindow will be initialized and displayed as a top-level window. The program can be compiled as a normal C program using a command similar to:
  $ cc -oflow main.c -liffe -lX11
This would result in a window appearing with the following layout.
You will notice that by default the button will appear at the bottom right corner of the window if no positional instructions are given. This may seem strange at first (i.e other systems default to the top left) however after you start to use the system you may start to see that it offers a few conveniences.

Our next step is to move the button to the top left. Rather than set absolute coordinates (such as 0, 0), Flow works using instructions. So as the window is resized, these same instructions get rerun and the window layout is regenerated (albeit at a different size). For now we just move up via the following code:
  WidgetFlow(button, "^");
As you may have guessed, directions are given within the WidgetFlow function via < > ^ v symbols. As the following image demonstrates, the button will keep moving upwards until it hits either the edge of the window or another positioned Widget.
With that in place, we can then instruct the Widget to move to the left. Rather than writing multiple statements in C, the command can be collapsed as seen in the following code:
  WidgetFlow(button, "^<");
So with this, the button will first move all the way to the top edge, and then all the way to the left edge as shown in the following:
With this in place, we will now create a new Text Box and provide it the same instructions to move up and left. This will place it neatly beside the original button. You will see that if we swap the instructions and move it left first and then up, it will result in being placed under the original button. This provides useful flexibility. The code for the Text Box should be as follows.
  ref(Widget) text = WidgetAdd(ev->sender, TextBox);
  WidgetFlow(text, "^<");
The following image shows the route that the Text Box will take. This should also be fairly easy to visualize compared to other approaches using many nested grid containers.
Next we are going to add a Separator Widget to close off the top row. For this we are going to do something slightly different. Instead of moving to the left, we provide an instruction for the Widget to expand left. this means it will keep growing towards the left until it touches the left edge. After this our usual instruction to move upwards is given.
  ref(Widget) sep = WidgetAdd(ev->sender, Separator);
  WidgetFlow(text, "=<^");
So again, you may notice that any directional instruction with a = symbol before it will expand in that direction rather than simply move. This process is demonstrated by the following image.
Note that the Separator Widget is by default 1x1 pixels in size. This allows us to expand it in the vertical direction as well as horizontal. The final result of this can be seen in the following image.
At this point, our code should be similar to the following:
  #include <iffe/iffe.h>

  widget(MainWindow, Init)
  {
    int dummy;
  };

  void OnInit(struct InitEvent *ev)
  {
    ref(Widget) button = WidgetAdd(ev->sender, Button);
    WidgetFlow(button, "^<");

    ref(Widget) text = WidgetAdd(ev->sender, TextBox);
    WidgetFlow(text, "^<");

    ref(Widget) sep = WidgetAdd(ev->sender, Separator);
    WidgetFlow(text, "=<^");
  }
Advanced Ordering
Next in this tutorial we will look further into the ways that ordering of the flow instructions can affect the layout and also provide useful results. We will add two new Widgets; a Text Area and another Separator. Ultimately we want all the remaining space to be taken up by the Text Area but for now we will only move it into the top left and not expand it just yet. As for the separator, we will use this to section off some button widgets but for now lets expand it horizontally and move it upwards. These tasks can be seen in the following code and subsequent image:
  ref(Widget) area = WidgetAdd(ev->sender, TextArea);
  WidgetFlow(area, "<^");

  ref(Widget) sep2 = WidgetAdd(ev->sender, Separator);
  WidgetFlow(sep2, "=<^");
Now that we have the Separator and Text Area out of the way temporarily, we simply add a Button. Next we move the Separator down to rest ontop of the newly placed button. Finally with everything else in the correct positions, we expand the Text Area in both dimensions. These steps are detailed below:
  ref(Widget) button2 = WidgetAdd(ev->sender, Button);

  WidgetFlow(sep2, "v");
  WidgetFlow(area, "=>=v");
As you may have noticed, the WidgetFlow instructions can be specified multiple times and not only will they append to the current list but they will also be in sequence of any instructions on sibling Widgets. The final output can be seen in the following.
Importantly, all of the Widgets are placed using instructions rather than absolute coordinates which means if we later resize the top-level window, these instructions are repeated and the UI layout effectively scales. For example the layout we have created should scale as shown:
Finally a complete listing of the code required to create a program that provides this layout. This should be considerably simpler when compared with other UI systems.
  #include <iffe/iffe.h>

  widget(MainWindow, Init)
  {
    int dummy;
  };

  void OnInit(struct InitEvent *ev)
  {
    ref(Widget) button = WidgetAdd(ev->sender, Button);
    WidgetFlow(button, "^<");

    ref(Widget) text = WidgetAdd(ev->sender, TextBox);
    WidgetFlow(text, "^<");

    ref(Widget) sep = WidgetAdd(ev->sender, Separator);
    WidgetFlow(text, "=<^");

    ref(Widget) area = WidgetAdd(ev->sender, TextArea);
    WidgetFlow(area, "<^");

    ref(Widget) sep2 = WidgetAdd(ev->sender, Separator);
    WidgetFlow(sep2, "=<^");

    ref(Widget) button2 = WidgetAdd(ev->sender, Button);

    WidgetFlow(sep2, "v");
    WidgetFlow(area, "=>=v");
  }
Hopefully this tutorial has given you some insight into how Flow works. You may have even noticed that we did not actually need the Separator Widgets in order to achieve the layout we created. Your next step could be to have a look at some of the examples and finally to download Iffe and have a play.
Last modified: 2020-11-29