/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
/*
* st-drawing-area.c: A dynamically-sized Cairo drawing area
*
* Copyright 2009, 2010 Red Hat, Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation, either version 2.1 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
* more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see .
*/
/**
* SECTION:st-drawing-area
* @short_description: A dynamically-sized Cairo drawing area
*
* #StDrawingArea is similar to #ClutterCairoTexture in that
* it allows drawing via Cairo; the primary difference is that
* it is dynamically sized. To use, connect to the #StDrawingArea::repaint
* signal, and inside the signal handler, call
* st_drawing_area_get_context() to get the Cairo context to draw to. The
* #StDrawingArea::repaint signal will be emitted by default when the area is
* resized or the CSS style changes; you can use the
* st_drawing_area_queue_repaint() as well.
*/
#include "st-drawing-area.h"
#include
#include
typedef struct _StDrawingAreaPrivate StDrawingAreaPrivate;
struct _StDrawingAreaPrivate {
cairo_t *context;
guint in_repaint : 1;
};
G_DEFINE_TYPE_WITH_PRIVATE (StDrawingArea, st_drawing_area, ST_TYPE_WIDGET);
/* Signals */
enum
{
REPAINT,
LAST_SIGNAL
};
static guint st_drawing_area_signals [LAST_SIGNAL] = { 0 };
static gboolean
draw_content (ClutterCanvas *canvas,
cairo_t *cr,
int width,
int height,
gpointer user_data)
{
StDrawingArea *area = ST_DRAWING_AREA (user_data);
StDrawingAreaPrivate *priv = st_drawing_area_get_instance_private (area);
priv->context = cr;
priv->in_repaint = TRUE;
clutter_cairo_clear (cr);
g_signal_emit (area, st_drawing_area_signals[REPAINT], 0);
priv->context = NULL;
priv->in_repaint = FALSE;
return TRUE;
}
static void
st_drawing_area_allocate (ClutterActor *self,
const ClutterActorBox *box,
ClutterAllocationFlags flags)
{
StThemeNode *theme_node = st_widget_get_theme_node (ST_WIDGET (self));
ClutterContent *content = clutter_actor_get_content (self);
ClutterActorBox content_box;
int width, height;
float resource_scale;
if (!st_widget_get_resource_scale (ST_WIDGET (self), &resource_scale))
{
ClutterActorBox empty = CLUTTER_ACTOR_BOX_INIT_ZERO;
clutter_actor_set_allocation (self, &empty, 0);
return;
}
clutter_actor_set_allocation (self, box, flags);
st_theme_node_get_content_box (theme_node, box, &content_box);
width = (int)(0.5 + content_box.x2 - content_box.x1);
height = (int)(0.5 + content_box.y2 - content_box.y1);
clutter_canvas_set_scale_factor (CLUTTER_CANVAS (content), resource_scale);
clutter_canvas_set_size (CLUTTER_CANVAS (content), width, height);
}
static void
st_drawing_area_style_changed (StWidget *self)
{
(ST_WIDGET_CLASS (st_drawing_area_parent_class))->style_changed (self);
st_drawing_area_queue_repaint (ST_DRAWING_AREA (self));
}
static void
st_drawing_area_resource_scale_changed (StWidget *self)
{
float resource_scale;
ClutterContent *content = clutter_actor_get_content (CLUTTER_ACTOR (self));
if (st_widget_get_resource_scale (ST_WIDGET (self), &resource_scale))
clutter_canvas_set_scale_factor (CLUTTER_CANVAS (content), resource_scale);
}
static void
st_drawing_area_class_init (StDrawingAreaClass *klass)
{
ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass);
StWidgetClass *widget_class = ST_WIDGET_CLASS (klass);
actor_class->allocate = st_drawing_area_allocate;
widget_class->style_changed = st_drawing_area_style_changed;
widget_class->resource_scale_changed = st_drawing_area_resource_scale_changed;
st_drawing_area_signals[REPAINT] =
g_signal_new ("repaint",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (StDrawingAreaClass, repaint),
NULL, NULL, NULL,
G_TYPE_NONE, 0);
}
static void
st_drawing_area_init (StDrawingArea *area)
{
ClutterContent *content = clutter_canvas_new ();
g_signal_connect (content, "draw", G_CALLBACK (draw_content), area);
clutter_actor_set_content (CLUTTER_ACTOR (area), content);
g_object_unref (content);
}
/**
* st_drawing_area_queue_repaint:
* @area: the #StDrawingArea
*
* Will cause the actor to emit a ::repaint signal before it is next
* drawn to the scene. Useful if some parameters for the area being
* drawn other than the size or style have changed. Note that
* clutter_actor_queue_redraw() will simply result in the same
* contents being drawn to the scene again.
*/
void
st_drawing_area_queue_repaint (StDrawingArea *area)
{
g_return_if_fail (ST_IS_DRAWING_AREA (area));
clutter_content_invalidate (clutter_actor_get_content (CLUTTER_ACTOR (area)));
}
/**
* st_drawing_area_get_context:
* @area: the #StDrawingArea
*
* Gets the Cairo context to paint to. This function must only be called
* from a signal hander for the ::repaint signal.
*
* Return Value: (transfer none): the Cairo context for the paint operation
*/
cairo_t *
st_drawing_area_get_context (StDrawingArea *area)
{
StDrawingAreaPrivate *priv;
g_return_val_if_fail (ST_IS_DRAWING_AREA (area), NULL);
priv = st_drawing_area_get_instance_private (area);
g_return_val_if_fail (priv->in_repaint, NULL);
return priv->context;
}
/**
* st_drawing_area_get_surface_size:
* @area: the #StDrawingArea
* @width: (out): location to store the width of the painted area
* @height: (out): location to store the height of the painted area
*
* Gets the size of the cairo surface being painted to, which is equal
* to the size of the content area of the widget. This function must
* only be called from a signal hander for the ::repaint signal.
*/
void
st_drawing_area_get_surface_size (StDrawingArea *area,
guint *width,
guint *height)
{
StDrawingAreaPrivate *priv;
ClutterContent *content;
float w, h, resource_scale;
g_return_if_fail (ST_IS_DRAWING_AREA (area));
priv = st_drawing_area_get_instance_private (area);
g_return_if_fail (priv->in_repaint);
content = clutter_actor_get_content (CLUTTER_ACTOR (area));
clutter_content_get_preferred_size (content, &w, &h);
if (st_widget_get_resource_scale (ST_WIDGET (area), &resource_scale))
{
w /= resource_scale;
h /= resource_scale;
}
else
{
w = h = 0.0f;
}
if (width)
*width = ceilf (w);
if (height)
*height = ceilf (h);
}