Support
Contribute
Contact
Tracker
Navigation
Personal tools
 

Development with Edje: Introduction


The following text is quoted directly from the introduction in the Edje API Reference by Carsten 'rasterman' Haitzler:

Edje is a complex graphical design & layout library.

It's purpose is to be a sequel to "Ebits" which to date has serviced the needs of Enlightenment development for version 0.17. The original design parameters under which Ebits came about were a lot more restricted than the resulting use of them, thus Edje was born.

Edje is a more complex layout engine compared to Ebits. It doesn't pretend to do containing and regular layout like a widget set. It still inherits the more simplistic layout ideas behind Ebits, but it now does them a lot more cleanly, allowing for easy expansion, and the ability to cover much more ground than Ebits ever could. For the purposes of Enlightenment 0.17, Edje should serve all the purposes of creating visual elements (borders of windows, scrollbars, etc.) and allow the designer the ability to animate, layout and control the look and feel of any program using Edje as its basic GUI constructor. This library allows for multiple collections of Layouts in one file, sharing the same image database and thus allowing a whole theme to be conveniently packaged into 1 file and shipped around.

Edje, unlike Ebits, separates the layout and behavior logic. Edje files ship with an image database, used by all the parts in all the collections to source graphical data. It has a directory of logical part names pointing to the part collection entry ID in the file (thus allowing for multiple logical names to point to the same part collection, allowing for the sharing of data between display elements). Each part collection consists of a list of visual parts, as well as a list of programs. A program is a conditionally run program that if a particular event occurs (a button is pressed, a mouse enters or leaves a part) will trigger an action that may affect other parts. In this way a part collection can be "programmed" via its file as to hilight buttons when the mouse passes over them or show hidden parts when a button is clicked somewhere etc. The actions performed in changing from one state to another are also allowed to transition over a period of time, allowing animation.

This separation and simplistic event driven style of programming can produce almost any look and feel one could want for basic visual elements. Anything more complex is likely the domain of an application or widget set that may use Edje as a convenient way of being able to configure parts of the display.

Contents

Meet Evas

From a developer's point of view, we can't expect to understand how Edje works without going through a brief introduction about Evas first.

Evas is canvas library that can be used to render various types of graphical elements (text, lines, images, etc.) through different back ends (Frame buffer devices, X11, OpenGL, etc.) using almost the same code and independently of the platform. Once a canvas is created, each of these graphical elements is included in the form of an Evas object while the canvas itself keeps track of a list of objects it contains along with their state, events and rendering taking a big load off the developer's shoulders.

Evas is not limited to the rendering of primitive objects, the developer can define new object types by assembling multiple primitives, these objects are know as smart objects and they can be included to a canvas and manipulated through the same API like any other object.

When a developer wants to build an interface consisting of elements defined using Edje, he does so through the Edje API which in itself could be seen as a method for creating smart objects with a specific format. Edje objects can be seen as just an specific type of Evas smart objects.

Meet Ecore

Reading through the introduction of the Ecore API Reference you can find it described as a "library of convenience functions". Ecore provides shortcuts to common methods of interaction between different members of the EFL and system resources like dbus.

In which way is Ecore relevant to Edje? Simply put, we need an Evas canvas to render our Edje objects and Ecore is the simplest way to get an Evas canvas up and running. Throw in the convenience of several already wrapped functions and we can imagine several reasons for using it alongside Evas and Edje.

The name of the Ecore library dealing with Evas is named (conveniently) Ecore_Evas and the name of every function included in it uses the ecore_evas prefix. Ecore_Evas is a "canvas wrapper" and its used to setup the drawing device for the canvas to render the objects.

This wrapper is intended to support each backend that Evas supports with its respective (and sometimes unique) attributes, for example, a regular window in a common windowing environment might have a "title" attribute while a frame buffer device normally does not.

API Basics

Now that we understood the reasons to include the Ecore and Evas libraries in an article about Edje, we can finally get our hands dirty with some code.

If you read through the API of any Enlightenment Foundation Library, you will probably see that every function has the same format: a library_name_ prefix, followed by an action (or method) to apply to an instance of an object created with said library, passed as the first parameter of the function, for example, edje_object_file_set(object,filename,group);. The exception (there is always one), are functions that refer to the library itself, which usually do not have any arguments.

Including and displaying an Edje object in a window is a process of a few, well defined steps.

Setting up the canvas

First things first, we need to include the header files and initialize the libraries:

    #include <stdlib.h>
    #include <Evas.h>
    #include <Ecore.h>
    #include <Ecore_Evas.h>

    int main() {
        if (!ecore_init()) return EXIT_FAILURE;
        if (!ecore_evas_init()) return EXIT_FAILURE;
    ...

All initialize-able Enlightenment Foundation Libraries do it with a function in the format: library_name_init(). Both Ecore and Ecore_Evas need to be initialized before being used and both will return success or failure using standard C values (0 is failure, any other number is success), hence the if (!*).

In order to render a canvas its necessary to create a canvas wrapper that will host it and to store a pointer to it for future reference:

    ...
    Ecore_Evas  *ecore_evas = NULL;
    ecore_evas = ecore_evas_software_x11_new(NULL, 0, 0, 0, 800, 600);
    if (!ecore_evas) return EXIT_FAILURE;
    ...

While the software_x11 engine is being used in this example, its possible to use any other supported engine by simply changing the second line to ecore_evas_ENGINE_name_new(..). A list of supported engines and their parameters can be found in the Official API reference.

Once the canvas wrapper has been setup we need to change its state to visible:

    ...
    ecore_evas_title_set(ecore_evas, "Example Application");
    ecore_evas_name_class_set(ecore_evas, "testapp", "Testapp");
    ecore_evas_show(ecore_evas);
    ...

While the first two lines are optional, it is useful to see the way we set the title name and class of the window that will host our canvas. The third function sets the given canvas wrapper to visible, this can be reversed later with ``ecore_evas_hide(..)``.

The canvas wrapper is ready to go, but the actual canvas that will be used to draw our Edje objects is nowhere to be found:

    ...
    Evas *evas = NULL;
    evas = ecore_evas_get(ecore_evas);
    ...

The function ecore_evas_get(..) returns a pointer to the canvas housed in the canvas wrapper, this is the pointer we need in order to include our Edje objects later.

The execution loop for the program can also be conveniently handled by Ecore:

    ...
    ecore_main_loop_begin();
    ...

Once ecore_main_loop_begin(..) has been called, both the canvas wrapper and the canvas itself will be drawn in their current state (a 800x600px empty window in this case). Ecore will continue to loop until an event handled by him occurs.

Once the execution of the main loop has finished it's a good practice to shut down any library we initiated:

    ...
    ecore_evas_shutdown();
    ecore_shutdown();
    }
    ...

Including the Edje Object

Now that we know how to create the necessary environment for our Edje objects to thrive we need to create an object and include it in our application. As usual we begin by including the necessary files and initializing the library:

    ...
    #include <Edje.h>

    ...
    int main() {
        ...
        if (!edje_init()) return EXIT_FAILURE;
        ...

Once a pointer to our canvas has been setup, we need a pointer to a valid Evas object to insert our Edje object:

    ...
    Evas_Object *edje = NULL;
    ...
    edje = edje_object_add(evas);
    edje_object_file_set(edje, "testfile.edj", "testgroup");
    ...

Both functions are specific to Edje, in the first case we use edje_object_add(..) to create a pointer to an Evas object and edje_object_file_set(..) to add the contents represented by "testgroup" in "testfile.edj".

As with any other Evas object we need to instruct Evas to make it visible, but not before adjusting the object inside the canvas or since we are only showing one object, adjust the canvas to the size of our object:

    ...
    Evas_Coord width, height;
    ...
    evas_object_move(edje, 0, 0);
    edje_object_size_min_get(edje, &width, &height);
    evas_object_resize(edje, width, height);
    ecore_evas_resize(ecore_evas, width, height);
    evas_object_show(edje);
    ...

First we use evas_object_move(..) to move our Edje object to the left-most, up-most corner of the canvas. The function edje_object_size_min_get(..), gets the minimal possible size of the object and evas_object_resize(..) changes the current size of the object to those values.

Before the end, we resize the canvas to the same values the object has with ecore_evas_resize(..) and (finally) instruct Evas to show the object.

Events with Ecore

The Ecore loop maintains a list of event "types" and a list of event "handlers" (both extensible). And remains idle until a listed signal is detected.

For example, we could create an additional function to handle the process of shutting down the libraries when closing the application:

    ...
    int 
    good_bye(void *data, int type, void *event)
    {
        //Remove the handler for no practical reason but showoff API
        if (ecore_event_handler_del(close)) printf("Handler deleted\n");
    
        printf("Good bye! \n");
        ecore_main_loop_quit();
        ecore_evas_shutdown();
        ecore_shutdown();
    }
    ...

And we could just plug it into our app using:

    ...
    Ecore_Event_Handler* close = NULL;
    ...
    close = ecore_event_handler_add(ECORE_EVENT_SIGNAL_EXIT,good_bye,"data");
    ...

The next time a signal of type ECORE_EVENT_SIGNAL_EXIT is received, the function good_bye will be executed.

Events with Evas

Each Evas object is capable of having its own list of accepted events, these list function similarly to the Ecore lists.

Using the function evas_object_event_callback_add(obj,type,func,data) we set a "handler" for signals of a given type emitted by that object. The function evas_object_event_callback_del(obj,type,func) is used to dispose the callback.

Events with Edje

We have established that Edje objects are rendered inside an Evas canvas where they behave like Evas objects, it makes sense to think they are capable of maintaining their own callback list as Evas objects do.

The API for maintaining the callbacks is pretty similar to the analogous Evas functions. For example, an alteration of the previous Ecore example, this one implements closing the application as a result of an event in the Edje Object:

    ...
    void
    good_bye(void *data, Evas_Object *o, const char *emission, const char *source)
    {
        //More API showoff
        if(edje_object_signal_callback_del(edje,emission,source,good_bye))
            printf("Deleted the handler: %s -> %s\n",source,emission);

        printf("Good bye! \n");
        ecore_main_loop_quit();
        ecore_evas_shutdown();
        ecore_shutdown();
    }
    ...
    edje_object_signal_callback_add(edje, "closeme!", "arbitrary_source", good_bye, "data");
    ...

As we can see, the parameter list of both, the creator and deletion functions is longer than before, in this case, the signal "type" is replaced by a couple of strings, emission, the signal content, in this case "closeme!" and source, the element in the Edje object that emitted the signal, in this case "arbitrary_source".

Plainly, the result is that the function good_bye will be executed when the "arbitrary_source" element inside the Evas_Object "edje", emits the "closeme!" signal.

The "data" parameter

It is important to notice the last parameter in every event related function, this is an pointer to any type of data you want to pass to the callback function. When we delete the callback, in every case, the return value will be the same pointer or NULL in case of failure.

References

Assembled Examples

The Enlightenment Foundation Libraries use pkg-config to store the necessary compilation data. Unix users with the libraries properly installed can compile these examples with: gcc -o example `pkg-config --cflags --libs evas ecore-evas edje` example.c.

API Basics

#include <stdlib.h>
#include <Evas.h>
#include <Ecore.h>
#include <Ecore_Evas.h>
#include <Edje.h>

//The pointer to a canvas wrapper
Ecore_Evas *ecore_evas = NULL;
//The pointer to an Evas canvas
Evas *evas = NULL;
//The pointer to a given Edje object
Evas_Object *edje = NULL;
//Width and height for resizing Evas/Edje objects
Evas_Coord width, height;

int main() {

    //Control that the libraries are properly initialized
    if (!ecore_init()) return EXIT_FAILURE;
    if (!ecore_evas_init()) return EXIT_FAILURE;
    if (!edje_init()) return EXIT_FAILURE;
 
    //Check the canvas wrapper (800x600 X11 window) is created correctly
    ecore_evas = ecore_evas_software_x11_new(NULL, 0, 0, 0, 800, 600);
    if (!ecore_evas) return EXIT_FAILURE;

    //We set some window attributes and make the wrapper visible
    ecore_evas_title_set(ecore_evas, "Example Application");
    ecore_evas_name_class_set(ecore_evas, "testapp", "Testapp");
    ecore_evas_show(ecore_evas);

    //Get the pointer to the canvas and add tan object
    evas = ecore_evas_get(ecore_evas);
    edje = edje_object_add(evas);
    edje_object_file_set(edje, "testfile.edj", "testgroup");

    //Setting the object and canvas to the minimal size of the object
    evas_object_move(edje, 0, 0);
    edje_object_size_min_get(edje, &width, &height);
    evas_object_resize(edje, width, height);
    ecore_evas_resize(ecore_evas, width, height);
    evas_object_show(edje);

    //Starting the main application loop
    ecore_main_loop_begin();

    //Once the window is closed (ending the loop) we clean up our mess
    ecore_evas_shutdown();
    ecore_shutdown();
    edje_shutdown();
}

Events in Ecore

#include <stdlib.h>
#include <stdio.h>
#include <Ecore.h>
#include <Ecore_Evas.h>

//The pointer to a canvas wrapper
Ecore_Evas *ecore_evas = NULL;
//Pointers to Ecore handlers
Ecore_Event_Handler* close = NULL;

//Handles cleaning up and closing the application
int
good_bye(void *data, int type, void *event)
{
    //Remove the handler for no practical reason but showoff API
    if (ecore_event_handler_del(close)) printf("Handler deleted\n");

    printf("Good bye! \n");
    ecore_main_loop_quit();
    ecore_evas_shutdown();
    ecore_shutdown();
}

int main() {

    //Control that the libraries are properly initialized
    if (!ecore_init()) return EXIT_FAILURE;
    if (!ecore_evas_init()) return EXIT_FAILURE;
    if (!edje_init()) return EXIT_FAILURE;
 
    //Check the canvas wrapper (800x600 X11 window) is created correctly
    ecore_evas = ecore_evas_software_x11_new(NULL, 0, 0, 0, 800, 600);
    if (!ecore_evas) return EXIT_FAILURE;

    //We set some window attributes and make the wrapper visible
    ecore_evas_title_set(ecore_evas, "Example Application");
    ecore_evas_name_class_set(ecore_evas, "testapp", "Testapp");
    ecore_evas_show(ecore_evas);

    //Add the handler for the exit signal before starting the loop
    close = ecore_event_handler_add(ECORE_EVENT_SIGNAL_EXIT,good_bye,"data");

    //Starting the main application loop
    ecore_main_loop_begin();
}

Events in Edje

#include <stdlib.h>
#include <stdio.h>
#include <Evas.h>
#include <Ecore.h>
#include <Ecore_Evas.h>
#include <Edje.h>

//The pointer to a canvas wrapper
Ecore_Evas *ecore_evas = NULL;
//The pointer to an Evas canvas
Evas *evas = NULL;
//The pointer to a given Edje object
Evas_Object *edje = NULL;
//Width and height for resizing Evas/Edje objects
Evas_Coord width, height;
//Pointers to Ecore handlers
Ecore_Event_Handler* close = NULL;

//Handles cleaning up and closing the application
void
good_bye(void *data, Evas_Object *o, const char *emission, const char *source)
{
    if(edje_object_signal_callback_del(edje,emission,source,good_bye))
        printf("Deleted the handler: %s -> %s\n",source,emission);

    printf("Good bye! \n");
    ecore_main_loop_quit();
    ecore_evas_shutdown();
    ecore_shutdown();
}

int main() {

    //Control that the libraries are properly initialized
    if (!ecore_init()) return EXIT_FAILURE;
    if (!ecore_evas_init()) return EXIT_FAILURE;
    if (!edje_init()) return EXIT_FAILURE;
 
    //Check the canvas wrapper (800x600 X11 window) is created correctly
    ecore_evas = ecore_evas_software_x11_new(NULL, 0, 0, 0, 800, 600);
    if (!ecore_evas) return EXIT_FAILURE;

    //We set some window attributes and make the wrapper visible
    ecore_evas_title_set(ecore_evas, "Example Application");
    ecore_evas_name_class_set(ecore_evas, "testapp", "Testapp");
    ecore_evas_show(ecore_evas);

    //Get the pointer to the canvas and add tan object
    evas = ecore_evas_get(ecore_evas);
    edje = edje_object_add(evas);
    edje_object_file_set(edje, "testfile.edj", "testgroup");

    //Setting the object and canvas to the minimal size of the object
    evas_object_move(edje, 0, 0);
    edje_object_size_min_get(edje, &width, &height);
    evas_object_resize(edje, width, height);
    ecore_evas_resize(ecore_evas, width, height);
    evas_object_show(edje);

    //Add the handler for the exit signal before starting the loop
    edje_object_signal_callback_add(edje, "closeme!", "arbitrary_source", good_bye, "data");

    //Starting the main application loop
    ecore_main_loop_begin();
}


EDC Source

All the examples use the same EDC source code compilable with ``edje_cc``.

 data {
    item: "test" "value";
 }

 collections {

    group {
        name: "testgroup";
        min: 250 250;
        max: 500 500;

        parts {

            part {
                name: "test";
                type: RECT;

                description {
                    color: 128 128 128 255;
                }
            }

            part {
                name: "i_just_sit_here";
                type: RECT;

                description {
                    color: 190 70 70 255;
                    rel1.relative: 0.25 0.25;
                    rel2.relative: 0.75 0.75;
                }
            }

        }

        programs {

            program {
                name: "i_actually_talk_to_the_app";
                signal: "mouse,clicked,1";
                source: "i_just_sit_here";
                action: SIGNAL_EMIT "closeme!" "arbitrary_source";
            }

        }
    }
 }