Upon reviewing the elm_glview, I've realized a few issues and mistakes that i've
[framework/uifw/elementary.git] / src / lib / elm_glview.c
1 #include <Elementary.h>
2 #include "elm_priv.h"
3
4 /**
5  * @defgroup GLView
6  *
7  * A simple GLView widget that allows GL rendering.
8  *
9  * Signals that you can add callbacks for are:
10  *
11  */
12 typedef struct _Widget_Data Widget_Data;
13
14 struct _Widget_Data
15 {
16    Evas_Object              *glview_image;
17
18    Elm_GLView_Mode           mode;
19    Elm_GLView_Resize_Policy  scale_policy;
20    Elm_GLView_Render_Policy  render_policy;
21
22    Evas_GL                  *evasgl;
23    Evas_GL_Config            config;
24    Evas_GL_Surface          *surface;
25    Evas_GL_Context          *context;
26
27    Evas_Coord                w, h;
28
29    Elm_GLView_Func           init_func;
30    Elm_GLView_Func           del_func;
31    Elm_GLView_Func           resize_func;
32    Elm_GLView_Func           render_func;
33
34    Ecore_Idle_Enterer       *render_idle_enterer;
35
36    Eina_Bool                 initialized;
37    Eina_Bool                 resized;
38 };
39
40 static const char *widtype = NULL;
41 static void _del_hook(Evas_Object *obj);
42 static void _on_focus_hook(void *data, Evas_Object *obj);
43
44 static const char SIG_FOCUSED[] = "focused";
45 static const char SIG_UNFOCUSED[] = "unfocused";
46
47 static void
48 _del_hook(Evas_Object *obj)
49 {
50    Widget_Data *wd = elm_widget_data_get(obj);
51    if (!wd) return;
52    
53    // Call delete func if it's registered
54    if (wd->del_func) 
55      {
56         evas_gl_make_current(wd->evasgl, wd->surface, wd->context);
57         wd->del_func(obj);
58      }
59
60    if (wd->render_idle_enterer) ecore_idle_enterer_del(wd->render_idle_enterer);
61
62    if (wd->surface) evas_gl_surface_destroy(wd->evasgl, wd->surface);
63    if (wd->context) evas_gl_context_destroy(wd->evasgl, wd->context);
64    if (wd->evasgl) evas_gl_free(wd->evasgl);
65
66    free(wd);
67 }
68
69 static void
70 _on_focus_hook(void *data __UNUSED__, Evas_Object *obj)
71 {
72    Widget_Data *wd = elm_widget_data_get(obj);
73    if (!wd) return;
74
75    if (elm_widget_focus_get(obj))
76      {
77         evas_object_focus_set(wd->glview_image, EINA_TRUE);
78         evas_object_smart_callback_call(obj, SIG_FOCUSED, NULL);
79      }
80    else
81      {
82         evas_object_focus_set(wd->glview_image, EINA_FALSE);
83         evas_object_smart_callback_call(obj, SIG_UNFOCUSED, NULL);
84      }
85 }
86
87 static void
88 _glview_update_surface(Evas_Object *obj)
89 {
90    Widget_Data *wd = elm_widget_data_get(obj);
91    if (!wd) return;
92
93    if (wd->surface)
94      {
95         evas_object_image_native_surface_set(wd->glview_image, NULL);
96         evas_gl_surface_destroy(wd->evasgl, wd->surface);
97         wd->surface = NULL;
98      }
99
100    evas_object_image_size_set(wd->glview_image, wd->w, wd->h);
101
102    if (!wd->surface)
103      {
104         Evas_Native_Surface ns;
105
106         wd->surface = evas_gl_surface_create(wd->evasgl, &wd->config,
107                                              wd->w, wd->h);
108         evas_gl_native_surface_get(wd->evasgl, wd->surface, &ns);
109         evas_object_image_native_surface_set(wd->glview_image, &ns);
110         elm_glview_changed_set(obj);
111      }
112 }
113
114 static void
115 _glview_resize(void *data, Evas *e __UNUSED__, Evas_Object *obj __UNUSED__, void *event_info __UNUSED__)
116 {
117    Widget_Data *wd = elm_widget_data_get(data);
118    Evas_Coord w, h;
119
120    if (!wd) return;
121
122    wd->resized = EINA_TRUE;
123
124    if (wd->scale_policy == ELM_GLVIEW_RESIZE_POLICY_RECREATE)
125      {
126         evas_object_geometry_get(wd->glview_image, NULL, NULL, &w, &h);
127         if ((w == 0) || (h == 0))
128           {
129              w = 64;
130              h = 64;
131           }
132         if ((wd->w == w) && (wd->h == h)) return;
133         wd->w = w;
134         wd->h = h;
135         _glview_update_surface(data);
136         /*
137         if (wd->render_func)
138           {
139              evas_gl_make_current(wd->evasgl, wd->surface, wd->context);
140              wd->render_func(data);
141           }
142           */
143      }
144 }
145
146 static Eina_Bool
147 _render_cb(void *obj)
148 {
149    Widget_Data *wd = elm_widget_data_get(obj);
150    if (!wd) return EINA_FALSE;
151
152    // Do a make current
153    if (!evas_gl_make_current(wd->evasgl, wd->surface, wd->context))
154      {
155         wd->render_idle_enterer = NULL;
156         ERR("Failed doing make current.\n");
157         return EINA_FALSE;
158      }
159
160    // Call the init function if it hasn't been called already
161    if (!wd->initialized)
162      {
163         if (wd->init_func) wd->init_func(obj);
164         wd->initialized = EINA_TRUE;
165      }
166
167    if (wd->resized)
168      {
169         if (wd->resize_func) wd->resize_func(obj);
170         wd->resized = EINA_FALSE;
171      }
172
173    // Call the render function
174    if (wd->render_func) wd->render_func(obj);
175
176    // Depending on the policy return true or false
177    if (wd->render_policy == ELM_GLVIEW_RENDER_POLICY_ON_DEMAND)
178      return EINA_TRUE;
179    else if (wd->render_policy == ELM_GLVIEW_RENDER_POLICY_ALWAYS)
180      {
181         // Return false so it only runs once
182         wd->render_idle_enterer = NULL;
183         return EINA_FALSE;
184      }
185    else
186      {
187         ERR("Invalid Render Policy.\n");
188         wd->render_idle_enterer = NULL;
189         return EINA_FALSE;
190      }
191    return EINA_TRUE;
192 }
193
194 static void
195 _set_render_policy_callback(Evas_Object *obj)
196 {
197    Widget_Data *wd = elm_widget_data_get(obj);
198
199    switch (wd->render_policy)
200      {
201       case ELM_GLVIEW_RENDER_POLICY_ON_DEMAND:
202          // Delete idle_enterer if it for some reason is around
203          if (wd->render_idle_enterer)
204            {
205               ecore_idle_enterer_del(wd->render_idle_enterer);
206               wd->render_idle_enterer = NULL;
207            }
208
209          // Set pixel getter callback
210          evas_object_image_pixels_get_callback_set
211             (wd->glview_image, (Evas_Object_Image_Pixels_Get_Cb)_render_cb, obj);
212          break;
213       case ELM_GLVIEW_RENDER_POLICY_ALWAYS:
214          // Unset the pixel getter callback if set already
215          evas_object_image_pixels_get_callback_set(wd->glview_image, NULL, NULL);
216
217          break;
218       default:
219          ERR("Invalid Render Policy.\n");
220          return;
221      }
222 }
223
224 /**
225  * Add a new glview to the parent
226  *
227  * @param parent The parent object
228  * @return The new object or NULL if it cannot be created
229  *
230  * @ingroup GLView
231  */
232 EAPI Evas_Object *
233 elm_glview_add(Evas_Object *parent)
234 {
235    Evas_Object *obj;
236    Evas *e;
237    Widget_Data *wd;
238    Evas_GL_Config cfg = { EVAS_GL_RGB_8,
239                           EVAS_GL_DEPTH_NONE,
240                           EVAS_GL_STENCIL_NONE };
241
242    ELM_WIDGET_STANDARD_SETUP(wd, Widget_Data, parent, e, obj, NULL);
243
244    ELM_SET_WIDTYPE(widtype, "glview");
245    elm_widget_type_set(obj, "glview");
246    elm_widget_sub_object_add(parent, obj);
247    elm_widget_on_focus_hook_set(obj, _on_focus_hook, NULL);
248    elm_widget_data_set(obj, wd);
249    elm_widget_del_hook_set(obj, _del_hook);
250
251    // Evas_GL
252    wd->evasgl = evas_gl_new(e);
253    if (!wd->evasgl)
254      {
255         ERR("Failed Creating an Evas GL Object.\n");
256         return NULL;
257      }
258
259    // Create image to render Evas_GL Surface
260    wd->glview_image = evas_object_image_filled_add(e);
261    evas_object_image_size_set(wd->glview_image, 1, 1);
262    evas_object_event_callback_add(wd->glview_image, EVAS_CALLBACK_RESIZE,
263                                   _glview_resize, obj);
264    elm_widget_resize_object_set(obj, wd->glview_image);
265    evas_object_show(wd->glview_image);
266
267    // Initialize variables
268    wd->mode                = 0;
269    wd->scale_policy        = ELM_GLVIEW_RESIZE_POLICY_RECREATE;
270    wd->render_policy       = ELM_GLVIEW_RENDER_POLICY_ON_DEMAND;
271    wd->config              = cfg;
272    wd->surface             = NULL;
273
274    // Initialize it to (64,64)  (It's an arbitrary value)
275    wd->w                   = 64;
276    wd->h                   = 64;
277
278    // Initialize the rest of the values
279    wd->init_func           = NULL;
280    wd->del_func            = NULL;
281    wd->render_func         = NULL;
282    wd->render_idle_enterer = NULL;
283    wd->initialized         = EINA_FALSE;
284    wd->resized             = EINA_FALSE;
285
286    // Create Context
287    if (!wd->context)
288      {
289         wd->context = evas_gl_context_create(wd->evasgl, NULL);
290         if (!wd->context)
291           {
292              ERR("Error Creating an Evas_GL Context.\n");
293              return NULL;
294           }
295      }
296    return obj;
297 }
298
299 /**
300  * Gets the gl api struct for gl rendering
301  *
302  * @param obj The glview object
303  * @return The api object or NULL if it cannot be created
304  *
305  * @ingroup GLView
306  */
307 EAPI Evas_GL_API *
308 elm_glview_gl_api_get(const Evas_Object *obj)
309 {
310    ELM_CHECK_WIDTYPE(obj, widtype) NULL;
311    Widget_Data *wd = elm_widget_data_get(obj);
312    if (!wd) return NULL;
313
314    return evas_gl_api_get(wd->evasgl);
315 }
316
317
318 /**
319  * Set the mode of the GLView. Supports Three simple modes.
320  *
321  * @param obj The glview object
322  * @param mode The mode Options OR'ed enabling Alpha, Depth, Stencil.
323  * @return True if set properly.
324  *
325  * @ingroup GLView
326  */
327 EAPI Eina_Bool
328 elm_glview_mode_set(Evas_Object *obj, Elm_GLView_Mode mode)
329 {
330    ELM_CHECK_WIDTYPE(obj, widtype) EINA_FALSE;
331    Widget_Data *wd = elm_widget_data_get(obj);
332    Evas_GL_Config cfg = { EVAS_GL_RGBA_8,
333                           EVAS_GL_DEPTH_NONE,
334                           EVAS_GL_STENCIL_NONE };
335    if (!wd) return EINA_FALSE;
336
337    // Set the configs
338    if (mode & ELM_GLVIEW_ALPHA)
339      cfg.color_format = EVAS_GL_RGBA_8;
340
341    if (mode & ELM_GLVIEW_DEPTH)
342      cfg.depth_bits = EVAS_GL_DEPTH_BIT_24;
343
344    if (mode & ELM_GLVIEW_STENCIL)
345      cfg.stencil_bits = EVAS_GL_STENCIL_BIT_8;
346
347    // Check for Alpha Channel and enable it
348    if (mode & ELM_GLVIEW_ALPHA)
349      evas_object_image_alpha_set(wd->glview_image, EINA_TRUE);
350    else
351      evas_object_image_alpha_set(wd->glview_image, EINA_FALSE);
352
353    wd->mode   = mode;
354    wd->config = cfg;
355
356    elm_glview_changed_set(obj);
357
358    return EINA_TRUE;
359 }
360
361 /**
362  * Set the resize policy for the glview object.
363  *
364  * @param obj The glview object.
365  * @param policy The scaling policy.
366  *
367  * By default, the resize policy is set to ELM_GLVIEW_RESIZE_POLICY_RECREATE.
368  * When resize is called it destroys the previous surface and recreates the newly
369  * specified size. If the policy is set to ELM_GLVIEW_RESIZE_POLICY_SCALE, however,
370  * glview only scales the image object and not the underlying GL Surface.
371  *
372  * @ingroup GLView
373  */
374 EAPI Eina_Bool
375 elm_glview_resize_policy_set(Evas_Object *obj, Elm_GLView_Resize_Policy policy)
376 {
377    ELM_CHECK_WIDTYPE(obj, widtype) EINA_FALSE;
378    Widget_Data *wd = elm_widget_data_get(obj);
379    if (!wd) return EINA_FALSE;
380
381    if (policy == wd->scale_policy) return EINA_TRUE;
382    switch (policy)
383      {
384       case ELM_GLVIEW_RESIZE_POLICY_RECREATE:
385       case ELM_GLVIEW_RESIZE_POLICY_SCALE:
386          wd->scale_policy = policy;
387          return EINA_TRUE;
388       default:
389          ERR("Invalid Scale Policy.\n");
390          return EINA_FALSE;
391      }
392    _glview_update_surface(obj);
393    elm_glview_changed_set(obj);
394 }
395
396 /**
397  * Set the render policy for the glview object.
398  *
399  * @param obj The glview object.
400  * @param policy The render policy.
401  *
402  * By default, the render policy is set to ELM_GLVIEW_RENDER_POLICY_ON_DEMAND.
403  * This policy is set such that during the render loop, glview is only redrawn
404  * if it needs to be redrawn. (i.e. When it is visible) If the policy is set
405  * to ELM_GLVIEWW_RENDER_POLICY_ALWAYS, it redraws regardless of whether it is
406  * visible/need redrawing or not.
407  *
408  * @ingroup GLView
409  */
410 EAPI Eina_Bool
411 elm_glview_render_policy_set(Evas_Object *obj, Elm_GLView_Render_Policy policy)
412 {
413    ELM_CHECK_WIDTYPE(obj, widtype) EINA_FALSE;
414    Widget_Data *wd = elm_widget_data_get(obj);
415    if (!wd) return EINA_FALSE;
416
417    if ((policy != ELM_GLVIEW_RENDER_POLICY_ON_DEMAND) &&
418        (policy != ELM_GLVIEW_RENDER_POLICY_ALWAYS))
419      {
420         ERR("Invalid Render Policy.\n");
421         return EINA_FALSE;
422      }
423    if (wd->render_policy == policy) return EINA_TRUE;
424    wd->render_policy = policy;
425    _set_render_policy_callback(obj);
426    _glview_update_surface(obj);
427    return EINA_TRUE;
428 }
429
430 /**
431  * Sets the size of the glview
432  *
433  * @param obj The glview object
434  * @param width width of the glview object
435  * @param height height of the glview object
436  *
437  * @ingroup GLView
438  */
439 EAPI void
440 elm_glview_size_set(Evas_Object *obj, int width, int height)
441 {
442    ELM_CHECK_WIDTYPE(obj, widtype);
443    Widget_Data *wd = elm_widget_data_get(obj);
444    if (!wd) return;
445
446    if ((width == wd->w) && (height == wd->h)) return;
447    wd->w = width;
448    wd->h = height;
449    _glview_update_surface(obj);
450    elm_glview_changed_set(obj);
451 }
452
453 /**
454  * Gets the size of the glview.
455  *
456  * @param obj The glview object
457  * @param width width of the glview object
458  * @param height height of the glview object
459  *
460  * Note that this function returns the actual image size of the glview.
461  * This means that when the scale policy is set to ELM_GLVIEW_RESIZE_POLICY_SCALE,
462  * it'll return the non-scaled size.
463  *
464  * @ingroup GLView
465  */
466 EAPI void
467 elm_glview_size_get(const Evas_Object *obj, int *width, int *height)
468 {
469    ELM_CHECK_WIDTYPE(obj, widtype);
470    Widget_Data *wd = elm_widget_data_get(obj);
471    if (!wd) return;
472
473    if (width) *width = wd->w;
474    if (height) *height = wd->h;
475 }
476
477 /**
478  * Set the init function that runs once in the main loop.
479  *
480  * @param obj The glview object.
481  * @param func The init function to be registered.
482  *
483  * The registered init function gets called once during the render loop.
484  * 
485  * @ingroup GLView
486  */
487 EAPI void
488 elm_glview_init_func_set(Evas_Object *obj, Elm_GLView_Func func)
489 {
490    ELM_CHECK_WIDTYPE(obj, widtype);
491    Widget_Data *wd = elm_widget_data_get(obj);
492    if (!wd) return;
493
494    wd->initialized = EINA_FALSE;
495    wd->init_func = func;
496 }
497
498 /**
499  * Set the render function that runs in the main loop.
500  *
501  * @param obj The glview object.
502  * @param func The delete function to be registered.
503  *
504  * The registered del function gets called when GLView object is deleted.
505  * 
506  * @ingroup GLView
507  */
508 EAPI void
509 elm_glview_del_func_set(Evas_Object *obj, Elm_GLView_Func func)
510 {
511    ELM_CHECK_WIDTYPE(obj, widtype);
512    Widget_Data *wd = elm_widget_data_get(obj);
513     if (!wd) return;
514
515    wd->del_func = func;
516 }
517
518 /**
519  * Set the resize function that gets called when resize happens.
520  *
521  * @param obj The glview object.
522  * @param func The resize function to be registered.
523  *
524  * @ingroup GLView
525  */
526 EAPI void
527 elm_glview_resize_func_set(Evas_Object *obj, Elm_GLView_Func func)
528 {
529    ELM_CHECK_WIDTYPE(obj, widtype);
530    Widget_Data *wd = elm_widget_data_get(obj);
531     if (!wd) 
532      {
533         ERR("Invalid Widget Object.\n");
534         return;
535      }
536
537    wd->resize_func = func;
538 }
539
540
541 /**
542  * Set the render function that runs in the main loop.
543  *
544  * @param obj The glview object.
545  * @param func The render function to be registered.
546  *
547  * @ingroup GLView
548  */
549 EAPI void
550 elm_glview_render_func_set(Evas_Object *obj, Elm_GLView_Func func)
551 {
552    ELM_CHECK_WIDTYPE(obj, widtype);
553    Widget_Data *wd = elm_widget_data_get(obj);
554    if (!wd) return;
555
556    wd->render_func = func;
557    _set_render_policy_callback(obj);
558 }
559
560 /**
561  * Notifies that there has been changes in the GLView.
562  *
563  * @param obj The glview object.
564  *
565  * @ingroup GLView
566  */
567 EAPI void
568 elm_glview_changed_set(Evas_Object *obj)
569 {
570    ELM_CHECK_WIDTYPE(obj, widtype);
571    Widget_Data *wd = elm_widget_data_get(obj);
572    if (!wd) return;
573
574    evas_object_image_pixels_dirty_set(wd->glview_image, EINA_TRUE);
575    if (wd->render_policy == ELM_GLVIEW_RENDER_POLICY_ALWAYS)
576      {
577         if (!wd->render_idle_enterer)
578           wd->render_idle_enterer = ecore_idle_enterer_before_add((Ecore_Task_Cb)_render_cb, obj);
579      }
580 }
581
582 /* vim:set ts=8 sw=3 sts=3 expandtab cino=>5n-2f0^-2{2(0W1st0 :*/