|
|
|
NavigationPersonal tools |
Creating Edje User Interfaces
[edit] IntroductionThis article describes how to easily create a user interface using:
The application will be a small application launcher and the actual interface will be written using edje and animations. The python part doesn't have any knowledge of the interface, where the icons are placed, how they should move, nor if they are using some kind of effect, like for instance pulsing. All it does is listen for "selected" signals. A video demostration the application can be found on Gustavo's blog [1] Code can be found at E17 SVN, under [2] [edit] Python CodeLet's start looking at the python part. This tutorial is based on revision 1.1 of code, that can be found at [3] of the Enlightenment SVN. [edit] Initialization codeAfter module imports and parsing the command line options we get the first set of interesting lines (39-51):
if options.engine == "x11":
f = ecore.evas.SoftwareX11
elif options.engine == "x11-16":
if ecore.evas.engine_type_supported_get("software_x11_16"):
f = ecore.evas.SoftwareX11_16
else:
print "warning: x11-16 is not supported, fallback to x11"
f = ecore.evas.SoftwareX11
w, h = options.geometry
ee = f(w=w, h=h)
ee.fullscreen = not options.no_fullscreen
edje.frametime_set(1.0 / options.fps)
[edit] Creating Edje objectsBy this time we have
try:
edje_obj = edje.Edje(canvas, file=edje_file, group="main")
except Exception, e: # should be EdjeLoadError, but it's wrong on python2.5
raise SystemExit("Failed to load Edje file: %s" % edje_file)
# resize edje to fit our window, show and remeber it for later use
edje_obj.size = canvas.size
edje_obj.show()
ee.data["edje"] = edje_obj
We Then we setup object size and show, making it visible on canvas. Notice that nothing will show on screen at this time since canvas itself is hidden. The last part is to remember this edje object inside [edit] CallbacksNow setup some event callbacks, lines 72-92:
def resize_cb(ee):
r = ee.evas.rect
ee.data["edje"].size = r.size
ee.callback_resize = resize_cb
def key_down_cb(bg, event, ee):
k = event.key
if k == "Escape":
ecore.main_loop_quit()
if k in ("F6", "f"):
ee.fullscreen = not ee.fullscreen
edje_obj.on_key_down_add(key_down_cb, ee)
def icon_selected(edje_obj, signal, source):
print "icon_selected", source
edje_obj.signal_callback_add("selected", "*", icon_selected)
The first callback is from The second is an evas object callback, when user press some key it will go to the focused object, that will call this Last but not least, we provide an edje signal callback. Edje signals have a name (emission) and source, in this case emission is "selected", from any Edje part ("*"). This is used to get informed when some item is selected. Notice that we don't have any other knowledge of the UI other than this signal, we don't know how many icons, where they are laid out, etc. [edit] Edje CodeNow let's look at the Edje code. Not everything will be explained, but only the basic parts to understand what is going on. This tutorial is based on revision 30969 of code, that can be found at [4] of the Enlightenment SVN. The Edje file make use of CPP macros to avoid typing, another feature rarely documented. This is quite useful as we can avoid duplicating code for each of the objects. Here is a small example (lines 61-143):
#define ICON(part_name, relx, rely, part_label) \
part { \
name: part_name"_area"; \
...
We define a macro ICON that take as parameter part_name, relx, rely and part_label, we use it to avoid typing 4 times the almost-the-same code. Notice that macros should be placed in just one line, if you want multi-line, as I did, you must finish each line with a backslash. If you want to concatenate strings, pay attention to place them together (without spaces), like part_name"_area", where I concatenate the macro argument (part_name) to "_area".
[edit] Defining resourcesLet us now look at the EDC code. We are dealing with one font and 5 images: one for the background and 4 for the application icons, see lines 1-11:
fonts {
font: "VeraBd.ttf" "Sans";
}
images {
image: "background.jpeg" LOSSY 95;
image: "audio_player.png" COMP;
image: "image_viewer.png" COMP;
image: "video_player.png" COMP;
image: "web_browser.png" COMP;
}
We now can refer to the font as "Sans", images by their name. Images will be stored according to their specification, in this case "background.png" will use lossy compression at 95%, while others will be stored as is, but compressed for size saving, these are the most common used options. [edit] Creating background imageLet's skip the script section and go define our background, it is as easy as (lines 49-59):
part {
name: "background";
type: IMAGE;
mouse_events: 0;
description {
state: "default" 0.0;
rel1 { relative: 0.0 0.0; }
rel2 { relative: 1.0 1.0; offset: -1 -1; }
image { normal: "background.jpeg"; }
}
}
It should be pretty auto-explanatory, with this part being an image, that get no mouse events, default state being positioned with top-left (rel1) at (0,0) and bottom-right (rel2) at object bottom-right edje, using regular image "background.jpeg". Just pay attention and understand what Also, don't bother with [edit] Defining ICON() macroThe user interface works the following way. We have 4 icons placed in each corner (top, right; top, left; bottom, right; bottom, left), and when one is selected the icon moves to the middle and starts pulsing (the icon and the label becomes bigger and the label is centered on the icon. At the same time, a "shadow" of the icon pulses in the background of the icon). In order to accomplish this we use 4 parts.
We make use of the ICON() macro to accomplish this, with the following parts: [edit] ICON() <part_name>_areaLines 62-72 defines the clicking area with position being the macro parameters (
part { \
name: part_name"_area"; \
type: RECT; \
mouse_events: 1; \
description { \
state: "default" 0.0; \
rel1 { relative: relx rely; offset: -96 -96; } \
rel2 { relative: relx rely; offset: 95 95; } \
color: 0 0 0 0; \
} \
} \
[edit] ICON() <part_name>_pulserLines 73-98 defines the pulser object, they all will be centered, by default objects are transparent so will not be visible, with "selected" state being initially (
part { \
name: part_name"_pulser"; \
type: IMAGE; \
mouse_events: 0; \
description { \
state: "default" 0.0; \
rel1 { relative: 0.5 0.5; offset: -64 -64; } \
rel2 { relative: 0.5 0.5; offset: 63 63; } \
color: 0 0 0 0; \
image { normal: part_name".png"; } \
} \
description { \
state: "selected" 0.0; \
inherit: "default" 0.0; \
rel1 { relative: 0.5 0.5; offset: -64 -64; } \
rel2 { relative: 0.5 0.5; offset: 63 63; } \
color: 255 255 255 128; \
} \
description { \
state: "selected" 1.0; \
inherit: "default" 0.0; \
rel1 { relative: 0.5 0.5; offset: -96 -96; } \
rel2 { relative: 0.5 0.5; offset: 95 95; } \
color: 255 255 255 0; \
} \
} \
Note that pulser is aligned to the center by defining both
[edit] ICON() <part_name>Lines 99-115 define the part that initially will be placed on one corner and then will move to the center. This is simple to do by defining two states, "default" with relative positioning based on macro arguments
part { \
name: part_name; \
type: IMAGE; \
mouse_events: 0; \
description { \
state: "default" 0.0; \
rel1 { relative: relx rely; offset: -32 -32; } \
rel2 { relative: relx rely; offset: 31 31; } \
image { normal: part_name".png"; } \
} \
description { \
state: "selected" 0.0; \
inherit: "default" 0.0; \
rel1 { relative: 0.5 0.5; offset: -64 -64; } \
rel2 { relative: 0.5 0.5; offset: 63 63; } \
} \
} \
[edit] ICON() <part_name>_labelLines 116-143 define the text label, initially centered at corner specified by macro arguments
part { \
name: part_name"_label"; \
type: TEXT; \
effect: SHADOW; \
mouse_events: 0; \
description { \
state: "default" 0.0; \
rel1 { relative: relx rely; offset: -100 33; } \
rel2 { relative: relx rely; offset: 99 53; } \
align: 0.5 0.5; \
color: 255 255 255 255; \
color2: 0 0 0 255; \
color3: 0 0 0 255; \
text { \
font: "Sans"; \
size: 18; \
text: part_label; \
min: 1 1; \
fit: 1 1; \
} \
} \
description { \
state: "selected" 0.0; \
inherit: "default" 0.0; \
rel1 { relative: 0.5 0.5; offset: -128 -25; } \
rel2 { relative: 0.5 0.5; offset: 127 24; } \
} \
}
[edit] Defining logic to control selected itemWe go back to the
script {
public selected = 0;
public pulsing = 0;
public stop_pulsing_timer_id = 0;
const Float:pulse_timeout = 10.0;
public unselect() {
if (get_int(selected) == 0)
return;
run_program(get_int(selected));
set_int(selected, 0);
}
public stop_pulsing() {
if (get_int(pulsing) == 0)
return;
set_state(get_int(pulsing), "default", 0.0);
set_int(pulsing, 0);
if (get_int(stop_pulsing_timer_id) != 0) {
cancel_timer(get_int(stop_pulsing_timer_id));
set_int(stop_pulsing_timer_id, 0);
}
}
public stop_pulsing_cb(val) {
stop_pulsing();
return 0;
}
}
The logic is pretty simple and done in Although Embryo/PAWN is simple, you need to be careful with public/global values, here
[edit] Defining ICON_PROGRAMS() macroEdje programs are simple way to do some action based on signals. You can change states, trigger transitions or even run some Embryo code. We use the following programs:
#define ICON_PROGRAMS(part_name) \
program { \
name: "set_selected_"part_name; \
signal: "mouse,clicked,1"; \
source: part_name"_area"; \
script { \
const pid = PROGRAM:"unselect_"part_name; \
if (get_int(selected) == pid) \
return; \
stop_pulsing(); \
unselect(); \
set_int(selected, pid); \
emit("selected", part_name); \
} \
} \
program { \
name: "select_"part_name; \
signal: "selected"; \
source: part_name; \
action: STATE_SET "selected" 0.0; \
target: part_name; \
target: part_name"_label"; \
transition: LINEAR 0.2; \
after: "start_pulse_"part_name; \
} \
program { \
name: "start_pulse_"part_name; \
script { \
stop_pulsing(); \
set_int(pulsing, PART:part_name"_pulser"); \
new i = timer(pulse_timeout, "stop_pulsing_cb", 0); \
set_int(stop_pulsing_timer_id, i); \
run_program(PROGRAM:"shrink_"part_name); \
} \
} \
program { \
name: "shrink_"part_name; \
action: STATE_SET "selected" 0.0; \
target: part_name"_pulser"; \
after: "grow_"part_name; \
} \
program { \
name: "grow_"part_name; \
action: STATE_SET "selected" 1.0; \
target: part_name"_pulser"; \
transition: LINEAR 0.5; \
after: "check_continue_pulsing_"part_name; \
} \
program { \
name: "check_continue_pulsing_"part_name; \
script { \
if (get_int(pulsing) == PART:part_name"_pulser") \
run_program(PROGRAM:"shrink_"part_name); \
else \
set_state(PART:part_name"_pulser", "default", 0.0); \
} \
} \
program { \
name: "unselect_"part_name; \
signal: "unselect_"part_name; \
action: STATE_SET "default" 0.0; \
target: part_name; \
target: part_name"_label"; \
target: part_name"_pulser"; \
transition: LINEAR 0.2; \
}
[edit] References
API (C, but can be applied to Python): Further reading:
|