Tuesday, April 30, 2013

Egui - EFL gui builder

* Assumed, reader knows how Evas/Elementary/Eo are used

Usually developer creates both user interface and application logic. If application wasn't properly designed, changes in interface can affect logic and vice versa.
We want to separate work of designer from developer, and provide opportunity to check how user interface looks like, even if logic is not still inside.
We are going to implement all main gui creator's functionality and in addition simulation.
Simulation can be used by gui designers, to check gui behavior without compilation and logic.
Also we are going to provide code export to different languages(C, Python, JavaScript).

Here I'm going to describe how we are going to do this.

The main idea is that we don't want to have separate project file, but we want to put "easy-to-write-by-hands" object's description into json metadata right into C source file's comments. (if we still want to have project file, we can put all jsons into separate file)

So part of our source file will look like this:

/**
* @egui
* {
*    "win": {
*          "parent" : null,
*          "public" : true,
*          "class" : "elm_win",
*          "constructor" : "elm_obj_win_constructor",
*          "elm_obj_win_constructor" : [null, "ELM_WIN_BASIC"],
*          "title_set" : "window!!!",
*          "autodel_set" : true,
*          "size_set" : [320, 320],
*          "visibility_set" : true,
*          "resize_object_add" : "bg"
*    }
* }
* @!egui
*/
Eo *win = eo_add_custom(ELM_OBJ_WIN_CLASS, NULL,
                       elm_obj_win_constructor(NULL, ELM_WIN_BASIC));
eo_do(win, evas_obj_size_set(320, 320));
eo_do(win, evas_obj_visibility_set(EINA_TRUE));
eo_do(win, elm_obj_win_title_set("window!!!"));
eo_do(win, elm_obj_win_autodel_set(EINA_TRUE));

/**
* @egui
* {
*    "but1" : {
*          "parent" : "win",
*          "public" : false,
*          "class" : "elm_button",
*          "constructor" : "eo_constructor",
*          "eo_constructor" : null,
*          "color_set" : [10, 255, 0, 255],
*          "visibility_set" : true,
*          "part_text_set" : [null, "button1"],
*          "size_set" : [100, 50],
*          "position_set" : [40, 20]
*       }
* }
* @!egui
*/

Eo *but1 = eo_add(ELM_OBJ_BUTTON_CLASS, win);
eo_do(but1, evas_obj_color_set(0, 250, 0, 255));
eo_do(but1, evas_obj_position_set(40, 20));
eo_do(but1, evas_obj_size_set(100, 50));
eo_do(but1, evas_obj_visibility_set(EINA_TRUE));
eo_do(but1, elm_wdg_part_text_set(NULL, "button1"));
 
In this example, "win" and "but1" are widgets, "parent", "public", "size_set" etc - are properties. You can see different properties names.
Some of them are shorter names of well-known evas/elementary functions.
Some other are used for internal builder needs.
As soon as there is such data, it's possible to parse json, save data and later simulate current gui behavior or generate sources in desired language.

How do we get such property names?
Gui Builder is based on Eo (EFL object oriented model).
Currently each Evas object or Elementary widget are implemented using Eo.
So widget's class name is provided in json.

During simulation and source code generation a proper function is fetched from
internal Egui's database according to property name and widget's class name.

What is internal Egui's database?
We have a script which searches for all data (functions, parameters, types, enums, etc)
of all classes in the provided paths. (all Elementary widgets, etc).
Only needed data(based on gui_db.in) will be selected and database for Egui will be generated.
(Script and "gui_db.in" currently are not in egui project)

Currently "gui_db.in" looks like this.
OPERATIONS =
["evas_obj_color", "set"] : "color_set" :
["evas_obj_size_set"] : "size_set" :
["evas_obj_position", "set"] : "position_set" :
["evas_obj_size_hint_weight", "set"] : "size_hint_weight_set" :
["evas_obj_visibility_set" : "visibility_set" :
["elm_obj_bg_color_set"] : "bg_color_set" :
["elm_obj_win_title_set"] : "title_set" :
["elm_obj_win", "autodel_set"] : "autodel_set" :
["elm_obj_win_resize_object_add"] : "resize_object_add" :
["elm_wdg_part_text_set"] : "part_text_set" :
["elm_obj_win_constructor"] : "" :
["elm_obj_container_content_set"] : "content_set" :
;

CLASSES =
elm_win  ,
elm_button,
elm_bg,
elm_label,
elm_bubble,
;
CLASSES - literally list of widgets which can be used in Egui. We will add all needed classes here.
OPERATIONS - list of tokens, which will be looked for in function names, and mapped into Egui property.
For example: ["evas_obj_color", "set] : "color_set" looks for every function,
which has parts "evas_obj_color" and "set" and stores it's data into hash.

In this case "evas_object_color_set" will be found in "Evas_Obj" class.
Data about this func will be stored in a hash with a key {"Evas_Obj", "color_set"}.

Another example:
Rule ["_multi_select_set"] : "multi"
will find two functions: "elm_obj_genlist_multi_select_set",  "elm_obj_gengrid_multi_select_set"
and store it into {"elm_genlist", "multi"} and {"elm_gengrid", "multi"} database records. So property "multi" can be used for both genlist and gengrid.
When simulating, this property will be dispatched correctrly as soon as
"multi" belongs to classes in separate hierarchies.

But name clashes in the same hierarchy should be avoided.
For example:
Suppose we will map "evas_obj_color_set" and "elm_obj_bg_color_set" into property "color_set".
Two database records will be created {"Evas_Obj", "color_set"} and {"elm_bg", "color_set"}. Dispatch will work correctly, because functions belong to different classes, but user will have to remember, that "color_set" in "elm_bg" widget receives three parameters and four in others. (To handle this, dispatch according to parameter number can be added in future).

Also, according to this database, widget's available properties will be displayed in "Property Inspector" of builder.

Code Generation
Following C source files can be generated for the described example. Header file, that declares structure which allows to get access to gui elements. Source file, that creates all widgets.
/**
***********************
** Generated gui.h file
************************
*/
#ifndef _gui_h
#define _gui_h
#include <Eo.h>

typedef struct
{
   Eo *win;
} GUI;

GUI* get_gui();

#endif

/**
***********************
** Generated gui.c file
************************
*/
#include <gui.h>
GUI* get_gui()
{
  Eo *win;
  Eo *but1;
  
  win = eo_add_custom(ELM_OBJ_WIN_CLASS, NULL, 
                      elm_obj_win_constructor(NULL, ELM_WIN_BASIC));
  but1 = eo_add(ELM_OBJ_BUTTON_CLASS, win);

  // all relevant calls of eo_do()

  // creation of external structure to provide access to widgets
  GUI *pub_widgets = (GUI*)calloc(1, sizeof(GUI));
  pub_widgets->win = win;
  return pub_widgets;
}

Trying Egui.
You will need different efl branch to do it.
> git clone git://git.enlightenment.org/core/efl.git
> cd efl
> git checkout -b eo_class_mro_get devs/yakov/eo_class_mro_get
Build and install this branch.
 
You can also rebase it on top of origin/master, if needed.
> git rebase origin/master
 
Checkout Egui:
> git clone git://git.enlightenment.org/devs/yakov/egui.git
 
Build:
> cd egui
> mkdir build
> cd build
> cmake ..
> make
Should be compiled without errors.
 
Run simulation:
> ./src/bin/egui -f ../examples/example.c -s

 
Generate source code:
  > ./src/bin/egui -f ../examples/example.c -g -o new
    - new.c and new.h will be generated in current dir
 
Run simulation on newly generated file:
> ./src/bin/egui -f new.c -s
 
Run kind of Egui main window :)
> ./src/bin/egui -f new.c
  press green button to open kind of project :)
 
Remember: sometimes, to close egui, you'll have to use "Ctrl+C" :)

We are also thinking how to name it. Maybe simply "egui", maybe "eline" (suggested by YossiK), you are welcome to suggest your own.

Waiting for your questions and comments. :)


1 comment :