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 Perl 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 Perl 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 Perl 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 Perl 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 Perl.

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 Perl 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 Perl API reference manual (not available yet) contains the description of all public classes and methods in the Perl API, as well as the XML formats defined by djnn.

  • the djnn Perl 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 Perl 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 Perl API comes as a set of Perl libraries, that are partly architecture-dependent and partly architecture-independent. To use them, you need:

  • to have Perl installed on your computer

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

  • to have the djnn libraries installed in the appropriate locations so that the Perl interpreter can find them.

Once this is done, using djnn is like using any other Perl library: you need some proficiency in the Perl 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

The djnn development team proposes binary packages for some operating systems. Here are instructions on how to use them.

(to be written)

3.3. Building from the sources

If you have obtained a source version of the djnn Perl API and you already have the djnn libraries installed, this section will help you to build a binary version. If you have installed the djnn Perl API as one or more binary packages, you can skip this section.

In principle, building the djnn Perl API should be as simple as:

% cd perl
% make

But you might have to install a few tools before that, or you might want to compile only a particular library, or to pass compilation options. This section gives indications that should help you.

3.3.1. Prerequisites

(to be written)

3.3.2. Directory structure

(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. Programming conventions

djnn respects a number of programming conventions in its use of Perl:

  • Perl is used in an object oriented style. In the Perl vocabulary, classes are named packages.

  • constructors are named new, as in C++ or Java.

  • for its external programming interface, djnn uses a named arguments style of passing arguments, which allows to pass arguments in any order. For instance, new GUI::Rectangle (-width => 10, -height => 30) is equivalent to new GUI::Rectangle (-height => 30, -width => 10).

  • for all types of components, the constructor accepts as arguments all the attributes defined in the corresponding XML format. For instance, width and height in the examples above are the same as the width and height attributes of rectangles in SVG. Constructors may have extra arguments though.

  • 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).

  • despite Perl good practice requiring to avoid invading the global namespace (::, or main::) with one’s code, for the sake of simplicity djnn exports its classes and namespaces to the global namespace. Consequently, you can use new Component instead of new djnn::Component, or new GUI::Text instead of new djnn::GUI::Text. This default behaviour can be modified, see chapter Structuring programs for details.

In addition to these conventions, here is some advice for your programs:

  • use the my syntax to declare variables, so that Perl can detect typographic mistakes;

  • use the use strict; clause in your programs. This will make Perl detect many mistakes;

  • use the -w option of Perl when launching programs to detect even more mistakes. You can do that in Unix-like systems by writing #!/usr/bin/perl -w at the beginning of your programs.

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

3.5. Compiling your own djnn code

3.5.1. Compiling and running an application

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

#!/usr/bin/perl -w

use strict;
use djnn::simple;
use djnn::GUI;

new GUI::Frame (-width => 200, -height => 80);
new GUI::Text (-text => "Hello World", -x => 50, -y => 30);
run ();

Now run the program. In a Unix-like environment, running can be done by typing perl hello.pl from the directory where you saved the file, or by making the file executable (chmod +x hello.pl) then typing hello.pl. In a Windows environment, you can double-click the icon of hello.pl.

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.

my $b = new Component (-id => 'border");
my $r1 = new GUI::Rectangle (-parent => $b,
                                -width => 100, -height => 50);
my $r2 = new GUI::Rectangle (-parent => $b, -x => 2, -y => 2,
                                -width => 96, -height => 46);

my $l = new Component (-id => 'label');
my $t = new GUI::Text (-text => "hello", -x => 30, -y => 20);
$l->add (-children => [$b, $t]);

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.

my $app = new Component (-id => "Loaded components");
my $e1 = load Component (-parent => $app, -uri => "test.svg#red-circle");
my $e2 = load Component (-parent => $app, -uri => "component.xml");

$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).

Upcoming Perl code

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. Perl references

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

my $b = new Component (-id => "border");
my $r = new GUI::Rectangle (-parent => $b)

We will use Perl references 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.

my $c = new Component (-id => 'root');
new GUI::Rectangle (-parent => $c, -id => 'r', ...);
new sound::Beep (-parent => $c, -id => 'b', -model => 1);
new Binding (-parent => $c, -source => 'r/press', -action => 'b');

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:

my $c = new Component (-id => 'root');
new sound::Beep (-parent => $c, -id => 'b', -model => 1);

new Binding (-parent => $c, -source => 'ivy://remote/r/press', -action => '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 three phases:

Upcoming Perl 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. 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.

my $c = new Component;
my $c1 = new Component (-parent => $c);
new GUI::Frame (-parent => $c1);
my $c2 = new Component (-parent => $c);
new GUI::Frame (-parent => $c2);
$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:

my $c = new Component;
my $c1 = new Component (-parent => $c);
my $c2 = new Component (-parent => $c);
new Morse::Letter (-parent => $c2, -letter => 'M');
new Morse::Letter (-parent => $c2, -letter => 'A');
new Morse::Letter (-parent => $c2, -letter => 'Y');
new Morse::Letter (-parent => $c1, -letter => 'S');
new Morse::Letter (-parent => $c1, -letter => 'O');
new Morse::Letter (-parent => $c1, -letter => 'S');
new Morse::Letter (-parent => $c2, -letter => 'D');
new Morse::Letter (-parent => $c2, -letter => 'A');
new Morse::Letter (-parent => $c2, -letter => 'Y');
$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.

my $c = new Component;
new GUI::Frame (-parent => $c);
new GUI::Text (-parent => $c, -text => "Window 1");
new GUI::Frame (-parent => $c);
new GUI::Text (-parent => $c, -text => "Window 2");
$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 components

to be written

4. Behavior 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

use djnn;
use djnn::GUI;

my $f = new GUI::Frame ();

$f->run;
SystemHook->run ();

The first two lines import the core djnn module and the GUI module. The fourth 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 sixth 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:

my $c = new Component;
new GUI::Frame (-parent => $c);
new GUI::Rectangle (-parent => $c, -x => 0, -y => 0, ...);
new GUI::Frame (-parent => $c);
new GUI::Rectangle (-parent => $c, -x => 0, -y => 0, ...);

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 Perl environment 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

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.

use djnn;
use djnn::GUI;


my $root =  new Component;
new GUI::Frame (-parent => $root, -width => 200, -height => 150);

new GUI::FillColor (-parent => $root, -color => 'black');
my $text = new GUI::Text (-parent => $root, -text => 'Hello world!',
                          -x => 50, -y => 50);

$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%

use djnn;
use djnn::GUI;

my $c = new Component ();
my $frame = new GUI::Frame (-parent => $c);

new GUI::FillColor (-parent => $c, -r => 200, -g => 50, -b => 50);

new GUI::Rectangle (-parent => $c,
                       -x => 50, -y=> 50,
                       -with => 50, -height => 50,
                       );

new GUI::FillOpacity (-parent => $c, -alpha => 0.5)

new GUI::Rectangle (-parent => $c,
                        -x => 50, -y => 150,
                        -width => 50, -height => 50);
}

$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.

use djnn;
use djnn::GUI;

my $c = new Component ();
my $frame = new GUI::Frame (-parent => $c,
                            -width => 400, -height => 300);

my $lg = new GUI::LinearGradient (-parent => $c,
                                  -x1 => 100, -y1 => 20,
                                  -x2 => 290, -y2 => 20);
$lg->addStop (-r => 200, -g => 167, -b => 100, -offset => 0);
$lg->addStop (-r => 213, -g => 220, -b => 224, -offset => 1);

my $r1 = new GUI::Rectangle (-parent => $c,
                             -x => 150, -y => 10,
                             -width => 100, -height => 40,
                             -rx => 5, -ry => 5);

my $rg = new GUI::RadialGradient (-parent => $c,
                                  -cx => 200, -cy => 200,
                                  -r => 90,
                                  -fx => 200, -fy => 200);
$rg->addStop (-r => 255, -g => 153, -b => 194, -offset => 0.1);
$rg->addStop (-r => 141, -g => 163, -b => 213, -offset => 0.9);

$circ = new GUI::Circle (-parent => $c,
                         -cx => 200, -cy => 200,
                         -r => 50);

$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).

use djnn;
use djnn::GUI;

my $c = new Component ()
my $frame = new GUI::Frame (-parent => $c);

my $family = new GUI::FontFamily (-parent => $c,
                        -family => 'helvetica',
                        );

my $style = new GUI::FontStyle (-parent => $c,
                        -style => 'italic',
                        );

my $weight = new GUI::FontWeight (-parent => $c,
                        -weight => 'bold',
                        );

my $text = new GUI::Text (-parent => $c,
                        -text => 'Hello world!',
                        -x => 10, -y => 50,
                        );


$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.

new GUI::Translation (-tx => 50, -ty => 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.

new GUI::Rotation (-a => 50, # the angle in degrees
                   -cx => 0, # x coordinate of the rotation axis
                   -cy => 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.

new GUI::Scaling (-sx => 10, -sy => 10);

The centre of the scaling can be specified with the -cx and -cy arguments; it is (0, 0) by default.

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 (-m11 => 0, -m12 => 1, -m13 => 0,
                     -m21 => 1, -m22 => 0, -m23 => 0);

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.

my $group = new Component ();

new GUI::OutlineColor (-parent => $group,
                       -color => 'red');

new GUI::Text (-parent => $group,
               -text => 'Hello world!',
               -x => 50, -y => 50);

new GUI::Rectangle (-parent => $group,
                    -x => 40, -y => 40,
                    -width => 130, -height => 20);
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 Perl 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

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

use djnn::files;

my $f = find Element (-uri => 'file:///tmp/foo');
my $d = find files::Dir (-uri => 'file:///tmp/bar');

In this example, $f and $d are components if the corresponding files exists, undef 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

to be written

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

This chapter explains how to create components or parts of components in Perl code. That is useful for situations not covered by current djnn modules, for managing legacy code and for quick and dirty experiments. The chapter introduces callbacks written in native code, actions hooked at key phases of the life cycle of a component, component creation, and common operations on the djnn tree available from your C code. It also provides advice about when and how to extend components with Perl code.

1.1. Introduction

djnn’s model-driven approach creates a border line between two realms. On one side of the border lies what you can express with the models provided by djnn, through components and properties. This sums up to what can be stored as XML files, basically. That is what we have done so far in this guide: with a few exceptions, the only Perl code you wrote was for adding components to an djnn tree and running it.

On the other side of the border is the realm of the Perl programming language. While it is desirable to create most of your applications with the elements provided by the djnn realm, that is not always possible, and you will sometimes want to write Perl code.

This chapter explains some of the bridges that cross that border, that are available when writing extended components. This includes event callbacks, event notifications, and customisation of the rendering. Although it is necessary only for customisation of the rendering, you may find it useful to create a Perl component subclass.

1.2. Wrapping Perl callbacks

djnn proposes various control mechanisms, such as the Binding, the Finite State Machine, the Connector, the Watcher, and the Switch. You can use them to control existing components, but you can also use some of them to transfer control to Perl code. The trick consists in wrapping your Perl code into djnn components, and this section explains the most basic wrapper.

The most basic solution is what is usually called the callback: a Perl function that you define and that the system calls in given circumstances. This is obtained in djnn with the PerlNative component, created from a simple Perl subroutine. Seen from djnn, it is a component. Seen from Perl, it is a callback subroutine. See for instance how to use it with a Binding:

sub eol {
 print "\n";
}

my $c = new Base::Clock ();

my $n1 = new PerlNative (-code => sub { print '.'; });
my $n2 = new PerlNative (-code => &\eol);

new Binding (-source => $c, -spec => 100, -action => $n1);
new Binding (-source => $c, -spec => 1000, -action => $n2);

Two syntaxes are available, as always with Perl subroutines: either a reference to an existing subroutine, or a subroutine defined inline in your code. Actually, two more equivalent syntaxes are available so as to spare you from creating a component explicitly:

new Binding (-source => $c, -spec => 100, -action => sub {print '.';});
new Binding (-source => $c, -spec => 1000, -action => \&eol);

A PerlNative component can be used wherever you would use a component: like here, as the action of a Binding, or as the action of a Watcher or a FSM transition, or even as a child of a Switch or a Component. Just make sure that you understand when these control components activate your PerlNative: upon a given event for the Binding, a given transition for the FSM, the activation of a given branch in a Switch, or the activation of the Component itself when you add your component as a child.

1.3. Customizing components in Perl

Leaving aside its software encapsulation capabilities, a component behaves just like an ordered list of elements. Rendering a component consists in rendering all its elements successively. However, you may at times want to interfere with the rendering, for instance for making layout computations before or after all children have been rendered, or even between the rendering of two children. For instance, imagine that you create a simple label made of a text and a rectangle, and that you want the size of the rectangle to be computed from the size of the text: as djnn does not yet provide advanced geometrical features, you will have to make the computations yourself. For doing that, you will have to customize the rendering of your component. The same applies to other phases of the life cycle of elements.

There are two ways of customizing components:

  • by adding actions that are bound to events in the life cycle (experimental feature);

  • by creating a subclass of djnn::Component as explained in the next chapter.

1.3.1. Understanding the life-cycle of components

So far we have mentioned a few events in the life of a component or an element. The most important are creation and rendering. These events are part of a more complex life-cycle that can be decribed by a finite state machine (see figure below).

To be completed.

Note: the life-cycle of an element or component can be extended to new events. This topic is currently reserved for chapter Writing djnn modules.

1.3.2. Creating your own component class

Some component extensions in Perl require that you define a subclass of djnn::Component. In other cases, it is not mandatory but you might find it useful to structure your code. The following code shows how to do it. It mainly consists of defining a Perl package and a constructor subroutine named new.

use djnn;
package MyComponent;

use strict;
use vars qw/@ISA/;
@ISA = qw/djnn::Component/;

sub new {
  my $proto = shift;
  # ... you might want to handle options yourself;
  #  here, they are all passed to Component...
  my $self = new PerlComponent (@_);
  bless $self, $proto;

  # ...store your own data
  my $self->{foo} = random ();

  return $self;
}

You can then define additional methods as explained in section Customising rendering, or add instructions to subroutine new.

sub RUN {
  my ($self, $context) = @_;

}

sub STOP {
}

1.3.3. Storing data in components

Components are Perl objects, implemented as Perl hash tables. As such, you can dynamically add new fields for extension purposes. Example :

$c->{my_field} = 3.1415;

The only constraint is to avoid field names that start with _c, which are reserved for the internal implementation of class Component.

Of course, the data you create that way is unknown by the djnn tree. It is up to you to manage it in your callbacks.

1.4. Operations in Perl code

When writing a plain callback (for Bindings and FSMs), a watcher callback (for watching properties) or the rendering or your own components, you can trigger whatever side effects you want outside of the djnn tree: writing to a file or a console, updating a data base, etc. You may also want to apply operations to the djnn tree, emit events, or propagate a data flow.

1.4.1. Retrieving the execution context

WORK IN PROGRESS

A callback used as an action in a Binding has access to information about why and from where it was called. The information is obtained with accessor methods defined in the NativeCode class. The most interesting information for callbacks used in Bindings is:

  • the source of the event, obtained with NativeCode::source

  • arguments passed when using an "array-style callback" as the action, obtained with NativeCode::userArgs

  • source-specific arguments, obtained with NativeCode::sourceArgs

A callback used as an action in a Watcher has access to information about why and from where it was called. The information is obtained with accessor methods defined in the NativeCode class. The most interesting information for callbacks used in Watchers is:

  • the owner of the changed properties, obtained with NativeCode::source

  • a test that says whether a property has changed: NativeCode::changed

1.4.2. Modifying the djnn tree

WORK IN PROGRESS

You may want to modify the djnn tree. The available operations are:

  • adding an element to the tree. You can use the add and new methods just like when you created the initial djnn tree. For the time being, however, you need to call an additional method named Element::synchronize on the component that you want to refresh after the change. Please be careful about synchronizing only the smallest components possible, otherwise your application will become too slow. In future versions of djnn the call to synchronize should become useless.

  • removing an element from the tree. Not supported yet, sorry.

  • moving an element in the tree, so as to change its grouping, its rendering order, or its rendering context. Currently you need to use remove followed by add.

  • changing properties in djnn elements. See paragraph Updating properties below.

1.4.3. Emitting events

We have seen that djnn modules each bring their own components that can be used as sources: windows, graphical objects, clocks, etc. But this is only the beginning of the event chain in your application: you want your own components to emit events, so that other components can in turn react to them, and so on.

For this, you need to remember the rules of bindings: the action is activated whenever the source (or more precisely the element designated in the source by the detailed specification) is activated. So, in order to allow other components to bind to your component, you just need to provide them with elements they can bind to, and to have them activated when appropriate. This starts with your component itself: it is a legitimate source and you can create bindings on it. But very often you will want to create specific children in your component.

The example below shows a component that contains a rectangle and an empty component press that is activated when the rectangle is pressed. In other words, a "press" on the button is a "press" on the rectangle.

my $c = new Component (-id => 'button');
my $rect = new GUI::Rectangle (-parent => $c, -id => "rect", -x => 40, -y => 20);
my $press = new Component (-parent => $c, -id => 'press');
new Binding (-parent => $c, -source => $rect, -spec => 'press', -action => $press);

new Binding (-source => $c, -spec => 'press', -action => ...);

This example is very simple and we will see later that there is a simpler way to just re-export an element like our rect/press here. But it is rare that you want to give the exact same name, or even the exact same structure, to your public event sources. In many cases you will want to use more complex components than this: an OR component that is activated whenever one of several components is activated, a state or a transition of a state machine, etc.

Your components can emit events with the notify method. Given a reference to a component $c, you can emit an event like this:

--- $c→notify (-foo ⇒ 10, -bar ⇒ 5); ---

In this call foo and bar represent

Currently there is no explicit declaration of the event vocabulary that a component can send or receive. It is thus virtually illimited. We warn the user of djnn that in order to introduce better software engineering practices this may change in the future.

1.4.4. Updating properties

WORK IN PROGRESS

Your components can change the state of properties.

1.5. What extension strategy?

Several options are available to you when you decide that you need to create extensions. The following options are listed to help you make your decision.

  1. No extension. Maybe you do not need to create extensions at all, and you can do with assembling existing components. Please take a minute to think it over before writing an extension. Bear in mind that code with extensions will be harder to port to other djnn rendering environments: you lose the benefits of model-driven development. Have you looked at the examples provided with djnn? Maybe you can do without an extension.

  1. Creating a PerlNative component. The most simple way for simple extensions. Your application becomes harder to port to another runtime environment.

  2. Creating a component in Perl, deriving the PerlComponent class. This allows you to control how your component is executed and stopped. Your application becomes harder to port to a new runtime environment.

  3. Creating a component in C and wrapping it in Perl. It can be either a NativeCode component or a new class of components, refer to the C library guide for how to create it. This is a bit complex, but you get both performance and ability to use it in any djnn runtime environment.

  4. Creating a module. When you have devised a set of components that you deem reusable enough, you can decide to create an djnn module. The components you have defined become a programming interface for other programmers. If your components are obtained by only assembling djnn components, you can decide to make a pure XML module that will be portable to any djnn runtime environment. See chapter Creating djnn modules for more details.

  5. Creating a native module in C or Perl. Native modules are the ultimate extensions to djnn. In general, they are used to provide new interaction modalities. If you provide XML parsers for the new elements in your module as well as an implementation or wrapping of the module in each language supported by djnn, users of your module will be able to port their application from one environment to another. See chapter Creating djnn modules for more details.

2. Creating native components

to be written

3. Creating a module

to be written

4. Parsing XML files

to be written

5. Connecting to the main loop

to be written