No issues found
1 /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
2 /*
3 * st-scroll-view-fade.h: Edge fade effect for StScrollView
4 *
5 * Copyright 2010 Intel Corporation.
6 * Copyright 2011 Adel Gadllah
7 *
8 * This program is free software; you can redistribute it and/or modify it
9 * under the terms and conditions of the GNU Lesser General Public License,
10 * version 2.1, as published by the Free Software Foundation.
11 *
12 * This program is distributed in the hope it will be useful, but WITHOUT ANY
13 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
14 * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
15 * more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public License
18 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 */
20
21
22 #define ST_SCROLL_VIEW_FADE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), ST_TYPE_SCROLL_VIEW_FADE, StScrollViewFadeClass))
23 #define ST_IS_SCROLL_VIEW_FADE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), ST_TYPE_SCROLL_VIEW_FADE))
24 #define ST_SCROLL_VIEW_FADE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), ST_TYPE_SCROLL_VIEW_FADE, StScrollViewFadeClass))
25
26 #include "st-scroll-view-fade.h"
27 #include "st-scroll-view.h"
28 #include "st-widget.h"
29 #include "st-theme-node.h"
30 #include "st-scroll-bar.h"
31 #include "st-scrollable.h"
32
33 #include <clutter/clutter.h>
34 #include <cogl/cogl.h>
35
36 typedef struct _StScrollViewFadeClass StScrollViewFadeClass;
37
38 #define DEFAULT_FADE_OFFSET 68.0f
39
40 static const gchar *fade_glsl_shader =
41 "uniform sampler2D tex;\n"
42 "uniform float height;\n"
43 "uniform float width;\n"
44 "uniform float offset_bottom;\n"
45 "uniform float offset_top;\n"
46 "uniform float offset_right;\n"
47 "uniform float offset_left;\n"
48 /*
49 * Used to pass the fade area to the shader
50 *
51 * [0][0] = x1
52 * [0][1] = y1
53 * [1][0] = x2
54 * [1][1] = y2
55 *
56 */
57 "uniform mat2 fade_area;\n"
58 "\n"
59 "void main ()\n"
60 "{\n"
61 " vec4 color = cogl_color_in * texture2D (tex, vec2 (cogl_tex_coord_in[0].xy));\n"
62 " float y = height * cogl_tex_coord_in[0].y;\n"
63 " float x = width * cogl_tex_coord_in[0].x;\n"
64 " float ratio = 1.0;\n"
65 " float fade_bottom_start = fade_area[1][1] - offset_bottom;\n"
66 " float fade_right_start = fade_area[1][0] - offset_right;\n"
67 " float ratio_top = y / offset_top;\n"
68 " float ratio_bottom = (fade_area[1][1] - y)/(fade_area[1][1] - fade_bottom_start);\n"
69 " float ratio_left = x / offset_left;\n"
70 " float ratio_right = (fade_area[1][0] - x)/(fade_area[1][0] - fade_right_start);\n"
71 " bool in_scroll_area = fade_area[0][0] <= x && fade_area[1][0] >= x;\n"
72 " bool fade_top = y < offset_top && in_scroll_area && (y >= fade_area[0][1]);\n"
73 " bool fade_bottom = y > fade_bottom_start && in_scroll_area && (y <= fade_area[1][1]);\n"
74 " bool fade_left = x < offset_left && in_scroll_area && (x >= fade_area[0][0]);\n"
75 " bool fade_right = x > fade_right_start && in_scroll_area && (x <= fade_area[1][0]);\n"
76 "\n"
77 " if (fade_top) {\n"
78 " ratio *= ratio_top;\n"
79 " }\n"
80 "\n"
81 " if (fade_bottom) {\n"
82 " ratio *= ratio_bottom;\n"
83 " }\n"
84 "\n"
85 " if (fade_left) {\n"
86 " ratio *= ratio_left;\n"
87 " }\n"
88 "\n"
89 " if (fade_right) {\n"
90 " ratio *= ratio_right;\n"
91 " }\n"
92 "\n"
93 " cogl_color_out = color * ratio;\n"
94 "}";
95
96 struct _StScrollViewFade
97 {
98 ClutterOffscreenEffect parent_instance;
99
100 /* a back pointer to our actor, so that we can query it */
101 ClutterActor *actor;
102
103 CoglHandle shader;
104 CoglHandle program;
105
106 gint tex_uniform;
107 gint height_uniform;
108 gint width_uniform;
109 gint fade_area_uniform;
110 gint offset_top_uniform;
111 gint offset_bottom_uniform;
112 gint offset_left_uniform;
113 gint offset_right_uniform;
114
115 StAdjustment *vadjustment;
116 StAdjustment *hadjustment;
117
118 guint is_attached : 1;
119
120 float vfade_offset;
121 float hfade_offset;
122 };
123
124 struct _StScrollViewFadeClass
125 {
126 ClutterOffscreenEffectClass parent_class;
127 };
128
129 G_DEFINE_TYPE (StScrollViewFade,
130 st_scroll_view_fade,
131 CLUTTER_TYPE_OFFSCREEN_EFFECT);
132
133 enum {
134 PROP_0,
135
136 PROP_VFADE_OFFSET,
137 PROP_HFADE_OFFSET
138 };
139
140 static gboolean
141 st_scroll_view_fade_pre_paint (ClutterEffect *effect)
142 {
143 StScrollViewFade *self = ST_SCROLL_VIEW_FADE (effect);
144 ClutterEffectClass *parent_class;
145
146 if (self->shader == COGL_INVALID_HANDLE)
147 return FALSE;
148
149 if (!clutter_actor_meta_get_enabled (CLUTTER_ACTOR_META (effect)))
150 return FALSE;
151
152 if (self->actor == NULL)
153 return FALSE;
154
155 if (self->program == COGL_INVALID_HANDLE)
156 self->program = cogl_create_program ();
157
158 if (!self->is_attached)
159 {
160 g_assert (self->shader != COGL_INVALID_HANDLE);
161 g_assert (self->program != COGL_INVALID_HANDLE);
162
163 cogl_program_attach_shader (self->program, self->shader);
164 cogl_program_link (self->program);
165
166 cogl_handle_unref (self->shader);
167
168 self->is_attached = TRUE;
169
170 self->tex_uniform =
171 cogl_program_get_uniform_location (self->program, "tex");
172 self->height_uniform =
173 cogl_program_get_uniform_location (self->program, "height");
174 self->width_uniform =
175 cogl_program_get_uniform_location (self->program, "width");
176 self->fade_area_uniform =
177 cogl_program_get_uniform_location (self->program, "fade_area");
178 self->offset_top_uniform =
179 cogl_program_get_uniform_location (self->program, "offset_top");
180 self->offset_bottom_uniform =
181 cogl_program_get_uniform_location (self->program, "offset_bottom");
182 self->offset_left_uniform =
183 cogl_program_get_uniform_location (self->program, "offset_left");
184 self->offset_right_uniform =
185 cogl_program_get_uniform_location (self->program, "offset_right");
186 }
187
188 parent_class = CLUTTER_EFFECT_CLASS (st_scroll_view_fade_parent_class);
189 return parent_class->pre_paint (effect);
190 }
191
192 static CoglHandle
193 st_scroll_view_fade_create_texture (ClutterOffscreenEffect *effect,
194 gfloat min_width,
195 gfloat min_height)
196 {
197 return cogl_texture_new_with_size (min_width,
198 min_height,
199 COGL_TEXTURE_NO_SLICING,
200 COGL_PIXEL_FORMAT_RGBA_8888_PRE);
201 }
202
203 static void
204 st_scroll_view_fade_paint_target (ClutterOffscreenEffect *effect)
205 {
206 StScrollViewFade *self = ST_SCROLL_VIEW_FADE (effect);
207 ClutterOffscreenEffectClass *parent;
208 CoglHandle material;
209
210 gdouble value, lower, upper, page_size;
211 ClutterActor *vscroll = st_scroll_view_get_vscroll_bar (ST_SCROLL_VIEW (self->actor));
212 ClutterActor *hscroll = st_scroll_view_get_hscroll_bar (ST_SCROLL_VIEW (self->actor));
213 gboolean h_scroll_visible, v_scroll_visible;
214
215 ClutterActorBox allocation, content_box, paint_box;
216
217 /*
218 * Used to pass the fade area to the shader
219 *
220 * [0][0] = x1
221 * [0][1] = y1
222 * [1][0] = x2
223 * [1][1] = y2
224 *
225 */
226 float fade_area[2][2];
227 ClutterVertex verts[4];
228
229 if (self->program == COGL_INVALID_HANDLE)
230 goto out;
231
232 clutter_actor_get_paint_box (self->actor, &paint_box);
233 clutter_actor_get_abs_allocation_vertices (self->actor, verts);
234
235 clutter_actor_get_allocation_box (self->actor, &allocation);
236 st_theme_node_get_content_box (st_widget_get_theme_node (ST_WIDGET (self->actor)),
237 (const ClutterActorBox *)&allocation, &content_box);
238
239 /*
240 * The FBO is based on the paint_volume's size which can be larger then the actual
241 * allocation, so we have to account for that when passing the positions
242 */
243 fade_area[0][0] = content_box.x1 + (verts[0].x - paint_box.x1);
244 fade_area[0][1] = content_box.y1 + (verts[0].y - paint_box.y1);
245 fade_area[1][0] = content_box.x2 + (verts[3].x - paint_box.x2);
246 fade_area[1][1] = content_box.y2 + (verts[3].y - paint_box.y2);
247
248 g_object_get (ST_SCROLL_VIEW (self->actor),
249 "hscrollbar-visible", &h_scroll_visible,
250 "vscrollbar-visible", &v_scroll_visible,
251 NULL);
252
253 if (v_scroll_visible)
254 {
255 if (clutter_actor_get_text_direction (self->actor) == CLUTTER_TEXT_DIRECTION_RTL)
256 fade_area[0][0] += clutter_actor_get_width (vscroll);
257
258 fade_area[1][0] -= clutter_actor_get_width (vscroll);
259 }
260
261 if (h_scroll_visible)
262 fade_area[1][1] -= clutter_actor_get_height (hscroll);
263
264 st_adjustment_get_values (self->vadjustment, &value, &lower, &upper, NULL, NULL, &page_size);
265
266 if (self->offset_top_uniform > -1) {
267 if (value > lower + 0.1)
268 cogl_program_set_uniform_1f (self->program, self->offset_top_uniform, self->vfade_offset);
269 else
270 cogl_program_set_uniform_1f (self->program, self->offset_top_uniform, 0.0f);
271 }
272
273 if (self->offset_bottom_uniform > -1) {
274 if (value < upper - page_size - 0.1)
275 cogl_program_set_uniform_1f (self->program, self->offset_bottom_uniform, self->vfade_offset);
276 else
277 cogl_program_set_uniform_1f (self->program, self->offset_bottom_uniform, 0.0f);
278 }
279
280 st_adjustment_get_values (self->hadjustment, &value, &lower, &upper, NULL, NULL, &page_size);
281
282 if (self->offset_left_uniform > -1) {
283 if (value > lower + 0.1)
284 cogl_program_set_uniform_1f (self->program, self->offset_left_uniform, self->hfade_offset);
285 else
286 cogl_program_set_uniform_1f (self->program, self->offset_left_uniform, 0.0f);
287 }
288
289 if (self->offset_right_uniform > -1) {
290 if (value < upper - page_size - 0.1)
291 cogl_program_set_uniform_1f (self->program, self->offset_right_uniform, self->hfade_offset);
292 else
293 cogl_program_set_uniform_1f (self->program, self->offset_right_uniform, 0.0f);
294 }
295
296 if (self->tex_uniform > -1)
297 cogl_program_set_uniform_1i (self->program, self->tex_uniform, 0);
298 if (self->height_uniform > -1)
299 cogl_program_set_uniform_1f (self->program, self->height_uniform, clutter_actor_get_height (self->actor));
300 if (self->width_uniform > -1)
301 cogl_program_set_uniform_1f (self->program, self->width_uniform, clutter_actor_get_width (self->actor));
302 if (self->fade_area_uniform > -1)
303 cogl_program_set_uniform_matrix (self->program, self->fade_area_uniform, 2, 1, FALSE, (const float *)fade_area);
304
305 material = clutter_offscreen_effect_get_target (effect);
306 cogl_material_set_user_program (material, self->program);
307
308 out:
309 parent = CLUTTER_OFFSCREEN_EFFECT_CLASS (st_scroll_view_fade_parent_class);
310 parent->paint_target (effect);
311 }
312
313 static void
314 on_adjustment_changed (StAdjustment *adjustment,
315 ClutterEffect *effect)
316 {
317 gdouble value, lower, upper, page_size;
318 gboolean needs_fade;
319 StScrollViewFade *self = ST_SCROLL_VIEW_FADE (effect);
320
321 st_adjustment_get_values (self->vadjustment, &value, &lower, &upper, NULL, NULL, &page_size);
322 needs_fade = (value > lower + 0.1) || (value < upper - page_size - 0.1);
323
324 if (!needs_fade)
325 {
326 st_adjustment_get_values (self->hadjustment, &value, &lower, &upper, NULL, NULL, &page_size);
327 needs_fade = (value > lower + 0.1) || (value < upper - page_size - 0.1);
328 }
329
330 clutter_actor_meta_set_enabled (CLUTTER_ACTOR_META (effect), needs_fade);
331 }
332
333 static void
334 st_scroll_view_fade_set_actor (ClutterActorMeta *meta,
335 ClutterActor *actor)
336 {
337 StScrollViewFade *self = ST_SCROLL_VIEW_FADE (meta);
338 ClutterActorMetaClass *parent;
339
340 g_return_if_fail (actor == NULL || ST_IS_SCROLL_VIEW (actor));
341
342 if (self->shader == COGL_INVALID_HANDLE)
343 {
344 clutter_actor_meta_set_enabled (meta, FALSE);
345 return;
346 }
347
348 if (self->vadjustment)
349 {
350 g_signal_handlers_disconnect_by_func (self->vadjustment,
351 (gpointer)on_adjustment_changed,
352 self);
353 self->vadjustment = NULL;
354 }
355
356 if (self->hadjustment)
357 {
358 g_signal_handlers_disconnect_by_func (self->hadjustment,
359 (gpointer)on_adjustment_changed,
360 self);
361 self->hadjustment = NULL;
362 }
363
364
365 if (actor)
366 {
367 StScrollView *scroll_view = ST_SCROLL_VIEW (actor);
368 StScrollBar *vscroll = ST_SCROLL_BAR (st_scroll_view_get_vscroll_bar (scroll_view));
369 StScrollBar *hscroll = ST_SCROLL_BAR (st_scroll_view_get_hscroll_bar (scroll_view));
370 self->vadjustment = ST_ADJUSTMENT (st_scroll_bar_get_adjustment (vscroll));
371 self->hadjustment = ST_ADJUSTMENT (st_scroll_bar_get_adjustment (hscroll));
372
373 g_signal_connect (self->vadjustment, "changed",
374 G_CALLBACK (on_adjustment_changed),
375 self);
376
377 g_signal_connect (self->hadjustment, "changed",
378 G_CALLBACK (on_adjustment_changed),
379 self);
380
381 on_adjustment_changed (NULL, CLUTTER_EFFECT (self));
382 }
383
384 parent = CLUTTER_ACTOR_META_CLASS (st_scroll_view_fade_parent_class);
385 parent->set_actor (meta, actor);
386
387 /* we keep a back pointer here, to avoid going through the ActorMeta */
388 self->actor = clutter_actor_meta_get_actor (meta);
389 }
390
391 static void
392 st_scroll_view_fade_dispose (GObject *gobject)
393 {
394 StScrollViewFade *self = ST_SCROLL_VIEW_FADE (gobject);
395
396 if (self->program != COGL_INVALID_HANDLE)
397 {
398 cogl_handle_unref (self->program);
399
400 self->program = COGL_INVALID_HANDLE;
401 self->shader = COGL_INVALID_HANDLE;
402 }
403
404 if (self->vadjustment)
405 {
406 g_signal_handlers_disconnect_by_func (self->vadjustment,
407 (gpointer)on_adjustment_changed,
408 self);
409 self->vadjustment = NULL;
410 }
411
412 if (self->hadjustment)
413 {
414 g_signal_handlers_disconnect_by_func (self->hadjustment,
415 (gpointer)on_adjustment_changed,
416 self);
417 self->hadjustment = NULL;
418 }
419
420 self->actor = NULL;
421
422 G_OBJECT_CLASS (st_scroll_view_fade_parent_class)->dispose (gobject);
423 }
424
425 static void
426 st_scroll_view_vfade_set_offset (StScrollViewFade *self,
427 float fade_offset)
428 {
429 if (self->vfade_offset == fade_offset)
430 return;
431
432 g_object_freeze_notify (G_OBJECT (self));
433
434 self->vfade_offset = fade_offset;
435
436 if (self->actor != NULL)
437 clutter_actor_queue_redraw (self->actor);
438
439 g_object_notify (G_OBJECT (self), "vfade-offset");
440 g_object_thaw_notify (G_OBJECT (self));
441 }
442
443 static void
444 st_scroll_view_hfade_set_offset (StScrollViewFade *self,
445 float fade_offset)
446 {
447 if (self->hfade_offset == fade_offset)
448 return;
449
450 g_object_freeze_notify (G_OBJECT (self));
451
452 self->hfade_offset = fade_offset;
453
454 if (self->actor != NULL)
455 clutter_actor_queue_redraw (self->actor);
456
457 g_object_notify (G_OBJECT (self), "hfade-offset");
458 g_object_thaw_notify (G_OBJECT (self));
459 }
460
461 static void
462 st_scroll_view_fade_set_property (GObject *object,
463 guint prop_id,
464 const GValue *value,
465 GParamSpec *pspec)
466 {
467 StScrollViewFade *self = ST_SCROLL_VIEW_FADE (object);
468
469 switch (prop_id)
470 {
471 case PROP_VFADE_OFFSET:
472 st_scroll_view_vfade_set_offset (self, g_value_get_float (value));
473 break;
474 case PROP_HFADE_OFFSET:
475 st_scroll_view_hfade_set_offset (self, g_value_get_float (value));
476 break;
477 default:
478 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
479 break;
480 }
481 }
482
483 static void
484 st_scroll_view_fade_get_property (GObject *object,
485 guint prop_id,
486 GValue *value,
487 GParamSpec *pspec)
488 {
489 StScrollViewFade *self = ST_SCROLL_VIEW_FADE (object);
490
491 switch (prop_id)
492 {
493 case PROP_HFADE_OFFSET:
494 g_value_set_float (value, self->vfade_offset);
495 break;
496 case PROP_VFADE_OFFSET:
497 g_value_set_float (value, self->hfade_offset);
498 break;
499 default:
500 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
501 break;
502 }
503 }
504
505 static void
506 st_scroll_view_fade_class_init (StScrollViewFadeClass *klass)
507 {
508 ClutterEffectClass *effect_class = CLUTTER_EFFECT_CLASS (klass);
509 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
510 ClutterOffscreenEffectClass *offscreen_class;
511 ClutterActorMetaClass *meta_class = CLUTTER_ACTOR_META_CLASS (klass);
512
513 gobject_class->dispose = st_scroll_view_fade_dispose;
514 gobject_class->get_property = st_scroll_view_fade_get_property;
515 gobject_class->set_property = st_scroll_view_fade_set_property;
516
517 meta_class->set_actor = st_scroll_view_fade_set_actor;
518
519 effect_class->pre_paint = st_scroll_view_fade_pre_paint;
520
521 offscreen_class = CLUTTER_OFFSCREEN_EFFECT_CLASS (klass);
522 offscreen_class->create_texture = st_scroll_view_fade_create_texture;
523 offscreen_class->paint_target = st_scroll_view_fade_paint_target;
524
525 g_object_class_install_property (gobject_class,
526 PROP_VFADE_OFFSET,
527 g_param_spec_float ("vfade-offset",
528 "Vertical Fade Offset",
529 "The height of the area which is faded at the edge",
530 0.f, G_MAXFLOAT, DEFAULT_FADE_OFFSET,
531 G_PARAM_READWRITE));
532 g_object_class_install_property (gobject_class,
533 PROP_HFADE_OFFSET,
534 g_param_spec_float ("hfade-offset",
535 "Horizontal Fade Offset",
536 "The width of the area which is faded at the edge",
537 0.f, G_MAXFLOAT, DEFAULT_FADE_OFFSET,
538 G_PARAM_READWRITE));
539
540 }
541
542 static void
543 st_scroll_view_fade_init (StScrollViewFade *self)
544 {
545 static CoglHandle shader = COGL_INVALID_HANDLE;
546
547 if (shader == COGL_INVALID_HANDLE)
548 {
549 if (clutter_feature_available (CLUTTER_FEATURE_SHADERS_GLSL))
550 {
551 shader = cogl_create_shader (COGL_SHADER_TYPE_FRAGMENT);
552 cogl_shader_source (shader, fade_glsl_shader);
553 cogl_shader_compile (shader);
554 if (!cogl_shader_is_compiled (shader))
555 {
556 gchar *log_buf = cogl_shader_get_info_log (shader);
557
558 g_warning (G_STRLOC ": Unable to compile the fade shader: %s",
559 log_buf);
560 g_free (log_buf);
561
562 cogl_handle_unref (shader);
563 shader = COGL_INVALID_HANDLE;
564 }
565 }
566 }
567
568 self->shader = shader;
569 self->is_attached = FALSE;
570 self->tex_uniform = -1;
571 self->height_uniform = -1;
572 self->width_uniform = -1;
573 self->fade_area_uniform = -1;
574 self->offset_top_uniform = -1;
575 self->offset_bottom_uniform = -1;
576 self->vfade_offset = DEFAULT_FADE_OFFSET;
577 self->hfade_offset = DEFAULT_FADE_OFFSET;
578
579 if (shader != COGL_INVALID_HANDLE)
580 cogl_handle_ref (self->shader);
581 }
582
583 ClutterEffect *
584 st_scroll_view_fade_new (void)
585 {
586 return g_object_new (ST_TYPE_SCROLL_VIEW_FADE, NULL);
587 }