Message Bubble UI Sample Overview

Mobile native

The [UI Sample] MessageBubble View sample application demonstrates how to make a complex view by recursive composition of standard EFL UI components and containers in a UI component hierarchy.

It uses UI components, such as elm_conform and elm_scroller for the view management, containers, such as elm_box and elm_table for UI component management inside the view, and UI components, such as elm_button and elm_entry for the content inside view.

The following figure illustrates the message bubble view, its wireframe structure, and the UI component tree.

Figure: [UI Sample] MessageBubble screen

[UI Sample] MessageBubble screen

[UI Sample] MessageBubble screen

Application Layout

The create_base_gui() function is responsible for creating the application layout. It starts by creating a window, then adds elm_conformant to it to decorate the window with an indicator. elm_naviframe is added to act as a view manager of the window and provide the window title functionality. The main view is created using the create_main_view() function and added to the naviframe.

static void
create_base_gui(appdata_s *ad)
{
   Evas_Object *main_scroller, *bg;

   // Window
   ad->win = elm_win_util_standard_add(PACKAGE, PACKAGE);
   elm_win_conformant_set(ad->win, EINA_TRUE);
   elm_win_autodel_set(ad->win, EINA_TRUE);
   elm_win_indicator_mode_set(ad->win, ELM_WIN_INDICATOR_SHOW);
   elm_app_base_scale_set(1.8);

   if (elm_win_wm_rotation_supported_get(ad->win)) 
   {
      int rots[4] = {0, 90, 180, 270};
      elm_win_wm_rotation_available_rotations_set(ad->win, (const int *)(&rots), 4);
   }

   evas_object_smart_callback_add(ad->win, "delete,request", win_delete_request_cb, NULL);
   eext_object_event_callback_add(ad->win, EEXT_CALLBACK_BACK, win_back_cb, ad);

   // Conformant
   ad->conform = elm_conformant_add(ad->win);
   elm_win_indicator_mode_set(ad->win, ELM_WIN_INDICATOR_SHOW);
   elm_win_indicator_opacity_set(ad->win, ELM_WIN_INDICATOR_OPAQUE);
   evas_object_size_hint_weight_set(ad->conform, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);
   elm_win_resize_object_add(ad->win, ad->conform);
   evas_object_show(ad->conform);

   // Indicator BG
   bg = elm_bg_add(ad->conform);
   elm_object_style_set(bg, "indicator/headerbg");
   elm_object_part_content_set(ad->conform, "elm.swallow.indicator_bg", bg);
   evas_object_show(bg);

   // Naviframe
   ad->nf = elm_naviframe_add(ad->conform);
   evas_object_size_hint_weight_set(ad->nf, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);
   elm_object_content_set(ad->conform, ad->nf);
   evas_object_show(ad->nf);

   // Main view
   main_scroller = create_main_view(ad);
   elm_naviframe_item_push(ad->nf, "Message Bubble", NULL, NULL, main_scroller, NULL);

   // Show the window after the base GUI is set up
   evas_object_show(ad->win);
}

Main View

The create_main_view() function creates the main view content, the main scroller and main box. The main box contains 2 objects, a bubble scroller and an input field table. The scroller can be scrolled from the left-top to the left-bottom part of the screen.

static Evas_Object*
create_main_view(appdata_s *ad)
{
   Evas_Object *main_scroller, *input_field_table;

   main_scroller = elm_scroller_add(ad->nf);
   elm_scroller_bounce_set(main_scroller, EINA_FALSE, EINA_TRUE);
   elm_scroller_origin_reverse_set(main_scroller, EINA_FALSE, EINA_TRUE);
   evas_object_size_hint_weight_set(main_scroller, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);
   evas_object_size_hint_align_set(main_scroller, EVAS_HINT_FILL, EVAS_HINT_FILL);
   evas_object_show(main_scroller);

   ad->main_box = elm_box_add(main_scroller);
   elm_box_align_set(ad->main_box, 0, 0);
   evas_object_size_hint_weight_set(ad->main_box, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);
   evas_object_show(ad->main_box);

A scroller is added to the main view, and a box structure to the scroller. The box is used for making a list of bubbles.

   ad->bubble_scroller = elm_scroller_add(ad->main_box);
   elm_scroller_bounce_set(ad->bubble_scroller, EINA_FALSE, EINA_TRUE);
   evas_object_size_hint_weight_set(ad->bubble_scroller, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);
   evas_object_size_hint_align_set(ad->bubble_scroller, EVAS_HINT_FILL, EVAS_HINT_FILL);

   ad->bubble_box = elm_box_add(ad->bubble_scroller);
   elm_box_align_set(ad->bubble_box, 0, 0);
   evas_object_size_hint_weight_set(ad->bubble_box, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);
   evas_object_show(ad->bubble_box);
   elm_box_padding_set(ad->bubble_box, ELM_SCALE_SIZE(10), ELM_SCALE_SIZE(15));

   elm_object_content_set(ad->bubble_scroller, ad->bubble_box);
   evas_object_show(ad->bubble_scroller);
   elm_box_pack_end(ad->main_box, ad->bubble_scroller);

An input field table is added to the main view using the create_input_field_table(ad) function. The input field table is a UI component containing buttons for entering and sending messages. This table is separate from the scroller.

   input_field_table = create_input_field_table(ad);
   evas_object_show(input_field_table);
   elm_box_pack_end(ad->main_box, input_field_table);
   elm_object_content_set(main_scroller, ad->main_box);

After the main elements of the MessageBubble are created, the application is ready to load saved messages. The following sample example uses sample messages.

   load_messages(ad);

   return main_scroller;
}

Message bubbles are added using the create_bubble_table() function. This function creates a table container that includes 2 labels with a bubble background. To show the latest message at the bottom of the screen, use the elm_box_pack_start() function. To show the last message on top, use the elm_box_pack_end() function. You can see the latest messages, when the messages are loaded.

If there are several messages to be loaded, use the idler callback to load messages.

The create_input_field_table(ad) function creates the content of the view by composing a scroller structure that contains the screen elements. The scroller contains a box layout with the images.

static void
load_messages(appdata_s *ad)
{
   Evas_Object *bubble_table;

   ad->total_messages = NUM_OF_SAMPLE_MESSAGES;
   ad->num_of_bubbles = 0;
   int count = ad->total_messages - 1;

   while (count >= 0) 
   {
      bubble_table = create_bubble_table(ad->bubble_box, count % 2 + 1,
                     bubble_messages[count],
                     bubble_times[count]);
      evas_object_show(bubble_table);
      elm_box_pack_start(ad->bubble_box, bubble_table);
      ad->num_of_bubbles++;
      count--;
   }
}

Create the layout with a table and button components using the create_bubble_table() function. The background is created as an Evas rectangle object, and its color is set with a rectangle object.

static Evas_Object *
create_bubble_table(Evas_Object *parent, Message_Bubble_Style style, const char *main_text, const char *sub_text)
{
   Evas_Object *bubble_table, *button, *bg, *main_label, *sub_label;
   Eina_Strbuf *strbuf = NULL;
   char *buf = NULL;

   bubble_table = elm_table_add(parent);
   evas_object_size_hint_weight_set(bubble_table, EVAS_HINT_EXPAND, 0.0);
   elm_table_padding_set(bubble_table, ELM_SCALE_SIZE(5), ELM_SCALE_SIZE(5));
   evas_object_show(bubble_table);

   button = elm_button_add(bubble_table);
   elm_object_style_set(button, "transparent");
   evas_object_size_hint_weight_set(button, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);
   evas_object_size_hint_align_set(button, EVAS_HINT_FILL, EVAS_HINT_FILL);

   bg = evas_object_rectangle_add(evas_object_evas_get(button));
   elm_object_content_set(button, bg);
   evas_object_event_callback_add(button, EVAS_CALLBACK_MOUSE_DOWN, bubble_button_mouse_down_cb, bg);
   evas_object_event_callback_add(button, EVAS_CALLBACK_MOUSE_UP, bubble_button_mouse_up_cb, bg);
   evas_object_show(button);

Add 2 label components:

  • Main label for the message
  • Sub-label for showing a timestamp

Create 2 bubble components for the sender and receiver. The bubbles differ by their alignment, color, and position in the table.

   main_label = elm_label_add(bubble_table);
   elm_object_text_set(main_label, buf);
   elm_label_wrap_width_set(main_label, ELM_SCALE_SIZE(BUBBLE_TEXT_WIDTH));
   elm_label_line_wrap_set(main_label, ELM_WRAP_MIXED);
   evas_object_size_hint_weight_set(main_label, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);
   evas_object_size_hint_align_set(main_label, EVAS_HINT_FILL, EVAS_HINT_FILL);
   evas_object_repeat_events_set(main_label, EINA_TRUE);
   evas_object_show(main_label);

   sub_label = elm_label_add(bubble_table);
   elm_object_text_set(sub_label, buf);
   evas_object_size_hint_weight_set(sub_label, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);
   evas_object_repeat_events_set(sub_label, EINA_TRUE);
   evas_object_show(sub_label);

   switch (style) 
   {
      case MESSAGE_BUBBLE_SENT:
         evas_object_size_hint_align_set(bubble_table, 1.0, 0.0);
         evas_object_size_hint_align_set(sub_label, 1.0, EVAS_HINT_FILL);
         evas_object_color_set(bg, 200, 170, 100, 255);
         elm_table_pack(bubble_table, button, 0, 0, 1, 2);
         elm_table_pack(bubble_table, main_label, 0, 0, 1, 1);
         elm_table_pack(bubble_table, sub_label, 0, 1, 1, 1);
         break;
      case MESSAGE_BUBBLE_RECEIVE:
         evas_object_size_hint_align_set(bubble_table, 0.0, 0.0);
         evas_object_size_hint_align_set(sub_label, 0.0, EVAS_HINT_FILL);
         evas_object_color_set(bg, 100, 170, 200, 255);
         elm_table_pack(bubble_table, button, 0, 0, 1, 2);
         elm_table_pack(bubble_table, main_label, 0, 0, 1, 1);
         elm_table_pack(bubble_table, sub_label, 0, 1, 1, 1);
         break;
      case MESSAGE_BUBBLE_NONE:
      case MESSAGE_BUBBLE_LAST:
      default:
         break;
   }

   return bubble_table;
}

Add the table component, button component, and the rectangle object in the layout using the create_input_field_table() function.

static Evas_Object *
create_input_field_table(appdata_s *ad)
{
   Evas_Object *table, *button, *bg;

   table = elm_table_add(ad->main_box);
   elm_table_homogeneous_set(table, EINA_TRUE);
   evas_object_size_hint_weight_set(table, EVAS_HINT_EXPAND, 0.0);
   evas_object_size_hint_align_set(table, EVAS_HINT_FILL, 1.0);
   evas_object_show(table);

Add another button and rectangle to show the background color of the new entry. The entry is added on multiple lines with a guide text. Add callback functions to change the background color, when the entry focus state changes.

   button = elm_button_add(table);
   elm_object_style_set(button, "transparent");
   evas_object_size_hint_weight_set(button, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);
   evas_object_size_hint_align_set(button, EVAS_HINT_FILL, EVAS_HINT_FILL);

   bg = evas_object_rectangle_add(evas_object_evas_get(button));
   elm_object_content_set(button, bg);
   evas_object_color_set(bg, 120, 220, 220, 255);
   evas_object_show(button);
   elm_table_pack(table, button, 0, 0, 3, 2);
   button = elm_button_add(table);
   elm_object_style_set(button, "transparent");
   evas_object_size_hint_weight_set(button, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);
   evas_object_size_hint_align_set(button, EVAS_HINT_FILL, EVAS_HINT_FILL);

   bg = evas_object_rectangle_add(evas_object_evas_get(button));
   elm_object_content_set(button, bg);
   evas_object_color_set(bg, 0, 0, 0, 0);
   evas_object_show(button);
   elm_table_pack(table, button, 0, 0, 2, 2);

   ad->input_field_entry = elm_entry_add(table);
   elm_object_part_text_set(ad->input_field_entry, "elm.guide", "Enter Message");
   evas_object_size_hint_weight_set(ad->input_field_entry, EVAS_HINT_EXPAND, 0.0);
   evas_object_size_hint_align_set(ad->input_field_entry, EVAS_HINT_FILL, EVAS_HINT_FILL);
   evas_object_smart_callback_add(ad->input_field_entry, "focused", entry_focused_cb, bg);
   evas_object_smart_callback_add(ad->input_field_entry, "unfocused", entry_unfocused_cb, bg);
   evas_object_show(ad->input_field_entry);
   elm_table_pack(table, ad->input_field_entry, 0, 0, 2, 2);

Clicking the Send button appends a new bubble to the message bubble box. The button handler includes a callback for detecting button clicks.

   button = elm_button_add(table);
   evas_object_size_hint_weight_set(button, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);
   evas_object_size_hint_align_set(button, 1.0, 1.0);
   elm_object_text_set(button, "SEND");
   evas_object_smart_callback_add(button, "clicked", send_button_clicked_cb, ad);
   evas_object_show(button);
   elm_table_pack(table, button, 2, 1, 1, 1);

   return table;
}

The send_message() function is called when the user clicks the Send button. It reads the text from the entry field and inserts it in a message bubble. The bubble is appended to the bottom of bubble box using the elm_box_pack_end() function.

Displaying the message bubble requires detecting the height of the bubble box. This is done using the elm_scroller_child_size_get() function. After this, the scroller is moved to the bottom of the screen using the elm_scroller_region_show() function.

static void
send_message(appdata_s *ad)
{
   Evas_Coord w, h;
   Evas_Object *bubble_table;
   const char *main_text = NULL;

   if (!ad->input_field_entry)
      return;
   main_text = elm_entry_entry_get(ad->input_field_entry);
   if (!main_text || (strlen(main_text) == 0))
      return;

   bubble_table = create_bubble_table(ad->bubble_box, MESSAGE_BUBBLE_SENT,
         elm_entry_entry_get(ad->input_field_entry),
         "00:00 AM");
   evas_object_show(bubble_table);
   elm_box_pack_end(ad->bubble_box, bubble_table);
   ad->num_of_bubbles++;
   ad->total_messages++;
   elm_entry_entry_set(ad->input_field_entry, "");
   elm_scroller_child_size_get(ad->bubble_scroller, &w, &h);
   elm_scroller_region_show(ad->bubble_scroller, 0, h, 0, 0);
}