1. Foreword

Welcome to djnn, the software development framework that will help you produce innovative user interfaces throughout efficient design and development processes.

djnn is an extensible environment, and we continuously develop extensions for supporting richer user interfaces. The current version includes the core system of djnn, the graphical module that implements the core of the SVG model, finite state machines for describing discrete behaviours, data-flow bricks for describing continuous behaviours, and initial support for input management.

This document is a guide to using the djnn C++ API. The introduction chapter provides background and practical information about djnn in general, this API in particular, and how to use the available documentation. The other chapters introduce the concepts used in djnn and describe how to use the services provided to build your own user interfaces.

This document is not an exhaustive list of classes and functions, structured by modules. the djnn C++ API Reference Manual (not available yet). This document rather takes on the endeavour of introducing you to the concepts of djnn programming through a progressive approach, focusing initially on the most common task: building a graphical user interface.

The copyright holder on this book is Ecole Nationale de l’Aviation Civile (2007-2014). This book is part of the djnn C++ API, and it is made available to you under the same terms as the said software. Please refer to the documents you received when obtaining the software.

2. Introduction

djnn is a model-driven application and user interface framework, designed for prototyping and producing highly interactive and multimodal user interfaces. djnn combines state-of-the-art 2D graphics capabilities with an architecture that supports industrial processes for designing and developing advanced user interfaces. djnn relies on the I* theoretical model of interactive software.

This guide describes the C++ version of djnn. In the present chapter, you will find practical information on this guide, background information on djnn, and practical information on using djnn in C++.

2.1. Reading the djnn documentation

2.1.1. How to read this guide

This guide contains several types of information. You can read it from cover to cover, or you can focus on some chapters depending on your goal.

  • if you want to start immediately with the code, skip the rest of this introduction or refer to the djnn C++ API Cookbook.

  • if you just want the essentials of the djnn model, read chapter Basic concepts: the djnn tree.

  • if you have a project team to organize, read this introduction, chapter Basic concepts: the djnn tree, and chapter Working in teams.

Because djnn relies on the use of models to describe interactive systems, and because models are still being added to successive versions of djnn, there still are some limitations to what type of interface you can express with the core model of djnn. Consequently, although chapter Extending components should be reserved to advanced programmers, reading it is currently necessary to build real-size applications. Anyway, early versions of djnn are only distributed to advanced programmers :-)

2.1.2. Where to find other documentation

There are two companion guides to this document:

  • the djnn C++ API reference manual (not available yet) contains the description of all public classes and methods in the C++ API, as well as the XML formats defined by djnn.

  • the djnn C++ API cookbook contains sample code for achieving given tasks, from drawing to architecturing your code or extending the djnn main loop.

These documents are available in PDF and in HTML. On Linux systems, you can know the exact location of the HTML documentation by typing man djnn.

These documents are mainly aimed at programmers. Guides or guide chapters for other actors of the design and development process are planned, but not yet available. They include information for graphic designers on how to produce artwork that programmers can readily reuse, and suggestions for project managers on how to organize the workflow in multidisciplinary groups. Check out later versions of the documentation!

2.2. About djnn

This section provides background information about the djnn environment: its philosophy, its components, etc. Reading it will provide you with interesting context. However, the hurried programmer can skip it with no harm, and maybe come back to it before reading chapter Extending components and part From graphics to multimodality.

2.2.1. A model-driven architecture

djnn is a model-driven framework. You may wonder: what does it mean and why? Let us start by the why. One of the problems that limits the availability of highly interactive user interfaces is the difficulty for industrial companies to combine their software engineering processes with the iterative processes required by user interfaces. Iterative prototyping can be costly if made with inappropriate tools. And using different tools for prototyping and development can lead to disillusions when it appears that they do not have the same capabilities and that the original design must be thrown away. Model-driven development is a solution to these problems, among others.

With model-driven development, you rely on models that researchers have produced for your domain of interest, and you create and assemble instances of those models. Instead of writing lines of code, you create data. It is not that much different, except that:

  • your model instances can be stored as data files, in XML format for instance;

  • the libraries or programs that run your models can be checked for compliance with the models.

For instance, the graphical model used in djnn is compatible with the SVG format. This guarantees compatibility between the graphical tools used by graphical designers and for early prototypes, the Perl or Python execution environment for prototypes, and the C or C++ execution environment.

2.2.2. From prototyping to development

djnn implements a model-driven architecture for user interaction. It builds up on that approach to propose new processes for building user interfaces, where the reuse of code from prototyping to development is maximised while respecting the nature of each phase of the cycle.

Prototyping is a phase where iterations and flexibility are central. Participatory design methods even recommend the use of paper prototypes to achieve those qualities. When an interactive prototype must be produced, interpreted languages are usually more appreciated than more software engineering oriented languages. Then when reaching specification and development time, the original code is usually thrown away.

However, there is no reason to throw away the images that were produced during prototyping: no extra quality is to be obtained by "recoding" them. Taking advantage of its model-driven architecture, djnn extends that reusability to other parts of the user interface: the graphical objects, the interactive behaviours, the component structure, and more to come. This is possible because with djnn you do not code these aspects: you instantiate the corresponding models. Once a model is produced, it can be reused. This feature dramatically reduces the time and effort from the original design to the final implementation. In many cases, programmers do not even have to write one single line of graphical code: all the graphics were produced during the design phase and are reused during the development.

2.2.3. Ready for graphical editing

Another feature of model-driven architectures is the ability to build or reuse graphical editors for instantiating and assembling models. The only constraint is to build models that conform to the standard format. Such editors are already available for the graphical model used in djnn, starting with the industry standard Adobe Illustrator. Specific editors can be developed for other models used in djnn.

2.2.4. Multimodal-ready!

Support for multimodality requires two things: a framework core that accomodates the use of multiple interaction sources at the same time, and support for new modalities. The djnn core provides the former. And additional djnn modules provide the latter, starting with animation and speech grammars.

2.2.5. Not a widget library

Many toolkits for graphical user interfaces are actually a set of widgets that you assemble for building point-and-click (or sometimes more advanced) user interfaces. djnn is not one of those toolkits, for several reasons:

  • djnn is aimed at modern user interfaces, that go beyond the scope of classical widgets. djnn is made for you to design new widgets, not only assemble existing ones;

  • djnn is an architecture framework for programming interactive software, not only a set of interaction components;

  • djnn does not give a privileged role to any interaction modality; it provides a set of primitives for building interactive components, and you can any modality in these components.

  • one of the core capacities of djnn is the ability to redefine the appearance or behavior of components , which challenges the classical architecture models of widget libraries;

  • several component libraries for different interaction styles are in preparation and will be available as djnn modules.

3. Setting up djnn

3.1. Introduction

Before going into further details about how to program with the djnn C++ API, we need to settle a few practical issues. This is the goal of this chapter.

The chapter starts with practical instructions for installing the libraries from binary packages or making them from the sources. It then provides more details about the structure of the libraries, before explaining how to compile your own code against the libraries.

In the practical instructions that follow, we often refer to djnn modules. This concept is described in the section about the API structure. For the time being, you just need to know that djnn comes as a set of libraries that each implement an djnn module.

The djnn C++ API comes as a set of C++ classes that wrap the djnn C libraries. To use them, you need:

  • to have a C++ compiler installed on your computer

  • to have the appropriate djnn C++ API for your platform/architecture.

  • to have the djnn libraries installed in the appropriate locations so that the C++ compiler can find them.

Once this is done, using djnn is like using any other C++ library: you need some proficiency in the C++ language (limited proficiency is required for using djnn, but you may need more for developing the rest of your application), and you load the libraries with the use instruction.

3.2. Installing binary packages

3.2.1. Mac OS X

to be written

3.2.2. Red Hat-like Linux

to be written

3.2.3. Debian-like Linux

to be written

3.2.4. Windows

to be written

3.3. Building from the sources

3.3.1. Prerequisites

to be written

3.3.2. Directory structure

to be written

3.3.3. Compilation configuration and options

to be written

3.4. API structure

3.4.1. djnn modules

The djnn environment is organised around the concept of module. An djnn application uses the core module, plus optional modules depending on the desired interaction modalities. There are:

  • a core module that contains the core of djnn: all that is necessary to create, manage and run components. You can see the core module as the core of a programming language.

  • a base module that is to the core module what the base library is to a proggramming language. The base module currently defines basic event sources (clocks, Ivy bus) and control structures to create discrete interactive behaviours.

  • a GUI module that contains the basic components for creating graphical user interfaces: graphical components based on the SVG model, windows, and the classical associated sources: a mouse and a keyboard.

  • a MacBook module that contains components that are specific to MacBooks: accelerometer, ambient lighting sensor, and keyboard lighting.

In addition to these, the following modules are currently under development:

  • a files module that allows to track the creation or modification of files in the file system.

  • a sound module to produce sound.

  • an input module, with a mechanism for detection and classification of input devices.

  • a Wifi module, with components that report on the availability of Wifi networks.

  • a network module, with components that inform on the availability and status of network interfaces and routes.

These modules come as separate libraries, so that you can pick only the necessary modules when deploying of your applications. Only the core module is mandatory for all applications. You may decide to develop an application that does not use the GUI module, for instance.

You may also decide to develop a new module to handle a new input or output interaction modality, although you will probably prefer to check our plans for module releases first. New module can be a library, a package or a plugin; what is important is that programmers can choose to load them or not.

3.4.2. One library, two headers per module

to be written

3.4.3. Programming conventions

djnn respects a number of programming conventions in its use of C++:

  • djnn classes are grouped in namespaces according to which djnn module they belong to. Classes from the core module are in namespace djnn:: (eg. djnn::Component). Classes from the base module are in namespace djnn::Base:: (eg. djnn::Base::FSM). Classes from the GUI module are in namespace djnn::GUI:: (eg. djnn::GUI::Text).

Chapter Structuring programs goes into more details about how to organize your C++ classes and what architecture patterns are encouraged by djnn.

3.5. Compiling your own djnn code

3.5.1. Compiling an application

Put the following code into a file named hello.cpp:

#include <djnnplusplus/GUI.h>
using namespace djnn;

int
main (int argc, const char** argv)
{

  GUI::Frame f (0, 0, 200, 200);
  GUI::Text t (50, 100, "hello world");
  f.run ();
  t.run ();
  SystemHook.run ();
}

This example uses components from the GUI module, and of course it uses the djnn core. Therefore, the instruction to compile it looks like the following:

g++ -o hello hello.cpp -ldjnn++-gui -ldjnn++-core

Notice the use of djnn header files and djnn libraries. Had application used more modules, such as the sound module, more headers and more libraries would have been required.

When you run it, the program should open a window, and display a text in it. Congratulations, you have written and run your first user interface with djnn!

Actually, the above program is not very interactive. It does not even know how to quit. Consequently, you will have to kill it either from your command line (press Ctrl-C in Linux) or from the "Close" button in the window bar. To know how to make more interactive programs, you will have to read a few more chapters.

3.5.2. Compiling your own djnn library or module

to be written

Building interactive applications

1. The application tree

This chapter describes how to build an interactive application by creating, running and evolving an djnn tree. It introduces the concept of component and a few basic component types, shows how to build the application tree with components and how to refer to components in the tree. It ends with a simple description of the execution of an interactive application.

1.1. Introduction to the djnn tree

In chapter Setting up djnn you have seen how to compile and run a simple djnn application. We will now see how to build more complex applications.

1.1.1. A tree of components

The central structure of an djnn application is a tree of components. Components are the nodes of the tree. If you know XML, the concept will be familiar to you: in the same way that an XML tree is made of XML elements, an djnn tree is made of djnn components. Actually, we will see later that djnn components can be created from XML elements, and stored as XML elements.

All user interaction modules provided by djnn, starting with the GUI module, come as a set of djnn components that you can use to build your application tree. For displaying a rectangle in a window, you will use a Rectangle component and a Frame component, for instance. For emitting sounds you can use a Beep component. For managing touch input you will need to deal with Pointer components. And for defining the behavior of your application you will use control components such as Binding, Connector or FSM components.

1.1.2. Composite components

Some components can contain children, and this is how you build your application tree. "Component" has the same meaning here as in the expression "component programming". Composite components are there to help you structure your application, provide reusability, and ensure encapsulation. You build an interactive component (some say an interactor) by assembling components into a composite component. The resulting interactor is a component that you or others can reuse to build more complex interactors, software components, or applications. In the end, your application itself is a component. It can take part in larger scale components made of multiple program, or you can reuse it later as a sub-component of a larger application.

Whereas other djnn components are mostly in charge of implementing the basic blocks of user interfaces, djnn composite components are mostly in charge of software engineering issues. Components implement encapsulation and parameterisation through a namespace system. All names exported by children are only visible to their parent and siblings. These names can in turn be exported (with an optional renaming) by the parent component to its own siblings and parents.

1.1.3. Tree traversals

Finally, a word about execution. For the time being, let us consider that the execution of the application is a series of traversals of the tree. During these traversals, all active djnn components are triggered. Being triggered causes the components to implement what they are made for: displaying a rectangle, managing a window, emitting a sound, etc. There are several types of traversals of the tree during its lifetime. Initialisation, rendering and control flows triggered by events are example of such traversals. If you are familiar with 3D graphics, you might like to understand the djnn tree as an extended scene graph. If you are more interested in the theory of languages and compilation, you might prefer to interpret it as the abstract tree of a language, because it is oriented towards structuring software as much as rendering graphics.

Rendering as a traversal of the tree

In the rest of this chapter, we will see in more detail how you can build an djnn tree for your application, and what happens when you run the application.

1.2. Building the tree

1.2.1. Creating components

The most simple way of building an application tree is to create new djnn components by assembling existing components. The following example creates a label component, made of two rectangles and a text. It uses constructors that each create a fresh component.

Element *l = new Component (0, "label");
Element *t = new GUI::Text (30, 20, "hello", l, "text label");

Element *b = new Component ();
Element *r1 = new GUI::Rectangle (0, 0, 100, 50, b);
Element *r2 = new GUI::Rectangle (2, 2, 96, 46, b);
l->addChild (b, "border");

This example shows two ways of assembling components. Each component class has a constructor that allows you to specify the parent of the component you are creating, and the name under which the new component will be known by its parent. This is how the text is added to the label. These two arguments are available in all constructors, and default to zero. See for instance how the two rectangles have no names specified. When no parent is specified the components can be added to a parent later, using the addChild method. This is how the border component is added to the label component.

Giving names to components is not mandatory. However, you will need it if you want to address a given component, for instance for setting its properties from a resource file or binding actions to events occuring in that component.

1.2.2. Loading components

We have seen how djnn components can be created with C++ code, using the constructor of component classes and passing it the appropriate parameters. Here is another way of creating components: loading them from XML data. Each builtin type of djnn components has an XML equivalent, and each djnn module comes with an XML parser that can read the corresponding XML elements. Therefore, you can load individual components from XML files (or from the Web) and use them to build the djnn tree. Or you can load more complex components that constitute a ready-made sub-tree. The following example loads a given graphical component from a SVG file, a whole component from another XML file, and uses them to build a tree.

Element *app = new Component (0, "Loaded components");
Element *e1 = Component::load ("test.svg#red-circle", app);
Element *e2 = Component::load ("component.xml", app);

app->run ();

1.2.3. Cloning components

To be written

1.2.4. Models

In your programs you might wish to create or load components in the sole purpose of cloning them in the future or running them upon given events, which means you do not want them to be run like the others. You can do this by putting them in components that will never be run. This is done by creating a new tree root (a component with no parent). Or, in some cases, you can pass an extra argument named model to the constructor, before the parent and identifier; this means that the created component is to be handled as a model and should not be run when its parent is run. See for instance the first argument to new sound::Beep() below, which means that beep is a model and should not be run when c is run (note for experts programmers: this is similar to the defun declaration in Lisp or the sub declaration in Perl).

Element *c = new Component;
Element *b = new sound::Beep (1, c, "beep");
c->run (); /* no beep */

Many non-persistent components (that is, components whose rendering spontaneously finishes just after starting) are similar to functions in functional or imperative languages and we will see examples where they are useful as models, not because we want to clone them but because we want to activate them explicitly and not when their parent is activated.

1.3. References in the tree

When building the tree, or later when implementing the application behaviour, it is often necessary to refer to existing components. djnn provides several ways of doing that.

1.3.1. C++ pointers

When programming in C++, the most efficient and natural-looking way of referring to a component is to use a pointer to it. That is what we have done in all examples so far, like with b and r in the following code:

Element* b = new Component (0, "border");
Element* r = new GUI::Rectangle (10, 10, 90, 90, b);

We will use C++ pointers at many places in this manual. Please be aware though that it this not a portable way of referring to djnn components: it does not transpose to ccmponents stored in XML files, for instance. For this, you need to use path references or alternative referencing systems.

1.3.2. Path references

When building the tree, or later when implementing the application behaviour, it is often necessary to refer to existing components. djnn provides several ways of doing that.

Element *c = new Component (0, "root");
new GUI::Rectangle (..., c, "r");
new sound::Beep (c, "b", true);
new Binding (Element::path ("r/press"), Element::path ("b"), c);

1.3.3. URIs

djnn supports the creation of your own naming systems, including URIs. This is especially useful for referring to components situated in other programs, possibly on other computers:

Element *c = new Component (0, "root");
new sound::Beep (c, "b", true);

new Binding (Element::URI ("ivy://remote/r/press"), Element::path ("b"));

1.3.4. Extended path references

djnn provides supports for the Xpointer standard defined by the Web consortium. To be completed.

1.4. Modifying the tree

To be written.

1.5. Executing your application

1.5.1. Execution phases

Let us come back to the simple complete application from chapter Setting up djnn. The execution of the main procedure can be decomposed in 3 phases:

Upcoming C++ code

The three phases are:

  • the construction of the tree;

  • the initial rendering of the tree or a portion of the tree, by a call to method run;

  • the runtime phase, in which the application tree waits for interactions. During this phases, the tree changes in reaction to end-user’s actions, or to any other external events. These changes are then interpreted into visible modifications (or any other perceptible change) of the UI through new renderings of the UI tree.

These phases are described in more details in in the following sections.

1.5.2. Initializing the tree

to be written

1.5.3. Initial rendering

The djnn tree is just a representation of your application and its user interface. Building it ensures that its components are ready to be activated, but it has no real effect. To really start interacting with the end-user, the tree must first be interpreted into perceptible effects: pixels on the screens, sounds, etc. This phase is called the initial rendering of the tree.

Rendering and execution

What does "rendering" mean? In djnn, rendering means executing. The term comes from computer graphics, and djnn extends it to all kinds of components. In computer graphics, images are built in two phases: first, a digital model of objects is created in memory, then the model is "rendered" as pixels on the screen.

Rendering is the operation by which a model is interpreted into its intended effect: displaying a shape on the screen, producing a sound, or just modifying data in the application. Being rendered (or interpreted) is the main role of components when they are executed, and every type of components encapsulates a given type of rendering. Chapters Building native components and Building modules explain how new component types can be created to create new effects.

Formally speaking, the execution of a tree is a sequence of activations of the components in the tree. When activated, composite components execute their children and atomic components trigger whatever rendering they are made for. Once your djnn tree is ready, you need to activate the part (or parts) of the tree that you want to be rendered when you launch your program. Usually you will run the root component of your application, which in turn will activate all its children, but you can decide to run only particular parts of it.

Rendering, or executing, a component is obtained by calling method run on this component. When this is applied to a container component, all its sub-components are executed in depth-first, left to right order; this means that the execution of the second child does not start before the first child and all its children have started executing. This ensures the tree traversal shown in the Introduction chapter.

You can control the initial rendering of the tree by choosing which components to execute. In a simple program, you will create a root component, fill it with children, then start it. But you can be more selective. For instance in the following code, component c2 and its children are not activated, which means that only one window will appear on the screen.

Element *c = new Component ();
Element *c1 = new Component (c);
new GUI::Frame (0, 0, 200, 200, c1, "frame 1");
Element *c2 = new Component (c);
new GUI::Frame (200, 200, 400, 400, c2, "frame 2");
c1->run ();

In order to have both windows appear, one should run c2. This is done by either calling c->run() instead of c1->run(), or calling c2->run() later during the execution of the application.

Running and stopping components

to be written.

Component order

The order in which components are executed or rendered is often important. Let us imagine, for instance, a hypothetical Morse module and the following code:

The order in which components are rendered is often important. Let us imagine, for instance, a hypothetical djnn::Morse:: module and the following code:

Element *c = new Component;
Element *c1 = new Component (c);
Element *c2 = new Component (c);
new Morse::Letter ('M', c2);
new Morse::Letter ('A', c2);
new Morse::Letter ('Y', c2);
new Morse::Letter ('S', c1);
new Morse::Letter ('O', c1);
new Morse::Letter ('S', c1);
new Morse::Letter ('D', c2);
new Morse::Letter ('A', c2);
new Morse::Letter ('Y', c2);
c->run ();

This example would emit the string ... --- ... -- .- -.-- -.. .- -.--, that is SOS MAYDAY. This illustrates how rendering works: the structure of the tree influences the rendering order. The tree is traversed in depth-first, left-to-right order. A component inserted before (at the left of) another component will be rendered before that other component.

Rendering context

Now let us come back to graphical user interfaces and examine how the rendering of a component can influence the rendering of other components in the tree. Consider the following example with two windows and two texts.

Element *c = new Component;
Element *f1 = new GUI::Frame (0, 0, 200, 200, "frame 1", c);
Element *t1 = new GUI::Text (10, 10, "Window 1", c);
Element *f2 = new GUI::Frame (200, 0, 200, 200, "frame 2", c);
Element *t2 = new GUI::Text (10, 10, "Window 2", c);
c->run ();

The four components in c are rendered in the order f1, t1, f2, t2, and the rendering of Frames influences the rendering of texts: text t1 is rendered in the first window, and text t2 in the second window.

This shows that the rendering of a component can affect the rendering of the components that are rendered after it. Actually, this is only true within the same component: components can influence the rendering of components that appear at their right in the same component, as well as their children.

1.5.4. Waiting for interactions

Although djnn makes it possible to write pure computation programs and sequential dialogues controlled by the computer through the command line, most applications you will write will wait for user input or external events during an indeterminate amount of time. The "system relay loop" component makes this possible in two different ways:

  • when running, it engages your program into a waiting loop or equivalent, until you decide to stop it,

  • it routes events traditionnally detected by the operating system: clock events, network events, and input events when appropriate.

After the initial rendering, interactive applications wait for user events, clock events, network events or any other events that will trigger their response, usually in a perceptible way. This is called the runtime phase. During this phase, the control of the application resides with the operating system (or with a main loop, depending what operating system you are using) until the application decides to quit in response to an event.

See section System relay in chapter Execution model for more details.

2. Defining basic behaviors

to be written.

3. Designing reusable components

To be written.

4. Behaviour bricks

to be written

Interacting with the user and the environment

1. Drawing

An essential part of djnn is its graphical user interface (GUI) module. The GUI module is a combination of graphics, drawing surface management, and input management. We focus here on graphics and a small subset of surface management. In this chapter you will learn the basics of drawing with djnn: basic graphical components, object grouping, transformations, adding color and gradients, text capabilities, clipping. This chapter also explains how to load SVG files to populate the djnn tree.

1.1. Getting started

The example below is one of the simplest applications with a GUI you can write. It does not do much, but the code demonstrates the basic code of every djnn program that involves a GUI:

  • load the appropriate modules

  • instantiate a frame

  • execute the application tree

  • wait for interaction

#include <djnnplusplus/core.h>
#include <djnnplusplus/GUI.h>
using namespace djnn;

main ()
{
  GUI::Frame f (0, 0, 200, 200, "my frame");
  f.run ();
  SystemHook.run ();
}

The first two lines import the core djnn module and the GUI module. The seventh line creates a Frame, that is a drawing surface that will appear on the display. Every program with a GUI must have at least one frame. The eighth line runs the frame, thus making it appear and stay on the display.

1.2. Windowing

The GUI module offers basic support for windowing: creating rectangular windows and sub-windows, managing the cursor. In a later chapter, a more complex model for windowing and display surfaces will be proposed. However, the current model should prove enough for most graphical applications.

1.2.1. Frames

Frames are djnn components that each represent a window on the computer screen. The window is managed by the operating system.

A frame defines an initial coordinate system. The (0, 0) coordinates represent the upper-left corner of the frame. The numeric value ot the x-abscissa increases from left to right, and the numeric value of the y-ordinate increases from top to bottom. This initial coordinate system is used to place graphical objects.

A frame applies to (contains) all the graphical components that are located after it in the same parent component. When two or more Frames are in the same parent, the first Frame applies to all components until the second Frame, and so on. For instance, to create two rectangles in two windows, write:

Component c;
GUI::Frame f1 (0, 0, 200, 200, &c);
GUI::Rectangle r1 (10, 10, 50, 50, &c);
GUI::Frame f2 (200, 0, 400, 200, &c);
GUI::Rectangle r2 (100, 10, 50, 50, &c);

You may find it surprising that a Frame is not a container in djnn. This is a deliberate choice: being aimed at multimodal user interfaces, djnn does not give a privileged place to graphics and windowing. The privileged place is occupied by Components. To build an object made of a window containing graphical objects, create a Component, put a Frame in it, then put graphical components.

1.2.2. Creating sub-windows

(to be written)

Note that an alternative to Frames for creating sub-windows is the use of Clips, described later in this chapter. With clips you can to obtain similar visual results, and more.

1.2.3. Cursors

Cursors are djnn components that allow you to control the mouse cursor. Like a graphical component, a cursor applies to the current Frame. When several cursors are specified for the same frame, the last one will apply.

Note: Cursors have only been tested on Windows.

1.3. Graphical shapes

Graphical shapes are provided as components that you add to the tree. The set of graphical components is similar to those from the SVG standard.

1.3.1. Basic shapes

djnn contains the following set of basic shapes: circles (Circle), ellipses (Ellipse), straight lines (Line), rectangles with optional round corners (Rectangle).

You can add a basic shape to your program by calling the constructor of this shape. new GUI::Rectangle () will create a new rectangle, and new GUI::Circle () will create a new circle. The djnn C++ API Reference Manual gives all the arguments you can give to specify the geometry of those graphical components, in addition to the ususal arguments for Components.

Shapes only describe the geometry of graphical objects. When used alone, they are displayed with default visual attributes.

1.3.2. Curves

Besides basic shapes, djnn can also draw arbitrary shapes with both straight line and cubic Bézier curves, with components Polygon and Path.

A polygon is a series of line segments. A path is a series of segments that can be straight lines, cubic Bézier curves or quadratic Bézier curves. A cubic Bézier curve is a curved segment that is defined with two end points and two control points. Each control point determines the shape of the curve by controlling one of the end point tangent vector.

Example of cubic Bézier curves

The polygon or path can optionally be closed (ie a straight line is drawn between the first point and last point), stroked and filled. The djnn C++ API Reference Manual details how to specify the geometry of curves.

Note
Mathematically speaking, the basic shapes from the previous section are equivalent to a curve that would construct the same shape.

1.3.3. Images

You can display a pixmap image with an Image component. djnn supports bitmap, gif, jpeg and png files.

If the image file supports transparency (not alpha-blending, only full transparency), djnn will render its transparent areas.

Note
You can apply the full set of transformations to an image. However, the result may sometimes be hard to read.

1.3.4. Texts

For displaying a text, you need to use an Text component.

Component root;
GUI::Frame f (0, 0, 200, 150, "my window", &root);
GUI::FillColor fc ("black", &root);
GUI::Text t (50, 50, "Hello world", &root);

root.run ();
SystemHook.run ();

A

1.4. Graphical style

You can control the appearance of graphical components by specifying how they should be rendered. This is done by using graphical components collectively named graphical style. If you have used UI programming toolkits in the past, it is important to note that with djnn you cannot specify the graphical style as an argument of a shape constructor, but that it is made of independent components. It reflects the fact that each style component modifies the graphical context within which all following shapes are drawn. Thus, if you put a red FillColor in the djnn tree, each following shape will be rendered with a red filling until a new FillColor or a NoFill is encountered in the tree.

1.4.1. Fill and stroke

Shapes and text can be filled (ie apply paint to the interior of the shape) and stroked (ie apply paint along the outline of the shape). A shape may be filled with a simple color (FillColor), a linear gradient (LinearGradient) or a radial gradient (RadialGradient), see Gradients for more details.

The outline can only be painted with a simple color (OutlineColor) but it can be customized with the following components:

  • width (OutlineWitdh)

  • cap style (OutlineCap)

  • join style (OutlineJoin)

  • miter limit (OutlineMiterLimit)

  • dash (DashArray, DashOffet)

1.4.2. Opacity

djnn provides a way to assign opacity values to fill and stroke so that the underlying graphics show through when you draw your shapes or images.

You can set the opacity of an object by adding an opacity component for the fill (FillOpacity) or the stroke (OutlineOpacity).

Example: fill opacity 100% (default) and 50%

Component c;
GUI::Frame f (0, 0, 200, 200, "my window", &c);
GUI::FillColor fc (200, 50, 50, &c);
GUI::Rectangle r1 (50, 50, 50, 50, &c);
GUI::FillOpacity fo (0.5, &c);
GUI::Rectangle r2 (50, 150, 50, 50, &c);

c.run ();
SystemHook.run ();

Opacity is a number between 0.0 and 1.0. Whereas a new fill or stroke colour encountered in the tree replaces the current fill or stroke colour in the graphical context, opacities are compounded by multiplying the current value with the new one.

1.4.3. Gradients

Perception is based on contrast, and color contrast is the easiest to create. Designers often want to use gradients rather than mere color juxtaposition. Gradients consists of continuously smooth color transition along a vector from one color to another, possibly followed by additional transitions along the same vector to other colors. djnn provides 2 types of gradients:

  • linear gradient (LinearGradient),

  • radial gradient (RadialGradient),

As in SVG, it is possible to add stops for each kind of gradient.

Component c;
GUI::Frame f (0, 0, 400, 300, "my window", &c);

GUI::LinearGradient lg (100, 20, 290, 20, &c);
lg.addStop (200, 167, 100, 0);
lg.addStop (213, 220, 224, 1);

GUI::Rectangle r1 (150, 10, 100, 40, 5, 5, &c);

GUI::RadialGradient rg (200, 200, 90, 200, 200, &c);
rg.addStop (255, 153, 194, 0.1);
rg.addStop (141, 163, 213, 0.9);

GUI::Circle (200, 200, 50, &c);

c.run ();
SystemHook.run ();

Example of gradients

1.4.4. Patterns

Filling with patterns is not yet implemented

1.4.5. Tiled images

Tiling is not yet available.

1.4.6. Fonts

For specifying the font used when rendering text, you can use the same font descriptions as in SVG. This includes font family (Helvetica, Arial, …), font size (12, 24, …), font style (italic, oblique) and font weight (bold, normal, lighter, bolder or a number in the [0, 100] range).

Component c;
GUI::Frame f (0, 0, 200, 200, "my window", &c);

GUI::FontFamily ff ("helvetica", &c);
GUI::FontStyle fs ("italic", &c);
GUI::FontWeight fw ("bold", &c);
GUI::Text t (10, 50, "Hello world", &c);

c.run ();
SystemHook.run ();

1.5. Geometrical transformations

Within djnn, each graphical context has a coordinate system inherited from its ancestor, the root coordinate system being that of the frame. The coordinates of a graphical shape are always relative to those of its context, and the coordinate system of a graphical context is always relative to that of its parent context. By default, a graphical context has an identity transformation attached to it, so that the coordinate system of the parent applies by default.

You can modify the current coordinate system, and thus change the way graphical objects are rendered, by adding geometrical transformations. All geometrical transformations are compounded in sequence; this can be interpreted as each transformation creating a new context for the interpretation of the following transformations and shapes. See below for a note concerning the importance of the order.

djnn offers usual transformations: translation, rotation and scaling. It also offers full homographies (also known as projective transformations), that is general 2D transformations expressed with a 3x3 matrix.

It must be emphasized that transformations act on the relation between two coordinate systems and they do not modify the coordinates of objects themselves, just the way they are rendered. You will see later how this allows you to have the same graphical objects rendered at two different scales in two different parts of your application tree. To figure out what is happening when you apply a transformation, you can imagine that the rendering engine draws each object inside a transparent sheet. To apply a translation, it moves the sheet of the given object; to apply a rotation, it spins this sheet. Similarly for scaling it modifies the sheet of the given component without touching the others sheets.

1.5.1. Translations

A translation has the effect of moving the graphical objects along the X and Y axes of their graphical context.

GUI::Translation t (50, 0);

1.5.2. Rotations

A translation has the effect of moving the graphical objects along the X and Y axes of their graphical context.

GUI::Rotation r (50, # the angle in degrees
                   0, # x coordinate of the rotation axis
                   10 # y Coordinate of the rotation axis
                  );

1.5.3. Scaling

A scaling scales up or down the graphical objects along the X and Y axis of their graphical context. The centre of the scaling can be specified too, it is expressed by its coordinates in the graphical context that applies to the scaling.

GUI::Scaling s (10, 10, # scaling factors along x and y
                0, 0    # centre of the scaling
                );

1.5.4. Homographies

You can also perform more complex transformations, such as a mirror effect, by directly manipulating the matrix of an homography. The new coordinates (x2, y2) are derived from an orignal point (x1, y1) as follows:

| x2 |   | a c e |   | x1 |   | a.x1 + c.y1 + e |
| y2 | = | b d f | x | y1 | = | b.x1 + d.y1 + f |
| 1  |   | 0 0 1 |   | 1  |   |       1         |

For example, you can permute the X and Y coordinates with the following transformation matrix

| x2 |   | 0 1 0 |   | x1 |   | y1 |
| y2 | = | 1 0 0 | x | y1 | = | x1 |
| 1  |   | 0 0 1 |   | 1  |   | 1  |

This is expressed as follows:

new GUI::Homography (0, 1, 0,
                     1, 0, 0,
                     0, 0, 1);

It gives the following effect:

Permute X and Y axes

1.5.5. Order of transformations

Each graphical context has a 3x3 transformation matrix. When the object is created, this matrix is set to identity. Later, when a translation (similarly rotation, scaling, skewing) is added to a graphical context, its transformation matrix is left-multiplied by a translation (similarly rotation, scaling, skewing) matrix.

In a composite transformation/sequence of transformations, the order of individual transformation is important because composite transformations are built from right to left (i.e. when a new transformation is applied the current transformation matrix is left-multiply by the matrix of the transformation).

For example, if you first rotate then translate, you get a different result than if you translate, then rotate. Indeed, the matrix produced by the product (translation x rotation) is almost always different from the matrix obtained by the product (rotation x translation)

A rotation of alpha degrees followed by a translation by (dx, dy):

         | cos(alpha)  -sin(alpha)  dx |
Matrix = | sin(alpha)   cos(alpha)  dy |
         |     0            0       1  |

A translation by (dx, dy) followed by a rotation of alpha degrees:

         | cos(alpha)  -sin(alpha)  cos(alpha).dx - sin(alpha).dy |
Matrix = | sin(alpha)   cos(alpha)  sin(alpha).dx + cos(alpha).dy |
         |     0            0                     1               |

1.6. Structuring graphics

1.6.1. Grouping graphics

It is often useful to bundle graphical components together so that they can be manipulated easily as a whole. The main usages are:

  • bundling components together so they can be cloned, hidden, moved and more as a whole,

  • bundling several components together so that they form a new single component composed of several simpler one reacting as a whole to events,

  • interposing a new coordinate system in a hierarchy of components. This can be very useful to manage panning, zooming and other kind of viewing transformation. See Coordinate transformations for an explanation of the transformation system,

  • composing attributes such as opacity with those of their children components (see Opacity for more details),

Grouping graphics in djnn is obtained by creating an empty component and putting graphical components in it. In the example below, we introduce a component named OutlineColor that makes all graphical components in the same group red.

Component group;

GUI::OutlineColor oc ("red", &group);
GUI::Text (50, 50, "Hello world", &group);
GUI::Rectangle (40, 40, 130, 20, &group);
group.run ();

1.6.2. Clipping your graphics

to be written

1.7. Using SVG files

The djnn::GUI module is able to create a tree of graphical components from a file formatted in the SVG format. This means that instead of instantiating graphical objects, resources and transformations with C++ constructors, you can load them from a SVG file.

When applied to an SVG file, the subroutine Component::load or Component::load creates a subtree of graphical components (objects, resources, transformations). You can then use the root of this subtree as a Component. Or, taking advantage of the fact that SVG elements can be named, use it as a library of graphical models, using subroutines find and clone to instantiate the parts of the file you are interested in. See chapter Creating models and instantiating for more details.

2. Animation

to be written

3. Display surfaces

to be written

4. Input devices

to be written

5. The GUI module

to be written

6. Files and directories

This chapter describes how to manage files and directories from your applications. It shows how to be notified of changes made to a given file or in a given directory, how to read the contents of directories and files, and how to create new ones.

6.1. Introduction

Most computers propose a file system as an option for the long time storage and retrieval of data, independently of the type of storage media. File systems usually consist of a hierarchical collection of files and directories, the latter being special files that "contain" other files. The djnn files module allows you to interact with these files, which are considered as components residing outside of the application tree.

In terms of user interaction, files and directories are not exactly an interaction modality. Nevertheless, being notified of their changes can play a part in an interactive application. For instance the file selector on Mac OS X updates its contents dynamically, whenever a file is created or deleted in the directory that is currently displayed. The files module allows you to do that. You might also want to write an application that reacts to the addition of files to a given location by reading them or copying them. In that sense, the file system is as meaningful an input source as a mouse, it is just of a different nature.

One of the peculiarities of the file system is that an application can both create files and discover existing files. Abusing the user interaction terminology, it is both an input and output modality. This will allow us to revisit once again the extended djnn tree, that is the representation of the environment of your application as a larger djnn tree. We have seen in the Displays and Input management chapters how existing components from this extended tree can be used as event sources. Here, we will do that and also create components in the extend tree.

6.2. Locating files and directories

The following example shows how one can access files and directories as djnn components.

upcoming C++ code

In this example, f and d are components if the corresponding files exists, 0 otherwise. Like input devices, you can add the resulting components to your application tree wherever you want as long as you do not expect them to produce anything when that part of the tree is activated. You will rather want to use them as input sources, or to use their children in connectors.

The naming system used to locate the files is exactly the same as in classical URLs. It works with absolute paths, as in the example obove, as well as for relative paths. In the latter case, the path in interpreted relative to where your program was launched from in the file system. For example, on a Unix-like operating system, if your program is stored as /usr/bin/myapp, you launch it from /home/mydir/mysubdir and you use file:../myfile.svg, this will search for /home/mydir/myfile.svg.

6.3. Reading data

6.3.1. Iterating directories

to be written

6.3.2. Reading files

to be written

6.4. File events

Various events can occur on a file or a directory: a file can be written to by another program, its permissions can be modified, a file can be added or removed from a directory, files and directories can be deleted, and so on.

the file or directory components.

6.5. Creating files and directories

to be written

6.6. Writing to files

to be written

7. Network interfaces

to be written

8. MacBook-specific components

to be written

Beyond the application tree

1. Writing extensions

to be written

2. Building native components

to be written

3. Writing a module

to be written

4. Parsing XML files

to be written

5. Interacting with the operating system

to be written