1/*
2 * Copyright 2011, 2012, 2013 Collabora Limited
3 * Copyright (C) 2012 Intel Corporation. All rights reserved.
4 *
5 * This program is free software; you can redistribute it and/or modify it
6 * under the terms and conditions of the GNU Lesser General Public License,
7 * version 2.1, as published by the Free Software Foundation.
8 *
9 * This program is distributed in the hope it will be useful, but WITHOUT ANY
10 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
11 * FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
12 * more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public License
15 * along with this program; if not, write to the Free Software Foundation,
16 * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
17 *
18 */
19
20#include "config.h"
21
22#if USE(ACCELERATED_COMPOSITING)
23
24#include "GraphicsLayerActor.h"
25
26#include "GraphicsContext.h"
27#include "GraphicsLayerClutter.h"
28#include "PlatformClutterLayerClient.h"
29#include "PlatformContextCairo.h"
30#include "RefPtrCairo.h"
31#include "TransformationMatrix.h"
32#include <algorithm>
33#include <wtf/gobject/GOwnPtr.h>
34#include <wtf/text/CString.h>
35
36using namespace WebCore;
37
38G_DEFINE_TYPE(GraphicsLayerActor, graphics_layer_actor, CLUTTER_TYPE_ACTOR)
39
40#define GRAPHICS_LAYER_ACTOR_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), GRAPHICS_LAYER_TYPE_ACTOR, GraphicsLayerActorPrivate))
41
42struct _GraphicsLayerActorPrivate {
43    PlatformClutterLayerClient* layerClient;
44    RefPtr<cairo_surface_t> surface;
45
46    GraphicsLayerClutter::LayerType layerType;
47
48    bool flatten;
49    bool drawsContent;
50
51    float scrollX;
52    float scrollY;
53
54    float translateX;
55    float translateY;
56};
57
58enum {
59    Property0,
60
61    PropertyTranslateX,
62    PropertyTranslateY,
63
64    PropertyLast
65};
66
67static void graphicsLayerActorAllocate(ClutterActor*, const ClutterActorBox*, ClutterAllocationFlags);
68static void graphicsLayerActorApplyTransform(ClutterActor*, CoglMatrix*);
69static void graphicsLayerActorDispose(GObject*);
70static void graphicsLayerActorGetProperty(GObject*, guint propID, GValue*, GParamSpec*);
71static void graphicsLayerActorSetProperty(GObject*, guint propID, const GValue*, GParamSpec*);
72static void graphicsLayerActorPaint(ClutterActor*);
73
74static gboolean graphicsLayerActorDraw(ClutterCanvas*, cairo_t*, gint width, gint height, GraphicsLayerActor*);
75static void graphicsLayerActorUpdateTexture(GraphicsLayerActor*);
76static void drawLayerContents(ClutterActor*, GraphicsContext&);
77
78static void graphics_layer_actor_class_init(GraphicsLayerActorClass* klass)
79{
80    GObjectClass* objectClass = G_OBJECT_CLASS(klass);
81    ClutterActorClass* actorClass = CLUTTER_ACTOR_CLASS(klass);
82
83    objectClass->get_property = graphicsLayerActorGetProperty;
84    objectClass->set_property = graphicsLayerActorSetProperty;
85    objectClass->dispose = graphicsLayerActorDispose;
86
87    actorClass->allocate = graphicsLayerActorAllocate;
88    actorClass->apply_transform = graphicsLayerActorApplyTransform;
89    actorClass->paint = graphicsLayerActorPaint;
90
91    g_type_class_add_private(klass, sizeof(GraphicsLayerActorPrivate));
92
93    GParamSpec* pspec = g_param_spec_float("translate-x", "Translate X", "Translation value for the X axis", -G_MAXFLOAT, G_MAXFLOAT, 0.0, static_cast<GParamFlags>(G_PARAM_READWRITE));
94    g_object_class_install_property(objectClass, PropertyTranslateX, pspec);
95
96    pspec = g_param_spec_float("translate-y", "Translate Y", "Translation value for the Y ayis", -G_MAXFLOAT, G_MAXFLOAT, 0.0, static_cast<GParamFlags>(G_PARAM_READWRITE));
97    g_object_class_install_property(objectClass, PropertyTranslateY, pspec);
98}
99
100static void graphics_layer_actor_init(GraphicsLayerActor* self)
101{
102    self->priv = GRAPHICS_LAYER_ACTOR_GET_PRIVATE(self);
103
104    clutter_actor_set_reactive(CLUTTER_ACTOR(self), FALSE);
105
106    self->priv->flatten = true;
107
108    // Default used by GraphicsLayer.
109    graphicsLayerActorSetAnchorPoint(self, 0.5, 0.5, 0.0);
110}
111
112static void graphicsLayerActorSetProperty(GObject* object, guint propID, const GValue* value, GParamSpec* pspec)
113{
114    GraphicsLayerActor* layer = GRAPHICS_LAYER_ACTOR(object);
115
116    switch (propID) {
117    case PropertyTranslateX:
118        graphicsLayerActorSetTranslateX(layer, g_value_get_float(value));
119        break;
120    case PropertyTranslateY:
121        graphicsLayerActorSetTranslateY(layer, g_value_get_float(value));
122        break;
123    default:
124        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, propID, pspec);
125    }
126}
127
128static void graphicsLayerActorGetProperty(GObject* object, guint propID, GValue* value, GParamSpec* pspec)
129{
130    GraphicsLayerActor* layer = GRAPHICS_LAYER_ACTOR(object);
131
132    switch (propID) {
133    case PropertyTranslateX:
134        g_value_set_float(value, graphicsLayerActorGetTranslateX(layer));
135        break;
136    case PropertyTranslateY:
137        g_value_set_float(value, graphicsLayerActorGetTranslateY(layer));
138        break;
139    default:
140        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, propID, pspec);
141    }
142}
143
144
145static void graphicsLayerActorDispose(GObject* object)
146{
147    GraphicsLayerActor* layer = GRAPHICS_LAYER_ACTOR(object);
148    GraphicsLayerActorPrivate* priv = layer->priv;
149
150    priv->surface.clear();
151
152    G_OBJECT_CLASS(graphics_layer_actor_parent_class)->dispose(object);
153}
154
155// Copied from cairo.
156#define MAX_IMAGE_SIZE 32767
157
158static void graphicsLayerActorAllocate(ClutterActor* self, const ClutterActorBox* box, ClutterAllocationFlags flags)
159{
160    CLUTTER_ACTOR_CLASS(graphics_layer_actor_parent_class)->allocate(self, box, flags);
161
162    ClutterContent* canvas = clutter_actor_get_content(self);
163    if (canvas)
164        clutter_canvas_set_size(CLUTTER_CANVAS(canvas), clutter_actor_get_width(self), clutter_actor_get_height(self));
165
166    // FIXME: maybe we can cache children allocation and not call
167    // allocate on them this often?
168    GOwnPtr<GList> children(clutter_actor_get_children(self));
169    for (GList* child = children.get(); child; child = child->next) {
170        ClutterActor* childActor = CLUTTER_ACTOR(child->data);
171
172        float childWidth = clutter_actor_get_width(childActor);
173        float childHeight = clutter_actor_get_height(childActor);
174
175        ClutterActorBox childBox;
176        childBox.x1 = clutter_actor_get_x(childActor);
177        childBox.y1 = clutter_actor_get_y(childActor);
178        childBox.x2 = childBox.x1 + childWidth;
179        childBox.y2 = childBox.y1 + childHeight;
180
181        clutter_actor_allocate(childActor, &childBox, flags);
182    }
183}
184
185static void graphicsLayerActorApplyTransform(ClutterActor* actor, CoglMatrix* matrix)
186{
187    GraphicsLayerActorPrivate* priv = GRAPHICS_LAYER_ACTOR(actor)->priv;
188
189    // Apply translation and scrolling as a single translation. These
190    // need to come before anything else, otherwise they'll be
191    // affected by other operations such as scaling, which is not what
192    // we want.
193    float translateX = priv->scrollX + priv->translateX;
194    float translateY = priv->scrollY + priv->translateY;
195
196    if (translateX || translateY)
197        cogl_matrix_translate(matrix, translateX, translateY, 0);
198
199    CoglMatrix modelViewTransform = TransformationMatrix();
200    CLUTTER_ACTOR_CLASS(graphics_layer_actor_parent_class)->apply_transform(actor, &modelViewTransform);
201
202    if (priv->flatten)
203        modelViewTransform = TransformationMatrix(&modelViewTransform).to2dTransform();
204    cogl_matrix_multiply(matrix, matrix, &modelViewTransform);
205}
206
207static void graphicsLayerActorPaint(ClutterActor* actor)
208{
209    GOwnPtr<GList> children(clutter_actor_get_children(actor));
210    for (GList* child = children.get(); child; child = child->next)
211        clutter_actor_paint(CLUTTER_ACTOR(child->data));
212}
213
214static gboolean graphicsLayerActorDraw(ClutterCanvas* texture, cairo_t* cr, gint width, gint height, GraphicsLayerActor* layer)
215{
216    if (!width || !height)
217        return FALSE;
218
219    GraphicsContext context(cr);
220    context.clearRect(FloatRect(0, 0, width, height));
221
222    GraphicsLayerActorPrivate* priv = layer->priv;
223    if (priv->surface) {
224        gint surfaceWidth = cairo_image_surface_get_width(priv->surface.get());
225        gint surfaceHeight = cairo_image_surface_get_height(priv->surface.get());
226
227        FloatRect srcRect(0.0, 0.0, static_cast<float>(surfaceWidth), static_cast<float>(surfaceHeight));
228        FloatRect destRect(0.0, 0.0, width, height);
229        context.platformContext()->drawSurfaceToContext(priv->surface.get(), destRect, srcRect, &context);
230    }
231
232    if (priv->layerType == GraphicsLayerClutter::LayerTypeWebLayer)
233        drawLayerContents(CLUTTER_ACTOR(layer), context);
234
235    return TRUE;
236}
237
238static void graphicsLayerActorUpdateTexture(GraphicsLayerActor* layer)
239{
240    GraphicsLayerActorPrivate* priv = layer->priv;
241    ASSERT(priv->layerType != GraphicsLayerClutter::LayerTypeVideoLayer);
242
243    ClutterActor* actor = CLUTTER_ACTOR(layer);
244    GRefPtr<ClutterContent> canvas = clutter_actor_get_content(actor);
245    if (canvas) {
246        // Nothing needs a texture, remove the one we have, if any.
247        if (!priv->drawsContent && !priv->surface) {
248            g_signal_handlers_disconnect_by_func(canvas.get(), reinterpret_cast<void*>(graphicsLayerActorDraw), layer);
249            clutter_actor_set_content(actor, 0);
250        }
251        return;
252    }
253
254    // We should have a texture, so create one.
255    canvas = adoptGRef(clutter_canvas_new());
256    clutter_actor_set_content(actor, canvas.get());
257
258    int width = std::max(static_cast<int>(ceilf(clutter_actor_get_width(actor))), 1);
259    int height = std::max(static_cast<int>(ceilf(clutter_actor_get_height(actor))), 1);
260    clutter_canvas_set_size(CLUTTER_CANVAS(canvas.get()), width, height);
261
262    g_signal_connect(canvas.get(), "draw", G_CALLBACK(graphicsLayerActorDraw), layer);
263}
264
265// Draw content into the layer.
266static void drawLayerContents(ClutterActor* actor, GraphicsContext& context)
267{
268    GraphicsLayerActorPrivate* priv = GRAPHICS_LAYER_ACTOR(actor)->priv;
269
270    if (!priv->drawsContent || !priv->layerClient)
271        return;
272
273    int width = static_cast<int>(clutter_actor_get_width(actor));
274    int height = static_cast<int>(clutter_actor_get_height(actor));
275    IntRect clip(0, 0, width, height);
276
277    // Apply the painted content to the layer.
278    priv->layerClient->platformClutterLayerPaintContents(context, clip);
279}
280
281static GraphicsLayerActor* graphicsLayerActorNew(GraphicsLayerClutter::LayerType type)
282{
283    GraphicsLayerActor* layer = GRAPHICS_LAYER_ACTOR(g_object_new(GRAPHICS_LAYER_TYPE_ACTOR, 0));
284    GraphicsLayerActorPrivate* priv = layer->priv;
285
286    priv->layerType = type;
287    if (priv->layerType == GraphicsLayerClutter::LayerTypeTransformLayer)
288        priv->flatten = false;
289
290    return layer;
291}
292
293GraphicsLayerActor* graphicsLayerActorNewWithClient(GraphicsLayerClutter::LayerType type, PlatformClutterLayerClient* layerClient)
294{
295    GraphicsLayerActor* layer = graphicsLayerActorNew(type);
296    graphicsLayerActorSetClient(layer, layerClient);
297
298    return layer;
299}
300
301void graphicsLayerActorSetClient(GraphicsLayerActor* layer, PlatformClutterLayerClient* client)
302{
303    layer->priv->layerClient = client;
304}
305
306PlatformClutterLayerClient* graphicsLayerActorGetClient(GraphicsLayerActor* layer)
307{
308    return layer->priv->layerClient;
309}
310
311void graphicsLayerActorSetSurface(GraphicsLayerActor* layer, cairo_surface_t* surface)
312{
313    GraphicsLayerActorPrivate* priv = layer->priv;
314    priv->surface = surface;
315    graphicsLayerActorUpdateTexture(layer);
316}
317
318void graphicsLayerActorInvalidateRectangle(GraphicsLayerActor* layer, const FloatRect& dirtyRect)
319{
320    ClutterContent* canvas = clutter_actor_get_content(CLUTTER_ACTOR(layer));
321    if (!canvas)
322        return;
323
324    // FIXME: Need to invalidate a specific area?
325    clutter_content_invalidate(canvas);
326}
327
328void graphicsLayerActorSetAnchorPoint(GraphicsLayerActor* layer, float x, float y, float z)
329{
330    ClutterActor* actor = CLUTTER_ACTOR(layer);
331    clutter_actor_set_pivot_point(actor, x, y);
332    clutter_actor_set_pivot_point_z(actor, z);
333}
334
335void graphicsLayerActorSetScrollPosition(GraphicsLayerActor* layer, float x, float y)
336{
337    if (x > 0 || y > 0)
338        return;
339
340    GraphicsLayerActorPrivate* priv = layer->priv;
341    priv->scrollX = x;
342    priv->scrollY = y;
343
344    clutter_actor_queue_redraw(CLUTTER_ACTOR(layer));
345}
346
347void graphicsLayerActorSetSublayers(GraphicsLayerActor* layer, GraphicsLayerActorList& subLayers)
348{
349    if (subLayers.isEmpty()) {
350        clutter_actor_remove_all_children(CLUTTER_ACTOR(layer));
351        return;
352    }
353
354    ClutterActor* newParentActor = CLUTTER_ACTOR(layer);
355    for (size_t i = 0; i < subLayers.size(); ++i) {
356        ClutterActor* childActor = CLUTTER_ACTOR(subLayers[i].get());
357        ClutterActor* oldParentActor = clutter_actor_get_parent(childActor);
358        if (oldParentActor) {
359            if (oldParentActor == newParentActor)
360                continue;
361            clutter_actor_remove_child(oldParentActor, childActor);
362        }
363        clutter_actor_add_child(newParentActor, childActor);
364    }
365}
366
367void graphicsLayerActorRemoveFromSuperLayer(GraphicsLayerActor* layer)
368{
369    ClutterActor* actor = CLUTTER_ACTOR(layer);
370    ClutterActor* parentActor = clutter_actor_get_parent(actor);
371    if (!parentActor)
372        return;
373
374    clutter_actor_remove_child(parentActor, actor);
375}
376
377GraphicsLayerClutter::LayerType graphicsLayerActorGetLayerType(GraphicsLayerActor* layer)
378{
379    GraphicsLayerActorPrivate* priv = layer->priv;
380    return priv->layerType;
381}
382
383void graphicsLayerActorSetLayerType(GraphicsLayerActor* layer, GraphicsLayerClutter::LayerType layerType)
384{
385    GraphicsLayerActorPrivate* priv = layer->priv;
386    priv->layerType = layerType;
387}
388
389void graphicsLayerActorSetTranslateX(GraphicsLayerActor* layer, float value)
390{
391    GraphicsLayerActorPrivate* priv = layer->priv;
392    priv->translateX = value;
393    clutter_actor_queue_redraw(CLUTTER_ACTOR(layer));
394}
395
396float graphicsLayerActorGetTranslateX(GraphicsLayerActor* layer)
397{
398    GraphicsLayerActorPrivate* priv = layer->priv;
399    return priv->translateX;
400}
401
402void graphicsLayerActorSetTranslateY(GraphicsLayerActor* layer, float value)
403{
404    GraphicsLayerActorPrivate* priv = layer->priv;
405    priv->translateY = value;
406    clutter_actor_queue_redraw(CLUTTER_ACTOR(layer));
407}
408
409float graphicsLayerActorGetTranslateY(GraphicsLayerActor* layer)
410{
411    GraphicsLayerActorPrivate* priv = layer->priv;
412    return priv->translateY;
413}
414
415void graphicsLayerActorSetDrawsContent(GraphicsLayerActor* layer, bool drawsContent)
416{
417    GraphicsLayerActorPrivate* priv = layer->priv;
418
419    if (drawsContent == priv->drawsContent)
420        return;
421
422    priv->drawsContent = drawsContent;
423
424    graphicsLayerActorUpdateTexture(layer);
425}
426
427void graphicsLayerActorSetFlatten(GraphicsLayerActor* layer, bool flatten)
428{
429    GraphicsLayerActorPrivate* priv = layer->priv;
430    if (flatten == priv->flatten)
431        return;
432
433    priv->flatten = flatten;
434}
435
436void graphicsLayerActorSetMasksToBounds(GraphicsLayerActor* layer, bool masksToBounds)
437{
438    ClutterActor* actor = CLUTTER_ACTOR(layer);
439    if (masksToBounds)
440        clutter_actor_set_clip(actor, 0, 0, clutter_actor_get_width(actor), clutter_actor_get_height(actor));
441    else
442        clutter_actor_remove_clip(actor);
443}
444
445WebCore::PlatformClutterAnimation* graphicsLayerActorGetAnimationForKey(GraphicsLayerActor* layer, const String key)
446{
447    return static_cast<WebCore::PlatformClutterAnimation*>(g_object_get_data(G_OBJECT(layer), key.utf8().data()));
448}
449
450#endif // USE(ACCELERATED_COMPOSITING)
451