From f3076c6d1951a8e66f3be74e1a0c53d9b34d6cb0 Mon Sep 17 00:00:00 2001 From: Xavi Artigas Date: Wed, 12 Feb 2020 19:22:43 +0100 Subject: [PATCH] homescreen: Simplify code and UI structure Remove global variables. Add comments. General beautification. --- apps/c/homescreen/src/homescreen.c | 411 +++++++++++++++++------------ 1 file changed, 238 insertions(+), 173 deletions(-) diff --git a/apps/c/homescreen/src/homescreen.c b/apps/c/homescreen/src/homescreen.c index ff328175..1b2eb087 100644 --- a/apps/c/homescreen/src/homescreen.c +++ b/apps/c/homescreen/src/homescreen.c @@ -1,240 +1,305 @@ #define EFL_BETA_API_SUPPORT #include -#include +#include -static Efl_Ui_Spotlight_Container *over_container; +#define TABLE_COLUMNS 4 +#define TABLE_ROWS 5 -#define SCALE 0.5 - -typedef struct +typedef struct _Build_Data { - Eina_Position2D position; - const char *name; - const char *icon; - Efl_Event_Cb cb; -} Icon; + Eo *over_container; + Efl_Io_Manager *io_manager; + Eo *table; + int x, y; +} Build_Data; -static void _fake_app_cb(void *data, const Efl_Event *ev); +static const char *launcher_apps[][3] = + { { "Call", "", "call-start" }, + { "Contacts", "", "contact-new" }, + { "Home", "xdg-open $HOME", "user-home" }, + { "Mail", "xdg-email 'info@enlightenment.org'", "mail-send-receive" }, + { "Documents", "xdg-open $(xdg-user-dir DOCUMENTS)", "folder-documents" } }; -#define P "/usr/share/icons/hicolor/128x128/apps/" +static void +_icon_clicked_cb(void *data EINA_UNUSED, const Efl_Event *ev EINA_UNUSED) +{ + const char *command = data; + printf("%s\n", command); + efl_add(EFL_EXE_CLASS, efl_main_loop_get(), + efl_core_command_line_command_string_set(efl_added, command), + efl_task_run(efl_added)); +} -static Icon workspace1[] = { - { EINA_POSITION2D(0, 0), "Firefox", P"firefox.png", NULL}, - { EINA_POSITION2D(0, 1), "Monodevelop", P"monodevelop.png", NULL}, - { EINA_POSITION2D(0, 2), "Terminal", "utilities-terminal", NULL}, - { EINA_POSITION2D(2, 0), NULL, NULL, NULL}, -}; +static void +_icon_deleted_cb(void *data, const Efl_Event *ev EINA_UNUSED) +{ + free(data); +} -static Icon workspace2[] = { - { EINA_POSITION2D(0, 3), "web-browser", "web-browser", _fake_app_cb}, - { EINA_POSITION2D(1, 3), "Emacs", P"emacs.png", _fake_app_cb}, - { EINA_POSITION2D(2, 3), "etui", P"etui.png", _fake_app_cb}, - { EINA_POSITION2D(3, 3), "CAD", P"librecad.png", _fake_app_cb}, - { EINA_POSITION2D(4, 3), "Libreoffice", P"libreoffice-base.png", _fake_app_cb}, - { EINA_POSITION2D(0, 4), "Riot", P"riot.png", _fake_app_cb}, - { EINA_POSITION2D(1, 4), "Tex", P"texstudio.png", _fake_app_cb}, - { EINA_POSITION2D(2, 4), "Telegram", P"telegram.png", _fake_app_cb}, - { EINA_POSITION2D(3, 4), "Vlc", P"vlc.png", _fake_app_cb}, - { EINA_POSITION2D(4, 4), "Mono", P"monodevelop.png", _fake_app_cb}, - { EINA_POSITION2D(2, 0), NULL, NULL, NULL}, -}; +// If "string" begins with the "key" prefix, sets "value" to whatever comes after "key" +// up until the next \n, replacing it with a \0, in a newly allocated string +// (which must be freed). +// Returns 1 if key was found. +static int +_parse_token(const char *string, const char *key, char **value) +{ + if (eina_str_has_prefix(string, key)) + { + int key_len = strlen(key); + const char *end = strchr(string + key_len, '\n'); + if (!end) + end = string + strlen(string); // Point to EOF '\0' + int len = end - string - key_len; + if (*value) free(*value); + *value = eina_strndup(string + key_len, len + 1); + *((*value) + len) = '\0'; + return 1; + } + return 0; +} -static Icon workspace3[] = { - { EINA_POSITION2D(0, 0), "bla", P"remmina.png", NULL}, - { EINA_POSITION2D(1, 1), "bla", "ic2", NULL}, - { EINA_POSITION2D(0, 2), "bla", "ic", NULL}, - { EINA_POSITION2D(1, 3), "bla", "ic2", NULL}, - { EINA_POSITION2D(0, 4), "bla", "ic", NULL}, - { EINA_POSITION2D(2, 0), "bla", "ic", NULL}, - { EINA_POSITION2D(3, 1), "bla", "ic2", NULL}, - { EINA_POSITION2D(2, 2), "bla", "ic", NULL}, - { EINA_POSITION2D(3, 3), "bla", "ic2", NULL}, - { EINA_POSITION2D(2, 4), "bla", "ic", NULL}, - { EINA_POSITION2D(4, 0), "bla", "ic", NULL}, - { EINA_POSITION2D(4, 2), "bla", "ic", NULL}, - { EINA_POSITION2D(4, 4), "bla", "ic", NULL}, - { EINA_POSITION2D(0, 2), NULL, NULL, NULL}, -}; +// Reads a .desktop file and returns the app name, the command to launch and the icon name. +// Returns 0 if it didn't work. +// Free the strings after usage. +static int +_parse_desktop_file(const char *desktop_file_path, char **app_name, char **app_command, char **app_icon_name) +{ + EINA_RW_SLICE_DECLARE(slice, 1024); + Efl_Io_File *desktop_file; + int ret = 0; -static Icon* workspaces[] = {workspace1, workspace2, workspace3}; + desktop_file = efl_new(EFL_IO_FILE_CLASS, + efl_file_set(efl_added, desktop_file_path), + efl_io_closer_close_on_invalidate_set(efl_added, EINA_TRUE)); -static void _home_screen_cb(void *data, const Efl_Event *cb); + if (!desktop_file) + return 0; -static Icon start_line_config[] = { - { EINA_POSITION2D(0, 0), "Call", "call-start", _fake_app_cb}, - { EINA_POSITION2D(0, 0), "Contact", "contact-new", _fake_app_cb}, - { EINA_POSITION2D(0, 0), "Home", "applications-internet", _home_screen_cb}, - { EINA_POSITION2D(0, 0), "Mail", "emblem-mail", _fake_app_cb}, - { EINA_POSITION2D(0, 0), "Documents", "emblem-documents", _fake_app_cb}, - { EINA_POSITION2D(0, 0), NULL, NULL, NULL}, -}; + char *name = NULL, *command = NULL, *icon = NULL; + while (!efl_io_reader_eos_get(desktop_file) && + efl_io_reader_read(desktop_file, &slice) == EINA_ERROR_NO_ERROR) + { + char *content = eina_rw_slice_strdup(slice); + char *ptr = content; + while ((ptr = strchr(ptr, '\n'))) + { + ptr++; + _parse_token(ptr, "Name=", &name); + _parse_token(ptr, "Exec=", &command); + _parse_token(ptr, "Icon=", &icon); + } + free(content); + } + if (name && command && icon) + { + *app_name = name; + *app_command = command; + *app_icon_name = icon; + ret = 1; + } + else + { + if (name) + free(name); + if (command) + free(command); + if (icon) + free(icon); + } -static Eo *compositor; + efl_unref(desktop_file); + return ret; +} -static Efl_Ui_Widget* -_create_icon(Icon *icon, Eo *parent) +// Creates a widget "button" with the specified name, icon and command +// to execute on click. +// These buttons are actually just an image with a label below. +static Efl_Ui_Widget * +_create_icon(Eo *parent, const char *name, const char *command, const char *icon) { Eo *box = efl_add(EFL_UI_BOX_CLASS, parent); // Icon - Eo *ic = efl_add(EFL_UI_IMAGE_CLASS, box, - efl_ui_image_icon_set(efl_added, icon->icon), - efl_gfx_hint_weight_set(efl_added, 1.0, 1.0), - efl_gfx_entity_size_set(efl_added, EINA_SIZE2D(300, 300)), - efl_pack(box, efl_added)); - if (icon->cb) - efl_event_callback_add(ic, EFL_INPUT_EVENT_CLICKED, icon->cb, icon); + efl_add(EFL_UI_IMAGE_CLASS, box, + efl_ui_image_icon_set(efl_added, icon), + efl_gfx_hint_weight_set(efl_added, 1.0, 1.0), + efl_gfx_hint_size_min_set(efl_added, EINA_SIZE2D(64, 64)), + efl_event_callback_add(efl_added, EFL_INPUT_EVENT_CLICKED, _icon_clicked_cb, command), + efl_event_callback_add(efl_added, EFL_EVENT_DEL, _icon_deleted_cb, command), + efl_pack(box, efl_added)); // Label efl_add(EFL_UI_TEXTBOX_CLASS, box, - efl_text_set(efl_added, icon->name), - efl_canvas_textblock_style_apply(efl_added, "effect_type=soft_shadow shadow_color=black"), - efl_text_horizontal_align_set(efl_added, 0.5), - efl_text_interactive_editable_set(efl_added, EINA_FALSE), - efl_text_interactive_selection_allowed_set(efl_added, EINA_FALSE), - efl_pack(box, efl_added)); + efl_text_set(efl_added, name), + efl_text_multiline_set(efl_added, EINA_TRUE), + efl_canvas_textblock_style_apply(efl_added, + "effect_type=soft_shadow shadow_color=#444 wrap=word font_size=10 align=center ellipsis=1"), + efl_gfx_hint_size_min_set(efl_added, EINA_SIZE2D(0, 40)), + efl_text_interactive_editable_set(efl_added, EINA_FALSE), + efl_text_interactive_selection_allowed_set(efl_added, EINA_FALSE), + efl_pack(box, efl_added)); return box; } -static Efl_Ui_Table* -_hs_screen_new(Icon *icons) +// Creates a widget "button" for the specified .desktop file. +// These buttons are actually just an image with a label below. +static Efl_Ui_Widget * +_create_app_icon(Eo *parent, const char *desktop_file_path) { - Efl_Ui_Table *table; + char *name = NULL, *command = NULL, *icon = NULL; + Eo *widget = NULL; - table = efl_add(EFL_UI_TABLE_CLASS, over_container); + if (!_parse_desktop_file(desktop_file_path, &name, &command, &icon)) + return NULL; - for (int y = 0; y < 5; ++y) - { - for (int x = 0; x < 5; ++x) - { - Eo *obj = efl_add(EFL_CANVAS_RECTANGLE_CLASS, table, efl_gfx_color_set(efl_added, 0, 0, 0, 0)); - efl_pack_table(table, obj, x, y, 1, 1); - } - } + widget = _create_icon(parent, name, command, icon); - for (int i = 0; icons[i].name; ++i) - { - Eo *icon = _create_icon(&icons[i], table); - efl_pack_table(table, icon, icons[i].position.x, icons[i].position.y, 1, 1); - } - - return table; + free(name); + free(icon); + return widget; } -static Efl_Ui_Widget* -_build_homescreen(Efl_Ui_Win *win) +// Adds a new empty page to the homescreen +static void +_add_page(Build_Data *bdata) +{ + bdata->table = efl_add(EFL_UI_TABLE_CLASS, bdata->over_container, + efl_pack_table_size_set(efl_added, TABLE_COLUMNS, TABLE_ROWS)); + efl_pack_end(bdata->over_container, bdata->table); + bdata->x = bdata->y = 0; +} + +// Adds all files in the array to the homescreen, adding pages as they become full. +static void +_app_found(void *data, Eina_Array *files) +{ + unsigned int i; + const char *item; + Eina_Array_Iterator iterator; + Build_Data *bdata = data; + + EINA_ARRAY_ITER_NEXT(files, i, item, iterator) + { + Eo *app = _create_app_icon(bdata->over_container, item); + if (app) + { + if (!bdata->table) + _add_page(bdata); + efl_pack_table(bdata->table, app, bdata->x, bdata->y, 1, 1); + bdata->x++; + if (bdata->x == TABLE_COLUMNS) + { + bdata->x = 0; + bdata->y++; + if (bdata->y == TABLE_ROWS) + bdata->table = NULL; + } + } + } +} + +// Called when directory listing has finished +static Eina_Value +_file_listing_done_cb (void *data, const Eina_Value file, const Eina_Future *dead EINA_UNUSED) +{ + Build_Data *bdata = data; + // Fill any remaining empty cells with invisible rectangles so the rest of the cells + // keep the same size as other pages + while (bdata->y < TABLE_ROWS) + { + while (bdata->x < TABLE_COLUMNS) + { + efl_add(EFL_CANVAS_RECTANGLE_CLASS, bdata->table, + efl_gfx_color_set(efl_added, 0, 0, 0, 0), + efl_pack_table(bdata->table, efl_added, bdata->x, bdata->y, 1, 1)); + bdata->x++; + } + bdata->x = 0; + bdata->y++; + } + return file; +} + +// Create Spotlight widget and start populating it with user apps. +static void +_build_homescreen(Efl_Ui_Win *win, Build_Data *bdata) { Efl_Ui_Spotlight_Indicator *indicator = efl_new(EFL_UI_SPOTLIGHT_ICON_INDICATOR_CLASS); Efl_Ui_Spotlight_Manager *scroll = efl_new(EFL_UI_SPOTLIGHT_SCROLL_MANAGER_CLASS); - - over_container = efl_add(EFL_UI_SPOTLIGHT_CONTAINER_CLASS, win, - efl_ui_spotlight_manager_set(efl_added, scroll), - efl_ui_spotlight_indicator_set(efl_added, indicator) + bdata->over_container = efl_add(EFL_UI_SPOTLIGHT_CONTAINER_CLASS, win, + efl_ui_spotlight_manager_set(efl_added, scroll), + efl_ui_spotlight_indicator_set(efl_added, indicator) ); + bdata->table = NULL; - for (int i = 0; i < 3; ++i) - { - Eo *screen = _hs_screen_new(workspaces[i]); - - efl_pack_end(over_container, screen); - } - return over_container; + Eina_Future *future = efl_io_manager_ls(bdata->io_manager, "/usr/share/applications", bdata, _app_found, NULL); + eina_future_then(future, _file_listing_done_cb, bdata, NULL); } +// The main box, with an upper space for the apps list and a lower space +// for the quick-action buttons. static Efl_Ui_Widget* _build_overall_structure(Efl_Ui_Win *win, Efl_Ui_Widget *homescreen) { - Efl_Ui_Widget *o, *start_line; + Efl_Ui_Widget *box, *start_line; - o = efl_add(EFL_UI_BOX_CLASS, win); - efl_pack_end(o, homescreen); + box = efl_add(EFL_UI_BOX_CLASS, win); - //start line - start_line = efl_add(EFL_UI_BOX_CLASS, win); + // Set box background + // Objects retrieved with efl_part() only survive one function call, so we ref it + Eo *background = efl_ref(efl_part(box, "background")); + efl_file_key_set(background, "e/desktop/background"); + efl_file_set(background, "../src/Hills.edj"); + efl_file_load(background); + efl_unref(background); + + efl_pack_end(box, homescreen); + + // Start line + start_line = efl_add(EFL_UI_BOX_CLASS, win, + efl_gfx_color_set(efl_part(efl_added, "background"), 128, 128, 128, 128)); efl_gfx_hint_weight_set(start_line, 1.0, 0.0); efl_ui_layout_orientation_set(start_line, EFL_UI_LAYOUT_ORIENTATION_HORIZONTAL); - efl_gfx_hint_size_min_set(start_line, EINA_SIZE2D(5*150*SCALE, 150*SCALE)); - efl_gfx_hint_size_max_set(start_line, EINA_SIZE2D(-1, 150*SCALE)); - efl_pack_end(o, start_line); + efl_pack_end(box, start_line); - for (int i = 0; i < 5; ++i) + for (unsigned int i = 0; i < sizeof(launcher_apps)/sizeof(launcher_apps[0]); ++i) { - efl_pack_end(start_line, _create_icon(&start_line_config[i], start_line)); + efl_pack_end(start_line, _create_icon(start_line, + launcher_apps[i][0], + strdup(launcher_apps[i][1]), + launcher_apps[i][2])); } - return o; -} - -static Efl_Ui_Widget* -_build_compositor(Efl_Ui_Win *win) -{ - Efl_Ui_Widget *comp; - Efl_Ui_Spotlight_Manager *stack; - - stack = efl_new(EFL_UI_SPOTLIGHT_FADE_MANAGER_CLASS); - comp = efl_add(EFL_UI_SPOTLIGHT_CONTAINER_CLASS, win, - efl_ui_spotlight_manager_set(efl_added, stack)); - - return comp; + return box; } +// Called when the app is closed static void -_home_screen_cb(void *data EINA_UNUSED, const Efl_Event *cb EINA_UNUSED) -{ - Efl_Canvas_Rectangle *rect; - - rect = efl_add(EFL_CANVAS_RECTANGLE_CLASS, compositor); - efl_ui_spotlight_push(compositor, rect); -} - -static void -_fake_app_cb(void *data EINA_UNUSED, const Efl_Event *ev EINA_UNUSED) -{ - Efl_Ui_Table *table = efl_add(EFL_UI_TABLE_CLASS, compositor); - Efl_Canvas_Rectangle *rect = efl_add(EFL_CANVAS_RECTANGLE_CLASS, compositor); - efl_gfx_color_set(rect, 50, 50, 50, 255); - efl_pack_table(table, rect, 0, 0, 1, 1); - Efl_Ui_Textbox *text = efl_add(EFL_UI_TEXTBOX_CLASS , table); - efl_text_interactive_editable_set(text, EINA_FALSE); - efl_text_multiline_set(text, EINA_FALSE); - efl_text_set(text, "This is a testing application"); - efl_pack_table(table, text, 0, 0, 1, 1); - efl_ui_spotlight_push(compositor, table); -} - -static void -_gui_quit_cb(void *data EINA_UNUSED, const Efl_Event *event EINA_UNUSED) +_gui_quit_cb(void *data, const Efl_Event *event EINA_UNUSED) { + Build_Data *bdata = data; + if (bdata->io_manager) + efl_del(bdata->io_manager); + free(bdata); efl_exit(0); } EAPI_MAIN void efl_main(void *data EINA_UNUSED, const Efl_Event *ev EINA_UNUSED) { - Eo *win, *over_container, *desktop, *background; + Eo *win, *desktop; + Build_Data *bdata = calloc (1, sizeof(Build_Data)); - efreet_init(); + bdata->io_manager = efl_add(EFL_IO_MANAGER_CLASS, efl_main_loop_get()); win = efl_add(EFL_UI_WIN_CLASS, efl_main_loop_get(), - efl_ui_win_autodel_set(efl_added, EINA_TRUE)); + efl_ui_win_autodel_set(efl_added, EINA_TRUE)); // when the user clicks "close" on a window there is a request to delete - efl_event_callback_add(win, EFL_UI_WIN_EVENT_DELETE_REQUEST, _gui_quit_cb, NULL); - efl_gfx_entity_size_set(win, EINA_SIZE2D(720*SCALE+15, 1280*SCALE)); + efl_event_callback_add(win, EFL_UI_WIN_EVENT_DELETE_REQUEST, _gui_quit_cb, bdata); - over_container = _build_homescreen(win); - desktop = _build_overall_structure(win, over_container); - - compositor = _build_compositor(win); - //efl_pack_end(compositor, desktop); - efl_ui_spotlight_push(compositor, desktop); - efl_gfx_entity_size_set(compositor, EINA_SIZE2D(720*SCALE, 1280*SCALE)); - - background = efl_add(EFL_UI_IMAGE_CLASS, win, - //efl_file_set(efl_added, "../src/wood_01.jpg"), - efl_file_key_set(efl_added, "e/desktop/background"), - efl_file_set(efl_added, "../src/Hills.edj"), - efl_gfx_image_scale_method_set(efl_added, EFL_GFX_IMAGE_SCALE_METHOD_EXPAND)); - efl_content_set(win, background); + _build_homescreen(win, bdata); + desktop = _build_overall_structure(win, bdata->over_container); + efl_content_set(win, desktop); } EFL_MAIN()