thiep pushed a commit to branch master. http://git.enlightenment.org/core/efl.git/commit/?id=b0d185ac127297f5dd73cb8b58966fd0256611d7
commit b0d185ac127297f5dd73cb8b58966fd0256611d7 Author: Thiep Ha <thie...@gmail.com> Date: Tue Aug 29 19:04:15 2017 +0900 Introduce text on path widget Text on path (textpath) allows application to make text follow a path. The path can be a efl_gfx_path or a circle. Thank hermet for initializing this work. @feature --- data/elementary/themes/Makefile.am | 1 + data/elementary/themes/default.edc | 2 + data/elementary/themes/edc/elm/textpath.edc | 24 + src/Makefile_Elementary.am | 4 + src/bin/elementary/test.c | 2 + src/bin/elementary/test_ui_textpath.c | 183 ++++++ src/lib/elementary/Elementary.h | 1 + src/lib/elementary/efl_ui_textpath.c | 673 +++++++++++++++++++++ src/lib/elementary/efl_ui_textpath.eo | 59 ++ .../elementary/efl_ui_textpath_internal_part.eo | 8 + 10 files changed, 957 insertions(+) diff --git a/data/elementary/themes/Makefile.am b/data/elementary/themes/Makefile.am index d7632d7641..ab354a0e9c 100644 --- a/data/elementary/themes/Makefile.am +++ b/data/elementary/themes/Makefile.am @@ -146,6 +146,7 @@ elementary/themes/edc/elm/separator.edc \ elementary/themes/edc/elm/slider.edc \ elementary/themes/edc/elm/slideshow.edc \ elementary/themes/edc/elm/spinner.edc \ +elementary/themes/edc/elm/textpath.edc \ elementary/themes/edc/elm/thumb.edc \ elementary/themes/edc/elm/toolbar.edc \ elementary/themes/edc/elm/tooltip.edc \ diff --git a/data/elementary/themes/default.edc b/data/elementary/themes/default.edc index 936df6b321..2a28c4a647 100644 --- a/data/elementary/themes/default.edc +++ b/data/elementary/themes/default.edc @@ -74,6 +74,8 @@ collections { #include "edc/elm/cursor.edc" #include "edc/elm/code.edc" #include "edc/elm/ews.edc" +#include "edc/elm/textpath.edc" + // desktop in general #include "edc/wallpaper.edc" diff --git a/data/elementary/themes/edc/elm/textpath.edc b/data/elementary/themes/edc/elm/textpath.edc new file mode 100644 index 0000000000..17c2baa4f9 --- /dev/null +++ b/data/elementary/themes/edc/elm/textpath.edc @@ -0,0 +1,24 @@ +group { name: "elm/textpath/base/default"; + styles { + style { name: "textpath_style"; + base: "font="FN" font_size=16 text_class=tb_plain wrap=none align=left color=#ffffffff style=shadow,bottom shadow_color=#00000080"; + tag: "br" "\n"; + tag: "b" "+ font="FNBD" text_class=tb_light"; + ENABLED_TEXTBLOCK_TAGS + } + } + parts { + part { name: "elm.text"; + type: TEXTBLOCK; + scale: 1; + description { state: "default" 0.0; + align: 0.0 0.0; + text { + style: "textpath_style"; + min: 0 1; + align: 0.0 0.5; + } + } + } + } +} diff --git a/src/Makefile_Elementary.am b/src/Makefile_Elementary.am index a99b24d37e..ad50ab466b 100644 --- a/src/Makefile_Elementary.am +++ b/src/Makefile_Elementary.am @@ -133,6 +133,8 @@ elm_public_eolian_files = \ lib/elementary/efl_ui_focus_manager_root_focus.eo \ lib/elementary/efl_ui_focus_object.eo \ lib/elementary/efl_ui_focus_user.eo \ + lib/elementary/efl_ui_textpath.eo \ + lib/elementary/efl_ui_textpath_internal_part.eo \ $(NULL) # Private classes (not exposed or shipped) @@ -690,6 +692,7 @@ lib_elementary_libelementary_la_SOURCES = \ lib/elementary/efl_ui_focus_manager_sub.c \ lib/elementary/efl_ui_focus_object.c \ lib/elementary/efl_ui_focus_manager_root_focus.c \ + lib/elementary/efl_ui_textpath.c \ $(NULL) @@ -860,6 +863,7 @@ bin/elementary/test_transit_bezier.c \ bin/elementary/test_ui_box.c \ bin/elementary/test_ui_clock.c \ bin/elementary/test_ui_grid.c \ +bin/elementary/test_ui_textpath.c \ bin/elementary/test_video.c \ bin/elementary/test_weather.c \ bin/elementary/test_web.c \ diff --git a/src/bin/elementary/test.c b/src/bin/elementary/test.c index 85b7b58318..4e8f76e539 100644 --- a/src/bin/elementary/test.c +++ b/src/bin/elementary/test.c @@ -316,6 +316,7 @@ void test_gfx_filters(void *data, Evas_Object *obj, void *event_info); void test_evas_snapshot(void *data, Evas_Object *obj, void *event_info); void test_evas_map(void *data, Edje_Object *obj, void *event_info); void test_efl_gfx_map(void *data, Edje_Object *obj, void *event_info); +void test_ui_textpath(void *data, Edje_Object *obj, void *event_info); Evas_Object *win, *tbx; // TODO: refactoring void *tt; @@ -970,6 +971,7 @@ add_tests: ADD_TEST(NULL, "Text", "Label Wrap", test_label_wrap); ADD_TEST(NULL, "Text", "Label Ellipsis", test_label_ellipsis); ADD_TEST(NULL, "Text", "Label Emoji", test_label_emoji); + ADD_TEST(NULL, "Text", "Text Path", test_ui_textpath); //------------------------------// ADD_TEST(NULL, "Stored Surface Buffer", "Launcher", test_launcher); diff --git a/src/bin/elementary/test_ui_textpath.c b/src/bin/elementary/test_ui_textpath.c new file mode 100644 index 0000000000..5860ff5a5c --- /dev/null +++ b/src/bin/elementary/test_ui_textpath.c @@ -0,0 +1,183 @@ +#include "test.h" +#ifdef HAVE_CONFIG_H +# include "elementary_config.h" +#endif + +#include <Elementary.h> + +#define CX 180 +#define CY 150 +#define CR 100 + +static Evas_Object *angle_sld, *slice_sld, *dir_chk; +static int path_type; + +static void +_autofit_changed_cb(void *data, const Efl_Event *event) +{ + Evas_Object *txtpath = data; + efl_ui_textpath_autofit_set(txtpath, elm_check_state_get(event->object)); +} + +static void +_ellipsis_changed_cb(void *data, const Efl_Event *event) +{ + Evas_Object *txtpath = data; + efl_ui_textpath_ellipsis_set(txtpath, elm_check_state_get(event->object)); +} + +static void +_direction_changed_cb(void *data, const Efl_Event *event) +{ + Evas_Object *txtpath = data; + int angle = elm_slider_value_get(angle_sld); + Eina_Bool val = elm_check_selected_get(event->object); + Efl_Ui_Textpath_Direction dir = val ? EFL_UI_TEXTPATH_DIRECTION_CW : + EFL_UI_TEXTPATH_DIRECTION_CCW; + efl_ui_textpath_circle_set(txtpath, CX, CY, CR, angle, dir); +} + +static void +_angle_changed_cb(void *data, const Efl_Event *event) +{ + Evas_Object *txtpath = data; + int angle = elm_slider_value_get(event->object); + printf("angle: %d\n", angle); + Eina_Bool val = elm_check_selected_get(dir_chk); + Efl_Ui_Textpath_Direction dir = val ? EFL_UI_TEXTPATH_DIRECTION_CW : + EFL_UI_TEXTPATH_DIRECTION_CCW; + efl_ui_textpath_circle_set(txtpath, CX, CY, CR, angle, dir); +} + +static void +_change_shape_cb(void *data, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED) +{ + Evas_Object *txtpath = data; + + efl_gfx_path_reset(txtpath); + path_type = (path_type + 1) % 2; + if (path_type == 1) + { + efl_gfx_path_append_move_to(txtpath, 20, 300); + efl_gfx_path_append_cubic_to(txtpath, 80, 80, 240, 270, 300, 20); + } + else + { + int angle = elm_slider_value_get(angle_sld); + Eina_Bool val = elm_check_selected_get(dir_chk); + Efl_Ui_Textpath_Direction dir = val ? EFL_UI_TEXTPATH_DIRECTION_CW : + EFL_UI_TEXTPATH_DIRECTION_CCW; + efl_ui_textpath_circle_set(txtpath, CX, CY, CR, angle, dir); + } +} + +static void +_slice_no_changed_cb(void *data, const Efl_Event *event) +{ + Evas_Object *txtpath = data; + int slice_no = elm_slider_value_get(event->object); + printf("Slice no: %d\n", slice_no); + efl_ui_textpath_slice_number_set(txtpath, slice_no); +} + +void +test_ui_textpath(void *data EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED) +{ + Evas_Object *win, *txtpath, *box, *hbox, *chk, *sld, *btn; + + elm_policy_set(ELM_POLICY_QUIT, ELM_POLICY_QUIT_LAST_WINDOW_CLOSED); + win = elm_win_util_standard_add("efl.ui.textpath", "TextPath test"); + elm_win_autodel_set(win, EINA_TRUE); + + box = elm_box_add(win); + elm_box_horizontal_set(box, EINA_FALSE); + efl_gfx_size_hint_weight_set(box, EFL_GFX_SIZE_HINT_EXPAND, EFL_GFX_SIZE_HINT_EXPAND); + efl_gfx_size_hint_align_set(box, EFL_GFX_SIZE_HINT_FILL, EFL_GFX_SIZE_HINT_FILL); + efl_gfx_visible_set(box, EINA_TRUE); + + txtpath = efl_add(EFL_UI_TEXTPATH_CLASS, win); + elm_box_pack_end(box, txtpath); + efl_ui_textpath_autofit_set(txtpath, EINA_TRUE); + + efl_text_set(txtpath, "This text follows the path which you defined. This is a <long> text designed to make it ellipsis."); + + efl_ui_textpath_circle_set(txtpath, CX, CY, CR, 0, EFL_UI_TEXTPATH_DIRECTION_CCW); + efl_gfx_visible_set(txtpath, EINA_TRUE); + path_type = 0; + + hbox = elm_box_add(win); + elm_box_horizontal_set(hbox, EINA_TRUE); + efl_gfx_size_hint_weight_set(hbox, EFL_GFX_SIZE_HINT_EXPAND, EFL_GFX_SIZE_HINT_EXPAND); + efl_gfx_size_hint_align_set(hbox, EFL_GFX_SIZE_HINT_FILL, EFL_GFX_SIZE_HINT_FILL); + efl_gfx_visible_set(hbox, EINA_TRUE); + elm_box_pack_end(box, hbox); + + chk = elm_check_add(win); + elm_object_text_set(chk, "Autofit"); + elm_check_state_set(chk, efl_ui_textpath_autofit_get(txtpath)); + efl_event_callback_add(chk, EFL_UI_CHECK_EVENT_CHANGED, _autofit_changed_cb, txtpath); + elm_box_pack_end(hbox, chk); + efl_gfx_visible_set(chk, EINA_TRUE); + + chk = elm_check_add(win); + elm_object_text_set(chk, "Ellipsis"); + elm_check_state_set(chk, efl_ui_textpath_ellipsis_get(txtpath)); + efl_event_callback_add(chk, EFL_UI_CHECK_EVENT_CHANGED, _ellipsis_changed_cb, txtpath); + elm_box_pack_end(hbox, chk); + efl_gfx_visible_set(chk, EINA_TRUE); + + chk = elm_check_add(win); + elm_object_text_set(chk, "Clockwise"); + efl_event_callback_add(chk, EFL_UI_CHECK_EVENT_CHANGED, _direction_changed_cb, txtpath); + elm_box_pack_end(hbox, chk); + efl_gfx_visible_set(chk, EINA_TRUE); + dir_chk = chk; + + hbox = elm_box_add(win); + elm_box_horizontal_set(hbox, EINA_TRUE); + efl_gfx_size_hint_weight_set(hbox, EFL_GFX_SIZE_HINT_EXPAND, EFL_GFX_SIZE_HINT_EXPAND); + efl_gfx_size_hint_align_set(hbox, EFL_GFX_SIZE_HINT_FILL, EFL_GFX_SIZE_HINT_FILL); + efl_gfx_visible_set(hbox, EINA_TRUE); + elm_box_pack_end(box, hbox); + + sld = elm_slider_add(win); + elm_object_text_set(sld, "Angle"); + elm_slider_min_max_set(sld, 0, 360); + elm_slider_value_set(sld, 0); + efl_gfx_size_hint_align_set(sld, 0.5, EFL_GFX_SIZE_HINT_FILL); + efl_gfx_size_hint_weight_set(sld, EFL_GFX_SIZE_HINT_EXPAND, EFL_GFX_SIZE_HINT_EXPAND); + efl_gfx_size_hint_min_set(sld, 150, 0); + efl_event_callback_add(sld, EFL_UI_SLIDER_EVENT_CHANGED, _angle_changed_cb, txtpath); + elm_box_pack_end(hbox, sld); + efl_gfx_visible_set(sld, EINA_TRUE); + angle_sld = sld; + + sld = elm_slider_add(win); + elm_object_text_set(sld, "Slice No"); + elm_slider_min_max_set(sld, 20, 300); + elm_slider_value_set(sld, 99); + efl_gfx_size_hint_align_set(sld, 0.5, EFL_GFX_SIZE_HINT_FILL); + efl_gfx_size_hint_weight_set(sld, EFL_GFX_SIZE_HINT_EXPAND, EFL_GFX_SIZE_HINT_EXPAND); + efl_gfx_size_hint_min_set(sld, 150, 0); + efl_event_callback_add(sld, EFL_UI_SLIDER_EVENT_CHANGED, _slice_no_changed_cb, txtpath); + elm_box_pack_end(hbox, sld); + efl_gfx_visible_set(sld, EINA_TRUE); + slice_sld = sld; + + hbox = elm_box_add(win); + elm_box_horizontal_set(hbox, EINA_TRUE); + efl_gfx_size_hint_weight_set(hbox, EFL_GFX_SIZE_HINT_EXPAND, EFL_GFX_SIZE_HINT_EXPAND); + efl_gfx_size_hint_align_set(hbox, EFL_GFX_SIZE_HINT_FILL, EFL_GFX_SIZE_HINT_FILL); + efl_gfx_visible_set(hbox, EINA_TRUE); + elm_box_pack_end(box, hbox); + + btn = elm_button_add(win); + elm_object_text_set(btn, "Change Path"); + evas_object_smart_callback_add(btn, "clicked", _change_shape_cb, txtpath); + elm_box_pack_end(hbox, btn); + efl_gfx_visible_set(btn, EINA_TRUE); + + elm_win_resize_object_add(win, box); + efl_gfx_size_set(win, 400, 400); + efl_gfx_visible_set(win, 1); +} diff --git a/src/lib/elementary/Elementary.h b/src/lib/elementary/Elementary.h index 08f884c1a8..f73df08558 100644 --- a/src/lib/elementary/Elementary.h +++ b/src/lib/elementary/Elementary.h @@ -150,6 +150,7 @@ EAPI extern Elm_Version *elm_version; # include "efl_ui_focus_manager_sub.eo.h" # include "efl_ui_focus_manager_root_focus.eo.h" # include "efl_ui_focus_user.eo.h" +# include <efl_ui_textpath.eo.h> #endif #include <elm_tooltip.h> diff --git a/src/lib/elementary/efl_ui_textpath.c b/src/lib/elementary/efl_ui_textpath.c new file mode 100644 index 0000000000..57d4f55810 --- /dev/null +++ b/src/lib/elementary/efl_ui_textpath.c @@ -0,0 +1,673 @@ +#ifdef HAVE_CONFIG_H +# include "elementary_config.h" +#endif + +#define ELM_LAYOUT_PROTECTED + +#include <Elementary.h> +#include "elm_priv.h" + +#include "elm_widget_layout.h" +#include "efl_ui_textpath_internal_part.eo.h" +#include "elm_part_helper.h" + + +#define MY_CLASS EFL_UI_TEXTPATH_CLASS + +#define MY_CLASS_NAME "Efl.Ui.Textpath" +#define MY_CLASS_NAME_LEGACY "elm_textpath" + +#define SLICE_DEFAULT_NO 99 + +typedef struct _Efl_Ui_Textpath_Point Efl_Ui_Textpath_Point; +typedef struct _Efl_Ui_Textpath_Line Efl_Ui_Textpath_Line; +typedef struct _Efl_Ui_Textpath_Segment Efl_Ui_Textpath_Segment; +typedef struct _Efl_Ui_Textpath_Data Efl_Ui_Textpath_Data; + +struct _Efl_Ui_Textpath_Point +{ + double x; + double y; +}; + +struct _Efl_Ui_Textpath_Line +{ + Efl_Ui_Textpath_Point start; + Efl_Ui_Textpath_Point end; +}; + +struct _Efl_Ui_Textpath_Segment +{ + EINA_INLIST; + int length; + Efl_Gfx_Path_Command_Type type; + union + { + Eina_Bezier bezier; + Efl_Ui_Textpath_Line line; + }; +}; + +struct _Efl_Ui_Textpath_Data +{ + Evas_Object *text_obj; + char *text; + Efl_Gfx_Path *path; + struct { + double x, y; + double radius; + double start_angle; + } circle; + Efl_Ui_Textpath_Direction direction; + int slice_no; + Eina_Bool autofit; + Eina_Bool ellipsis; + + Eina_Inlist *segments; + int total_length; +}; + +#define EFL_UI_TEXTPATH_DATA_GET(o, sd) \ + Efl_Ui_Textpath_Data *sd = efl_data_scope_get(o, EFL_UI_TEXTPATH_CLASS) + +static inline double +_deg_to_rad(double angle) +{ + return angle / 180 * M_PI; +} + +static void +_segment_draw(Efl_Ui_Textpath_Data *pd, int slice_no, int w1, int w2, int cmp, Evas_Map *map, Eina_Bezier bezier) +{ + int x = 0, y = 0, w = 0, h = 0; + int i, len, seg_len; + double u0, u1, v0, v1; + double dist, t, dt; + double px, py, px2, py2; + double rad; + Eina_Vector2 vec, nvec, vec0, vec1, vec2, vec3; + Eina_Matrix2 mat; + + len = w2 - w1; + efl_gfx_size_get(pd->text_obj, &w, &h); + seg_len = eina_bezier_length_get(&bezier); + if (pd->autofit) + dt = len / (seg_len * (double) slice_no); + else + dt = 1.0 / (double) slice_no; + dist = len / (double)slice_no; + rad = _deg_to_rad(90); + eina_matrix2_values_set(&mat, cos(rad), -sin(rad), sin(rad), cos(rad)); + + eina_bezier_values_get(&bezier, NULL, NULL, NULL, NULL, NULL, NULL, &px2, &py2); + t = 0; + eina_bezier_point_at(&bezier, t, &px, &py); + eina_bezier_point_at(&bezier, t + dt, &px2, &py2); + + vec.x = (px2 - px); + vec.y = (py2 - py); + eina_vector2_normalize(&nvec, &vec); + + eina_vector2_transform(&vec, &mat, &nvec); + eina_vector2_normalize(&nvec, &vec); + eina_vector2_scale(&vec, &nvec, ((double) h) * 0.5); + + vec1.x = (vec.x + px); + vec1.y = (vec.y + py); + vec2.x = (-vec.x + px); + vec2.y = (-vec.y + py); + + //add points to map + for (i = 0; i < slice_no; i++) + { + //v0, v3 + vec0.x = vec1.x; + vec0.y = vec1.y; + vec3.x = vec2.x; + vec3.y = vec2.y; + + //v1, v2 + t = ((double) (i + 1) * dt); + eina_bezier_point_at(&bezier, t, &px, &py); + eina_bezier_point_at(&bezier, t + dt, &px2, &py2); + + vec.x = (px2 - px); + vec.y = (py2 - py); + eina_vector2_normalize(&nvec, &vec); + eina_vector2_transform(&vec, &mat, &nvec); + eina_vector2_normalize(&nvec, &vec); + eina_vector2_scale(&vec, &nvec, ((double) h) * 0.5); + + vec1.x = (vec.x + px); + vec1.y = (vec.y + py); + vec2.x = (-vec.x + px); + vec2.y = (-vec.y + py); + + evas_map_point_coord_set(map, cmp + i * 4, (int) vec0.x + x, (int) vec0.y + y, 0); + evas_map_point_coord_set(map, cmp + i * 4 + 1, (int) vec1.x + x, (int) vec1.y + y, 0); + evas_map_point_coord_set(map, cmp + i * 4 + 2, (int) vec2.x + x, (int) vec2.y + y, 0); + evas_map_point_coord_set(map, cmp + i * 4 + 3, (int) vec3.x + x, (int) vec3.y + y, 0); + + //UV + u0 = w1 + i * dist; + u1 = u0 + dist; + v0 = (double) 0; + v1 = (double) h; + + evas_map_point_image_uv_set(map, cmp + i * 4, u0, v0); + evas_map_point_image_uv_set(map, cmp + i * 4 + 1, u1, v0); + evas_map_point_image_uv_set(map, cmp + i * 4 + 2, u1, v1); + evas_map_point_image_uv_set(map, cmp + i * 4 + 3, u0, v1); + } +} + +static void +_text_on_line_draw(Efl_Ui_Textpath_Data *pd, int w1, int w2, int cmp, Evas_Map *map, Efl_Ui_Textpath_Line line) +{ + double x1, x2, y1, y2; + Evas_Coord x, y, w, h; + double line_len, len, sina, cosa; + + x1 = line.start.x; + y1 = line.start.y; + x2 = line.end.x; + y2 = line.end.y; + + line_len = sqrt((x2 - x1)*(x2 - x1) + (y2 - y1)*(y2 - y1)); + len = w2 - w1; + if (line_len > len) + { + x2 = x1 + len * (x2 - x1) / line_len; + y2 = y1 + len * (y2 - y1) / line_len; + } + + efl_gfx_geometry_get(pd->text_obj, &x, &y, &w, &h); + + len = sqrt((x2 - x1)*(x2 - x1) + (y2 - y1)*(y2 - y1)); + sina = (y2 - y1) / len; + cosa = (x2 - x1) / len; + + h = h / 2; + evas_map_point_coord_set(map, cmp + 3, x1 - h * sina, y1 + h * cosa, 0); + evas_map_point_coord_set(map, cmp + 2, x2 - h * sina, y2 + h * cosa, 0); + evas_map_point_coord_set(map, cmp + 1, x2 + h * sina, y2 - h * cosa, 0); + evas_map_point_coord_set(map, cmp + 0, x1 + h * sina, y1 - h * cosa, 0); + + h *= 2; + evas_map_point_image_uv_set(map, cmp + 0, w1, 0); + evas_map_point_image_uv_set(map, cmp + 1, w2, 0); + evas_map_point_image_uv_set(map, cmp + 2, w2, h); + evas_map_point_image_uv_set(map, cmp + 3, w1, h); +} + +static int +_map_point_calc(Efl_Ui_Textpath_Data *pd) +{ + int map_no = 0; + Efl_Ui_Textpath_Segment *seg; + + EINA_INLIST_FOREACH(pd->segments, seg) + { + if (seg->type == EFL_GFX_PATH_COMMAND_TYPE_LINE_TO) + { + map_no++; + } + else if (seg->type == EFL_GFX_PATH_COMMAND_TYPE_CUBIC_TO) + { + int no = pd->slice_no * seg->length / (double)pd->total_length; + map_no += no; + } + } + map_no *= 4; + + return map_no; +} + +static void +_text_draw(Efl_Ui_Textpath_Data *pd) +{ + Efl_Ui_Textpath_Segment *seg; + Evas_Map *map; + double slice_unit, slice_len; + int w, h, w1, w2; + int remained_w; + int total_slice, drawn_slice; + int cur_map_point = 0, map_point_no; + + efl_gfx_size_get(pd->text_obj, &w, &h); + if (pd->autofit) + remained_w = w; + else + remained_w = pd->total_length; + slice_unit = (double)pd->slice_no / pd->total_length; + + slice_len = 1.0 / slice_unit; + total_slice = w / slice_len + 1; + + map_point_no = _map_point_calc(pd); + if (map_point_no == 0) + { + evas_object_map_enable_set(pd->text_obj, EINA_FALSE); + return; + } + map = evas_map_new(map_point_no); + + w1 = w2 = 0; + EINA_INLIST_FOREACH(pd->segments, seg) + { + int len = seg->length; + if (!pd->autofit) + len = (double)seg->length * w / (double)pd->total_length; + if (remained_w <= 0) + break; + w2 = w1 + len; + if (w2 > w) + w2 = w; + if (seg->type == EFL_GFX_PATH_COMMAND_TYPE_LINE_TO) + { + drawn_slice += 1; + _text_on_line_draw(pd, w1, w2, cur_map_point, map, seg->line); + cur_map_point += 4; + } + else + { + int slice_no; + slice_no = pd->slice_no * seg->length / (double)pd->total_length; + if (slice_no == 0) + slice_no = len * slice_unit + 1; + if (remained_w == 0) + slice_no = total_slice - drawn_slice; + drawn_slice += slice_no; + _segment_draw(pd, slice_no, w1, w2, cur_map_point, map, seg->bezier); + cur_map_point += slice_no * 4; + } + w1 = w2; + remained_w -= len; + } + evas_object_map_enable_set(pd->text_obj, EINA_TRUE); + evas_object_map_set(pd->text_obj, map); + evas_map_free(map); +} + +static void +_path_data_get(Eo *obj, Efl_Ui_Textpath_Data *pd, Eina_Bool set_min) +{ + const Efl_Gfx_Path_Command_Type *cmd; + const double *points; + Efl_Ui_Textpath_Segment *seg; + + EINA_INLIST_FREE(pd->segments, seg) + { + pd->segments = eina_inlist_remove(pd->segments, EINA_INLIST_GET(seg)); + free(seg); + } + + Evas_Coord x, y; + efl_gfx_position_get(obj, &x, &y); + + pd->total_length = 0; + efl_gfx_path_get(obj, &cmd, &points); + if (cmd) + { + int pos = -1; + Eina_Rectangle *rect = eina_rectangle_new(0, 0, 0, 0); + + while (*cmd != EFL_GFX_PATH_COMMAND_TYPE_END) + { + double px0, py0, ctrl_x0, ctrl_y0, ctrl_x1, ctrl_y1, px1, py1; + + if (*cmd == EFL_GFX_PATH_COMMAND_TYPE_MOVE_TO) + { + pos++; + px0 = points[pos] + x; + pos++; + py0 = points[pos] + y; + } + else if (*cmd == EFL_GFX_PATH_COMMAND_TYPE_CUBIC_TO) + { + Eina_Bezier bz; + double bx, by, bw, bh; + + pos++; + ctrl_x0 = points[pos] + x; + pos++; + ctrl_y0 = points[pos] + y; + pos++; + ctrl_x1 = points[pos] + x; + pos++; + ctrl_y1 = points[pos] + y; + pos++; + px1 = points[pos] + x; + pos++; + py1 = points[pos] + y; + + eina_bezier_values_set(&bz, px0, py0, ctrl_x0, ctrl_y0, ctrl_x1, ctrl_y1, px1, py1); + seg = malloc(sizeof(Efl_Ui_Textpath_Segment)); + if (!seg) + { + ERR("Failed to allocate segment"); + px0 = px1; + py0 = py1; + continue; + } + seg->length = eina_bezier_length_get(&bz); + seg->bezier = bz; + seg->type = EFL_GFX_PATH_COMMAND_TYPE_CUBIC_TO; + pd->segments = eina_inlist_append(pd->segments, EINA_INLIST_GET(seg)); + pd->total_length += seg->length; + + //move points + px0 = px1; + py0 = py1; + + eina_bezier_bounds_get(&bz, &bx, &by, &bw, &bh); + Eina_Rectangle *brect = eina_rectangle_new(bx, by, bw, bh); + eina_rectangle_union(rect, brect); + eina_rectangle_free(brect); + } + else if (*cmd == EFL_GFX_PATH_COMMAND_TYPE_LINE_TO) + { + pos++; + px1 = points[pos] + x; + pos++; + py1 = points[pos] + y; + + seg = malloc(sizeof(Efl_Ui_Textpath_Segment)); + if (!seg) + { + ERR("Failed to allocate segment"); + px0 = px1; + py0 = py1; + } + seg->type = EFL_GFX_PATH_COMMAND_TYPE_LINE_TO; + seg->line.start.x = px0; + seg->line.start.y = py0; + seg->line.end.x = px1; + seg->line.end.y = py1; + seg->length = sqrt((px1 - px0)*(px1 - px0) + (py1 - py0)*(py1 - py0)); + pd->segments = eina_inlist_append(pd->segments, EINA_INLIST_GET(seg)); + pd->total_length += seg->length; + + Eina_Rectangle *lrect = eina_rectangle_new(px0, py0, px1 - px0, py1 - py0); + eina_rectangle_union(rect, lrect); + eina_rectangle_free(lrect); + } + cmd++; + } + if (set_min) + { + efl_gfx_size_hint_min_set(obj, rect->w, rect->h); + } + eina_rectangle_free(rect); + } +} + +static void +_sizing_eval(Efl_Ui_Textpath_Data *pd) +{ + _text_draw(pd); +} + +static void +_textpath_ellipsis_set(Efl_Ui_Textpath_Data *pd, Eina_Bool enabled) +{ + Eina_Strbuf *buf = eina_strbuf_new(); + const char *format; + + edje_object_part_text_style_user_pop(pd->text_obj, "elm.text"); + if (enabled) + { + eina_strbuf_append_printf(buf, "DEFAULT='ellipsis=1.0'"); + format = eina_stringshare_add(eina_strbuf_string_get(buf)); + eina_strbuf_free(buf); + edje_object_part_text_style_user_push(pd->text_obj, "elm.text", format); + } +} + +static void +_ellipsis_set(Efl_Ui_Textpath_Data *pd) +{ + if (!pd->text_obj) return; + + Evas_Coord w = 0, h = 0; + Eina_Bool is_ellipsis = EINA_FALSE; + const Evas_Object *tb; + + tb = edje_object_part_object_get(pd->text_obj, "elm.text"); + evas_object_textblock_size_native_get(tb, &w, &h); + evas_object_size_hint_min_set(pd->text_obj, w, h); + if (pd->ellipsis) + { + if (w > pd->total_length) + { + is_ellipsis = EINA_TRUE; + w = pd->total_length; + } + } + efl_gfx_size_set(pd->text_obj, w, h); + _textpath_ellipsis_set(pd, is_ellipsis); +} + +static void +_path_changed_cb(void *data, const Efl_Event *event EINA_UNUSED) +{ + EFL_UI_TEXTPATH_DATA_GET(data, sd); + + _path_data_get(data, sd, EINA_TRUE); + _sizing_eval(sd); +} + +static Eina_Bool +_textpath_text_set_internal(Eo *obj, Efl_Ui_Textpath_Data *pd, const char *part, const char *text) +{ + ELM_WIDGET_DATA_GET_OR_RETURN(obj, wd, EINA_FALSE); + Eina_Bool ret = EINA_TRUE; + + if (!text) text = ""; + ret = edje_object_part_text_set(pd->text_obj, part, text); + _ellipsis_set(pd); + + return ret; +} + +EOLIAN static void +_efl_ui_textpath_efl_canvas_group_group_calculate(Eo *obj EINA_UNUSED, Efl_Ui_Textpath_Data *pd) +{ + _sizing_eval(pd); +} + +EOLIAN static void +_efl_ui_textpath_efl_canvas_group_group_add(Eo *obj, Efl_Ui_Textpath_Data *priv) +{ + ELM_WIDGET_DATA_GET_OR_RETURN(obj, wd); + + efl_canvas_group_add(efl_super(obj, MY_CLASS)); + elm_widget_sub_object_parent_add(obj); + + priv->text_obj = edje_object_add(evas_object_evas_get(obj)); + elm_widget_theme_object_set(obj, priv->text_obj, "textpath", "base", + elm_widget_style_get(obj)); + efl_gfx_size_hint_weight_set(priv->text_obj, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND); + efl_gfx_size_hint_align_set(priv->text_obj, EVAS_HINT_FILL, EVAS_HINT_FILL); + efl_gfx_visible_set(priv->text_obj, EINA_TRUE); + + evas_object_smart_member_add(priv->text_obj, obj); + elm_widget_sub_object_add(obj, priv->text_obj); + + efl_event_callback_add(obj, EFL_GFX_PATH_EVENT_CHANGED, _path_changed_cb, obj); +} + +EOLIAN static void +_efl_ui_textpath_class_constructor(Efl_Class *klass) +{ + evas_smart_legacy_type_register(MY_CLASS_NAME_LEGACY, klass); +} + +EOLIAN static Efl_Object * +_efl_ui_textpath_efl_object_constructor(Eo *obj, Efl_Ui_Textpath_Data *pd) +{ + obj = efl_constructor(efl_super(obj, MY_CLASS)); + pd->autofit = EINA_TRUE; + pd->slice_no = SLICE_DEFAULT_NO; + pd->direction = EFL_UI_TEXTPATH_DIRECTION_CW; + + return obj; +} + +EOLIAN static void +_efl_ui_textpath_efl_object_destructor(Eo *obj, Efl_Ui_Textpath_Data *pd) +{ + Efl_Ui_Textpath_Segment *seg; + + if (pd->text) free(pd->text); + if (pd->text_obj) evas_object_del(pd->text_obj); + EINA_INLIST_FREE(pd->segments, seg) + { + pd->segments = eina_inlist_remove(pd->segments, EINA_INLIST_GET(seg)); + free(seg); + } + + efl_destructor(efl_super(obj, MY_CLASS)); +} + +EOLIAN static Eina_Bool +_efl_ui_textpath_text_set(Eo *obj, Efl_Ui_Textpath_Data *pd, const char *part, const char *text) +{ + return _textpath_text_set_internal(obj, pd, part, text); +} + +EOLIAN static const char * +_efl_ui_textpath_text_get(Eo *obj EINA_UNUSED, Efl_Ui_Textpath_Data *pd, const char *part) +{ + return edje_object_part_text_get(pd->text_obj, part); +} + +EOLIAN static void +_efl_ui_textpath_efl_text_text_set(Eo *obj, Efl_Ui_Textpath_Data *pd, const char *text) +{ + _textpath_text_set_internal(obj, pd, "elm.text", text); +} + +EOLIAN static const char * +_efl_ui_textpath_efl_text_text_get(Eo *obj EINA_UNUSED, Efl_Ui_Textpath_Data *pd) +{ + return edje_object_part_text_get(pd->text_obj, "elm.text"); +} + +EOLIAN static Efl_Ui_Theme_Apply +_efl_ui_textpath_elm_widget_theme_apply(Eo *obj, Efl_Ui_Textpath_Data *pd) +{ + Efl_Ui_Theme_Apply ret = EFL_UI_THEME_APPLY_FAILED; + + ret = elm_obj_widget_theme_apply(efl_super(obj, MY_CLASS)); + if (!ret) return EFL_UI_THEME_APPLY_FAILED; + + elm_widget_theme_object_set(obj, pd->text_obj, "textpath", "base", + elm_widget_style_get(obj)); + _ellipsis_set(pd); + + return ret; +} + +EOLIAN static void +_efl_ui_textpath_efl_gfx_position_set(Eo *obj, Efl_Ui_Textpath_Data *pd, Evas_Coord x, Evas_Coord y) +{ + efl_gfx_position_set(efl_super(obj, MY_CLASS), x, y); + _path_data_get(obj, pd, EINA_FALSE); + _text_draw(pd); +} + +EOLIAN static void +_efl_ui_textpath_efl_gfx_size_set(Eo *obj, Efl_Ui_Textpath_Data *pd EINA_UNUSED, Evas_Coord w, Evas_Coord h) +{ + efl_gfx_size_set(efl_super(obj, MY_CLASS), w, h); +} + +EOLIAN static void +_efl_ui_textpath_circle_set(Eo *obj, Efl_Ui_Textpath_Data *pd, double x, double y, double radius, double start_angle, Efl_Ui_Textpath_Direction direction) +{ + if (pd->circle.x == x && pd->circle.y == y && + pd->circle.radius == radius && + pd->circle.start_angle == start_angle && + pd->direction == direction && + _map_point_calc(pd) > 0) + { + ERR("Same circle"); + return; + } + pd->circle.x = x; + pd->circle.y = y; + pd->circle.radius = radius; + pd->circle.start_angle = start_angle; + pd->direction = direction; + + efl_gfx_path_reset(obj); + if (direction == EFL_UI_TEXTPATH_DIRECTION_CW) + { + efl_gfx_path_append_arc(obj, x - radius, y - radius, radius * 2, + radius * 2, start_angle, -360); + } + else + { + efl_gfx_path_append_arc(obj, x - radius, y - radius, radius * 2, + radius * 2, start_angle, 360); + } + + _sizing_eval(pd); +} + +EOLIAN static Eina_Bool +_efl_ui_textpath_autofit_get(Eo *obj EINA_UNUSED, Efl_Ui_Textpath_Data *pd) +{ + return pd->autofit; +} + +EOLIAN static void +_efl_ui_textpath_autofit_set(Eo *obj EINA_UNUSED, Efl_Ui_Textpath_Data *pd, Eina_Bool autofit) +{ + if (pd->autofit == autofit) return; + pd->autofit = autofit; + _sizing_eval(pd); +} + +EOLIAN static int +_efl_ui_textpath_slice_number_get(Eo *obj EINA_UNUSED, Efl_Ui_Textpath_Data *pd) +{ + return pd->slice_no; +} + +EOLIAN static void +_efl_ui_textpath_slice_number_set(Eo *obj EINA_UNUSED, Efl_Ui_Textpath_Data *pd, int slice_no) +{ + if (pd->slice_no == slice_no) return; + pd->slice_no = slice_no; + _sizing_eval(pd); +} + +EOLIAN static void +_efl_ui_textpath_ellipsis_set(Eo *obj EINA_UNUSED, Efl_Ui_Textpath_Data *pd, Eina_Bool ellipsis) +{ + if (pd->ellipsis == ellipsis) return; + pd->ellipsis = ellipsis; + + _ellipsis_set(pd); + _sizing_eval(pd); +} + +EOLIAN static Eina_Bool +_efl_ui_textpath_ellipsis_get(Eo *obj EINA_UNUSED, Efl_Ui_Textpath_Data *pd) +{ + return pd->ellipsis; +} + +/* Efl.Part begin */ +ELM_PART_OVERRIDE(efl_ui_textpath, EFL_UI_TEXTPATH, EFL_UI_LAYOUT, Efl_Ui_Textpath_Data, Elm_Part_Data) +ELM_PART_OVERRIDE_TEXT_SET(efl_ui_textpath, EFL_UI_TEXTPATH, EFL_UI_LAYOUT, Efl_Ui_Textpath_Data, Elm_Part_Data) +ELM_PART_OVERRIDE_TEXT_GET(efl_ui_textpath, EFL_UI_TEXTPATH, EFL_UI_LAYOUT, Efl_Ui_Textpath_Data, Elm_Part_Data) +#include "efl_ui_textpath_internal_part.eo.c" +/* Efl.Part end */ + +#define EFL_UI_TEXTPATH_EXTRA_OPS \ + EFL_CANVAS_GROUP_ADD_OPS(efl_ui_textpath) + +#include "efl_ui_textpath.eo.c" diff --git a/src/lib/elementary/efl_ui_textpath.eo b/src/lib/elementary/efl_ui_textpath.eo new file mode 100644 index 0000000000..6a5bab0940 --- /dev/null +++ b/src/lib/elementary/efl_ui_textpath.eo @@ -0,0 +1,59 @@ +enum Efl.Ui.Textpath.Direction { + cw, + ccw +} + +class Efl.Ui.Textpath (Efl.Ui.Layout, Efl.Object, Efl.Text, Efl.Gfx.Path) +{ + [[Efl Ui Textpath class]] + legacy_prefix: elm_textpath; + methods { + circle_set { + [[Set a circle with given center, radius, and start angle.]] + params { + @in x: double; + @in y: double; + @in radius: double; + @in start_angle: double; + @in direction: Efl.Ui.Textpath.Direction; + } + } + @property autofit { + [[The ability to fit the text within the path. + Set it to EINA_TRUE to let text occupy only portion + same as its size. Otherwise, text will occupied the whole path. + By default, it is EINA_TRUE.]] + values { + autofit: bool; + } + } + @property slice_number { + [[The number of slices. The larger the number of slice_num is, + The better the text follows the path.]] + values { + slice_no: int; + } + } + @property ellipsis { + [[Control the ellipsis behavior of the textpath.]] + set { + } + get { + } + values { + ellipsis: bool; [[To ellipsis text or not]] + } + } + } + implements { + class.constructor; + Efl.Object.constructor; + Efl.Object.destructor; + Efl.Canvas.Group.group_calculate; + Efl.Text.text {get; set;} + Efl.Part.part; + Elm.Widget.theme_apply; + Efl.Gfx.position { set; } + Efl.Gfx.size { set; } + } +} diff --git a/src/lib/elementary/efl_ui_textpath_internal_part.eo b/src/lib/elementary/efl_ui_textpath_internal_part.eo new file mode 100644 index 0000000000..daba6f3a33 --- /dev/null +++ b/src/lib/elementary/efl_ui_textpath_internal_part.eo @@ -0,0 +1,8 @@ +class Efl.Ui.Textpath.Internal.Part (Efl.Ui.Layout.Internal.Part, Efl.Text) +{ + [[Efl UI Textpath internal part class]] + data: null; + implements { + Efl.Text.text { set; get; } + } +} --