FLUID for FLTK 1.4.0
Tutorials

Hello, World!

The first FLUID tutorial explains the FLUID basics. It creates a single main() function that opens an application window with a static text box inside.

After launching FLUID we want to make sure that two very useful tool windows are open. The "Widget Bin" gives quick access to all available widgets and functional types. It can be opened via the main menu: Edit > Show Widget Bin, or using the shortcut Alt-B.

The second very helpful tool box is the "Code View". The Code View gives a preview of the code as it will be generated by FLUID. All changes in the layout or in attributes are reflected immediately in the Code View. Choose Edit > Show Code View or press Alt-C to get this toolbox. Make sure that Auto-Refresh and Auto-Position are active in the Code View.

Let's start Hello World from scratch. If there is already a previous project loaded, select File > New or Ctrl-N.

Before we can create a window, we need a "C" function that can be called when we run the program. Select New > Code > Function/Method... or click on the image in the widget bin.

A function is added as a first line to the widget tree in the main window, and a "Function/Method Properties" dialog box will pop up. For our Hello World program, delete all text in the top "Name(args)" text field. If this field is left empty, FLUID will generate a main(argc, argv) function for us.

Function/Method Properties Dialog

Click OK to apply the changes you made in the Function Properties dialog. You can get this dialog back at any time by selecting the function in the main window and pressing F1, or by double-clicking it.

Note that the function will not show up in the Code View yet. FLUID will not generate code for functions that don't have any content, and only create a forward declaration in the header file, assuming that the function will be implemented inside another module.

Keep the main function selected and add an Fl_Window to the function by selecting New > Group > Window..., by clicking the image in the Widget Bin, or by dragging the Group/Window image from the Widget Bin onto the desktop.

A new application window will open up on the desktop. The thin red outline around the window indicates that it is selected. Dragging the red line will resize the window. Take a look at the Code View: the main function is now generated, including code to instantiate our Fl_Window.

To edit all the attributes of our window, make sure it is selected and press F1, or double-click the entry in the main FLUID window, or double-click the window itself. The "Widget Properties" dialog box will pop up. Enter some text in the "Label:" text box and see how the label is updated immediately in the window itself, in the widget list, and in the Code View.

Adding a static text to our window is just as easy. Put an Fl_Box into our window by selecting New > Other > Box, or by clicking on the image in the Widget Bin, or by dragging the Other/Box image from the Widget Bin into our newly created window.

Most importantly, enter the text "Hello, World!" in the "Label:" field of the Box Widget Properties panel to achieve the goal of this tutorial. Now is also a great time to experiment with label styles, label fonts, sizes, colors, etc. .

Finally, we should save our project as an .fl project file somewhere. Once the project is saved, select File > Write Code or press Shift-Ctrl-C to write our source code and header file to the same directory.

Compile the program using a Makefile, CMake file, or fltk-config as described in the FLTK manual and the README.txt files in the FLTK source tree.

7GUIs, Task 1

In the first "Hello World" tutorial, we built an entire application in FLUID. It's a boring application though that does nothing except quitting when the close button in the window border is clicked.

Task 1 of 7GUIs

The second tutorial will introduce callbacks by implementing task 1, "Counter", of 7GUIs. 7GUIs has been created as a spin-off of my master’s thesis Comparison of Object-Oriented and Functional Programming for GUI Development at the Human-Computer Interaction group of the Leibniz Universität Hannover in 2014. 7GUIs defines seven tasks that represent typical challenges in GUI programming. https://eugenkiss.github.io/7guis/ .

Task 1 requires "Understanding the basic ideas of a language/toolkit. The task is to build a frame containing a label or read-only textfield T and a button B. Initially, the value in T is “0” and each click of B increases the value in T by one."

Our knowledge from tutorial 1 is enough to generate the main() function, and add an Fl_Window, an Fl_Output, and an Fl_Button. To make life easy, FLUID comes with a built-in template for this tutorial. Just select File > New from Template... and double-click "1of7GUIs" in the Template Panel.

We will need to reference the output widget in our callback, so let's assign a pointer to the widget to a global variable and give that variable a name. Open the Widget Properties dialog by double-clicking the output widget. Change to the "C++" tab, and enter "`counter_widget`" in the "Name:" field.

The "Count" button is the active element in our application. To tell the app what to do when the user clicks the button, we create a callback function for that button. Open the widget properties dialog for the button. In the "C++" tab, we find the input field "Callback:".

The callback is called exactly once every time the user clicks the button. Our strategy here is to read the current value from the counter_widget, increment it by 1, and write it back to counter_widget. The FLTK documentation tells us that we can use Fl_Output::ivalue() to get text in Fl_Output as an integer, and we can write it back by calling Fl_Output::value(int). When the value is changed, FLTK will automatically redraw the output widget for us. So here is the callback code:

int i = counter_widget->ivalue();
i++;
counter_widget->value(i);

That's it. This is a complete interactive desktop application. Compile, link, run, and test it to see how it works.

Cube View

This tutorial will show you how to generate a complete user interface class with FLUID that is used for the CubeView program provided with FLTK.

CubeView demo

The window is of class CubeViewUI, and is completely generated by FLUID, including class member functions. The central display of the cube is a separate subclass of Fl_Gl_Window called CubeView. CubeViewUI manages CubeView using callbacks from the various sliders and rollers to manipulate the viewing angle and zoom of CubeView.

At the completion of this tutorial you will (hopefully) understand how to:

  1. Use FLUID to create a complete user interface class, including constructor and any member functions necessary.
  2. Use FLUID to set callback member functions of custom widget classes.
  3. Subclass an Fl_Gl_Window to suit your purposes.

The CubeView Class

The CubeView class is a subclass of Fl_Gl_Window. It has methods for setting the zoom, the x and y pan, and the rotation angle about the x and y axes.

You can safely skip this section as long as you realize that CubeView is a sublass of Fl_Gl_Window and will respond to calls from CubeViewUI, generated by FLUID.

The CubeView Class Definition

Here is the CubeView class definition, as given by its header file "test/CubeView.h":

#include <FL/Fl.H>
#include <FL/Fl_Gl_Window.H>
#include <FL/gl.h>
class CubeView : public Fl_Gl_Window {
public:
CubeView(int x, int y, int w, int h, const char *l = 0);
// This value determines the scaling factor used to draw the cube.
double size;
/* Set the rotation about the vertical (y) axis.
*
* This function is called by the horizontal roller in
* CubeViewUI and the initialize button in CubeViewUI.
*/
void v_angle(double angle) { vAng = angle; }
// Return the rotation about the vertical (y) axis.
double v_angle() const { return vAng; }
/* Set the rotation about the horizontal (x) axis.
*
* This function is called by the vertical roller in
* CubeViewUI and the initialize button in CubeViewUI.
*/
void h_angle(double angle) { hAng = angle; }
// The rotation about the horizontal (x) axis.
double h_angle() const { return hAng; }
/* Sets the x shift of the cube view camera.
*
* This function is called by the slider in CubeViewUI
* and the initialize button in CubeViewUI.
*/
void panx(double x) { xshift = x; }
/* Sets the y shift of the cube view camera.
*
* This function is called by the slider in CubeViewUI
* and the initialize button in CubeViewUI.
*/
void pany(double y) { yshift = y; }
/* The widget class draw() override.
*
* The draw() function initializes Gl for another round of
* drawing, then calls specialized functions for drawing each
* of the entities displayed in the cube view.
*/
void draw();
private:
/* Draw the cube boundaries.
*
* Draw the faces of the cube using the boxv[] vertices,
* using GL_LINE_LOOP for the faces.
*/
void drawCube();
double vAng, hAng;
double xshift, yshift;
float boxv0[3]; float boxv1[3];
float boxv2[3]; float boxv3[3];
float boxv4[3]; float boxv5[3];
float boxv6[3]; float boxv7[3];
};
The CubeView Class Implementation

Here is the CubeView implementation. It is very similar to the "CubeView" demo included with FLTK.

#include "CubeView.h"
#include <math.h>
CubeView::CubeView(int x, int y, int w, int h, const char *l)
: Fl_Gl_Window(x, y, w, h, l)
{
Fl::use_high_res_GL(1);
vAng = 0.0;
hAng = 0.0;
size = 10.0;
xshift = 0.0;
yshift = 0.0;
/* The cube definition. These are the vertices of a unit cube
* centered on the origin.*/
boxv0[0] = -0.5; boxv0[1] = -0.5; boxv0[2] = -0.5;
boxv1[0] = 0.5; boxv1[1] = -0.5; boxv1[2] = -0.5;
boxv2[0] = 0.5; boxv2[1] = 0.5; boxv2[2] = -0.5;
boxv3[0] = -0.5; boxv3[1] = 0.5; boxv3[2] = -0.5;
boxv4[0] = -0.5; boxv4[1] = -0.5; boxv4[2] = 0.5;
boxv5[0] = 0.5; boxv5[1] = -0.5; boxv5[2] = 0.5;
boxv6[0] = 0.5; boxv6[1] = 0.5; boxv6[2] = 0.5;
boxv7[0] = -0.5; boxv7[1] = 0.5; boxv7[2] = 0.5;
}
void CubeView::drawCube() {
/* Draw a colored cube */
#define ALPHA 0.5
glShadeModel(GL_FLAT);
glBegin(GL_QUADS);
glColor4f(0.0, 0.0, 1.0, ALPHA);
glVertex3fv(boxv0);
glVertex3fv(boxv1);
glVertex3fv(boxv2);
glVertex3fv(boxv3);
glColor4f(1.0, 1.0, 0.0, ALPHA);
glVertex3fv(boxv0);
glVertex3fv(boxv4);
glVertex3fv(boxv5);
glVertex3fv(boxv1);
glColor4f(0.0, 1.0, 1.0, ALPHA);
glVertex3fv(boxv2);
glVertex3fv(boxv6);
glVertex3fv(boxv7);
glVertex3fv(boxv3);
glColor4f(1.0, 0.0, 0.0, ALPHA);
glVertex3fv(boxv4);
glVertex3fv(boxv5);
glVertex3fv(boxv6);
glVertex3fv(boxv7);
glColor4f(1.0, 0.0, 1.0, ALPHA);
glVertex3fv(boxv0);
glVertex3fv(boxv3);
glVertex3fv(boxv7);
glVertex3fv(boxv4);
glColor4f(0.0, 1.0, 0.0, ALPHA);
glVertex3fv(boxv1);
glVertex3fv(boxv5);
glVertex3fv(boxv6);
glVertex3fv(boxv2);
glEnd();
glColor3f(1.0, 1.0, 1.0);
glBegin(GL_LINES);
glVertex3fv(boxv0);
glVertex3fv(boxv1);
glVertex3fv(boxv1);
glVertex3fv(boxv2);
glVertex3fv(boxv2);
glVertex3fv(boxv3);
glVertex3fv(boxv3);
glVertex3fv(boxv0);
glVertex3fv(boxv4);
glVertex3fv(boxv5);
glVertex3fv(boxv5);
glVertex3fv(boxv6);
glVertex3fv(boxv6);
glVertex3fv(boxv7);
glVertex3fv(boxv7);
glVertex3fv(boxv4);
glVertex3fv(boxv0);
glVertex3fv(boxv4);
glVertex3fv(boxv1);
glVertex3fv(boxv5);
glVertex3fv(boxv2);
glVertex3fv(boxv6);
glVertex3fv(boxv3);
glVertex3fv(boxv7);
glEnd();
} // drawCube
void CubeView::draw() {
if (!valid()) {
glLoadIdentity();
glViewport(0, 0, pixel_w(), pixel_h());
glOrtho(-10, 10, -10, 10, -20050, 10000);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
}
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glPushMatrix();
glTranslatef((GLfloat)xshift, (GLfloat)yshift, 0);
glRotatef((GLfloat)hAng, 0, 1, 0);
glRotatef((GLfloat)vAng, 1, 0, 0);
glScalef(float(size), float(size), float(size));
drawCube();
glPopMatrix();
}

The CubeViewUI Class

We will completely construct a window to display and control the CubeView defined in the previous section using FLUID.

Defining the CubeViewUI Class

Once you have started FLUID, the first step in defining a class is to create a new class within FLUID using the New->Code->Class menu item. Name the class "CubeViewUI" and leave the subclass blank. We do not need any inheritance for this window. You should see the new class declaration in the FLUID browser window.

FLUID file for CubeView
Adding the Class Constructor

Click on the CubeViewUI class in the FLUID window and add a new method by selecting New->Code->Function/Method. The name of the function will also be CubeViewUI. FLUID will understand that this will be the constructor for the class and will generate the appropriate code. Make sure you declare the constructor public.

Then add a window to the CubeViewUI class. Highlight the name of the constructor in the FLUID browser window and click on New->Group->Window. In a similar manner add the following to the CubeViewUI constructor:

  • A horizontal roller named hrot
  • A vertical roller named vrot
  • A horizontal slider named xpan
  • A vertical slider named ypan
  • A horizontal value slider named zoom

None of these additions need be public. And they shouldn't be unless you plan to expose them as part of the interface for CubeViewUI.

When you are finished you should have something like this:

FLUID window containing CubeView demo

We will talk about the show() method that is highlighted shortly.

Adding the CubeView Widget

What we have is nice, but does little to show our cube. We have already defined the CubeView class and we would like to show it within the CubeViewUI.

The CubeView class inherits the Fl_Gl_Window class, which is created in the same way as an Fl_Box widget. Use New->Other->Box to add a square box to the main window. This will be no ordinary box, however.

The Box properties window will appear. The key to letting CubeViewUI display CubeView is to enter CubeView in the Class: text entry box. This tells FLUID that it is not an Fl_Box, but a similar widget with the same constructor.

In the Extra Code: field enter #include "CubeView.h"

This #include is important, as we have just included CubeView as a member of CubeViewUI, so any public CubeView methods are now available to CubeViewUI.

CubeView methods
Defining the Callbacks

Each of the widgets we defined before adding CubeView can have callbacks that call CubeView methods. You can call an external function or put a short amount of code in the Callback field of the widget panel. For example, the callback for the ypan slider is:

cube->pany(((Fl_Slider *)o)->value());
cube->redraw();

We call cube->redraw() after changing the value to update the CubeView window. CubeView could easily be modified to do this, but it is nice to keep this exposed. In the case where you may want to do more than one view change only redrawing once saves a lot of time.

There is no reason to wait until after you have added CubeView to enter these callbacks. FLUID assumes you are smart enough not to refer to members or functions that don't exist.

Adding a Class Method

You can add class methods within FLUID that have nothing to do with the GUI. As an example add a show function so that CubeViewUI can actually appear on the screen.

Make sure the top level CubeViewUI is selected and select New->Code->Function/Method. Just use the name show(). We don't need a return value here, and since we will not be adding any widgets to this method FLUID will assign it a return type of void.

CubeView constructor

Once the new method has been added, highlight its name and select New->Code->Code. Enter the method's code in the code window.

Adding Constructor Initialization Code

If you need to add code to initialize a class, for example setting initial values of the horizontal and vertical angles in the CubeView, you can simply highlight the constructor and select New->Code->Code. Add any required code.

Generating the Code

Now that we have completely defined the CubeViewUI, we have to generate the code. There is one last trick to ensure this all works. Open the preferences dialog from Edit->Preferences.

At the bottom of the preferences dialog box is the key: "Include Header from Code". Select that option and set your desired file extensions and you are in business. You can include the CubeViewUI.h (or whatever extension you prefer) as you would any other C++ class.