Add experimental GtkDrawingArea based UI.

This commit is contained in:
Daiki Ueno
2010-06-15 19:17:05 +09:00
parent 231186ae71
commit 63b9fea27b
17 changed files with 1429 additions and 303 deletions

View File

@ -26,13 +26,15 @@
#include <cogl/cogl.h>
#include <cogl/cogl-pango.h>
#include <math.h>
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif /* HAVE_CONFIG_H */
#include "eek-clutter-key-actor.h"
#include "eek-keysym.h"
#include "eek-drawing.h"
#include "eek-section.h"
#include "eek-keyboard.h"
#define noKBDRAW_DEBUG
@ -54,6 +56,7 @@ struct _EekClutterKeyActorPrivate
{
EekKey *key;
ClutterActor *texture;
PangoFontDescription *font;
};
static struct {
@ -61,20 +64,24 @@ static struct {
GHashTable *outline_textures;
/* manually maintain the ref-count of outline_textures to set it
to NULL on destroy */
to NULL when all the EekClutterKeyActor are destroyed */
gint outline_textures_ref_count;
} texture_cache;
static struct {
/* keysym category -> PangoFontDescription * */
PangoFontDescription *category_fonts[EEK_KEYSYM_CATEGORY_LAST];
/* manually maintain the ref-count of category_fonts to set it to
NULL when all the EekClutterKeyActor are destroyed */
gint category_fonts_ref_count;
} font_cache;
static ClutterActor *get_texture (EekClutterKeyActor *actor);
static void draw_key_on_layout (EekKey *key,
static void draw_key_on_layout (EekClutterKeyActor *actor,
PangoLayout *layout);
static void key_enlarge (ClutterActor *actor);
static void key_shrink (ClutterActor *actor);
static void draw_rounded_polygon (cairo_t *cr,
gboolean filled,
gdouble radius,
EekPoint *points,
gint num_points);
static void
eek_clutter_key_actor_real_paint (ClutterActor *self)
@ -104,7 +111,7 @@ eek_clutter_key_actor_real_paint (ClutterActor *self)
/* Draw the label on the key. */
layout = clutter_actor_create_pango_layout (self, NULL);
draw_key_on_layout (priv->key, layout);
draw_key_on_layout (EEK_CLUTTER_KEY_ACTOR(self), layout);
pango_layout_get_extents (layout, NULL, &logical_rect);
/* FIXME: Color should be configurable through a property. */
@ -129,12 +136,11 @@ eek_clutter_key_actor_real_get_preferred_width (ClutterActor *self,
gfloat *min_width_p,
gfloat *natural_width_p)
{
EekClutterKeyActorPrivate *priv = EEK_CLUTTER_KEY_ACTOR_GET_PRIVATE (self);
PangoLayout *layout;
/* Draw the label on the key - just to validate the glyph cache. */
layout = clutter_actor_create_pango_layout (self, NULL);
draw_key_on_layout (priv->key, layout);
draw_key_on_layout (EEK_CLUTTER_KEY_ACTOR(self), layout);
cogl_pango_ensure_glyph_cache_for_layout (layout);
g_object_unref (layout);
@ -189,6 +195,18 @@ eek_clutter_key_actor_dispose (GObject *object)
texture_cache.outline_textures = NULL;
}
}
if (priv->font) {
/* no need to pango_font_description_free (priv->font) */
priv->font = NULL;
if (--font_cache.category_fonts_ref_count == 0) {
gint i;
for (i = 0; i < EEK_KEYSYM_CATEGORY_LAST; i++) {
pango_font_description_free (font_cache.category_fonts[i]);
font_cache.category_fonts[i] = NULL;
}
}
}
G_OBJECT_CLASS (eek_clutter_key_actor_parent_class)->dispose (object);
}
@ -268,6 +286,8 @@ eek_clutter_key_actor_init (EekClutterKeyActor *self)
priv = self->priv = EEK_CLUTTER_KEY_ACTOR_GET_PRIVATE(self);
priv->key = NULL;
priv->texture = NULL;
priv->font = NULL;
clutter_actor_set_reactive (CLUTTER_ACTOR(self), TRUE);
g_signal_connect (self, "button-press-event",
G_CALLBACK (on_button_press_event), NULL);
@ -343,20 +363,20 @@ create_texture_for_key (EekKey *key)
cairo_set_source (cr, pat);
draw_rounded_polygon (cr,
TRUE,
outline->corner_radius,
outline->points,
outline->num_points);
eek_draw_rounded_polygon (cr,
TRUE,
outline->corner_radius,
outline->points,
outline->num_points);
cairo_pattern_destroy (pat);
cairo_set_source_rgba (cr, 0.3, 0.3, 0.3, 0.5);
draw_rounded_polygon (cr,
FALSE,
outline->corner_radius,
outline->points,
outline->num_points);
eek_draw_rounded_polygon (cr,
FALSE,
outline->corner_radius,
outline->points,
outline->num_points);
cairo_destroy (cr);
return texture;
}
@ -382,272 +402,57 @@ get_texture (EekClutterKeyActor *actor)
}
static void
draw_text_on_layout (PangoLayout *layout,
const gchar *text,
gdouble scale)
{
PangoFontDescription *font_desc;
#define FONT_SIZE (720 * 50)
/* FIXME: Font should be configurable through a property. */
font_desc = pango_font_description_from_string ("Sans");
pango_font_description_set_size (font_desc, FONT_SIZE * scale);
pango_layout_set_font_description (layout, font_desc);
pango_layout_set_alignment (layout, PANGO_ALIGN_CENTER);
pango_layout_set_text (layout, text, -1);
pango_font_description_free (font_desc);
}
static void
draw_key_on_layout (EekKey *key,
draw_key_on_layout (EekClutterKeyActor *self,
PangoLayout *layout)
{
PangoLayout *buffer;
PangoRectangle logical_rect = { 0, };
EekBounds bounds;
EekClutterKeyActorPrivate *priv = EEK_CLUTTER_KEY_ACTOR_GET_PRIVATE (self);
guint keysym;
const gchar *label, *empty_label = "";
gdouble scale_x, scale_y;
EekKeysymCategory category;
EekBounds bounds;
eek_element_get_bounds (EEK_ELEMENT(key), &bounds);
keysym = eek_key_get_keysym (key);
if (font_cache.category_fonts_ref_count == 0) {
EekContainer *keyboard, *section;
PangoFontDescription *font;
PangoLayout *copy;
section = EEK_ELEMENT_GET_CLASS(priv->key)->
get_parent (EEK_ELEMENT(priv->key));
g_return_if_fail (EEK_IS_SECTION(section));
keyboard = EEK_ELEMENT_GET_CLASS(section)->
get_parent (EEK_ELEMENT(section));
g_return_if_fail (EEK_IS_KEYBOARD(keyboard));
copy = pango_layout_copy (layout);
font = pango_font_description_from_string ("Sans");
pango_layout_set_font_description (copy, font);
eek_get_fonts (EEK_KEYBOARD(keyboard), copy,
font_cache.category_fonts);
g_object_unref (G_OBJECT(copy));
}
keysym = eek_key_get_keysym (priv->key);
if (keysym == EEK_INVALID_KEYSYM)
return;
category = eek_keysym_get_category (keysym);
if (category == EEK_KEYSYM_CATEGORY_UNKNOWN)
return;
if (!priv->font) {
priv->font = font_cache.category_fonts[category];
font_cache.category_fonts_ref_count++;
}
pango_layout_set_font_description (layout, priv->font);
eek_element_get_bounds (EEK_ELEMENT(priv->key), &bounds);
pango_layout_set_width (layout, PANGO_SCALE * bounds.width);
pango_layout_set_ellipsize (layout, PANGO_ELLIPSIZE_END);
label = eek_keysym_to_string (keysym);
if (!label)
label = empty_label;
/* Compute the layout extents. */
buffer = pango_layout_copy (layout);
draw_text_on_layout (buffer, label, 1.0);
pango_layout_get_extents (buffer, NULL, &logical_rect);
scale_x = scale_y = 1.0;
if (PANGO_PIXELS(logical_rect.width) > bounds.width)
scale_x = bounds.width / PANGO_PIXELS(logical_rect.width);
if (PANGO_PIXELS(logical_rect.height) > bounds.height)
scale_y = bounds.height / PANGO_PIXELS(logical_rect.height);
g_object_unref (buffer);
/* Actually draw on the layout */
draw_text_on_layout (layout,
label,
(scale_x < scale_y ? scale_x : scale_y) * 0.8);
eek_draw_text_on_layout (layout, label);
if (label != empty_label)
g_free ((gpointer)label);
}
/*
* The functions below are borrowed from
* libgnomekbd/gkbd-keyboard-drawing.c.
* Copyright (C) 2006 Sergey V. Udaltsov <svu@gnome.org>
*
* length(), point_line_distance(), normal_form(), inverse(), multiply(),
* intersect(), rounded_corner(), draw_rounded_polygon()
*/
static gdouble
length (gdouble x, gdouble y)
{
return sqrt (x * x + y * y);
}
static gdouble
point_line_distance (gdouble ax, gdouble ay, gdouble nx, gdouble ny)
{
return ax * nx + ay * ny;
}
static void
normal_form (gdouble ax, gdouble ay,
gdouble bx, gdouble by,
gdouble * nx, gdouble * ny, gdouble * d)
{
gdouble l;
*nx = by - ay;
*ny = ax - bx;
l = length (*nx, *ny);
*nx /= l;
*ny /= l;
*d = point_line_distance (ax, ay, *nx, *ny);
}
static void
inverse (gdouble a, gdouble b, gdouble c, gdouble d,
gdouble * e, gdouble * f, gdouble * g, gdouble * h)
{
gdouble det;
det = a * d - b * c;
*e = d / det;
*f = -b / det;
*g = -c / det;
*h = a / det;
}
static void
multiply (gdouble a, gdouble b, gdouble c, gdouble d,
gdouble e, gdouble f, gdouble * x, gdouble * y)
{
*x = a * e + b * f;
*y = c * e + d * f;
}
static void
intersect (gdouble n1x, gdouble n1y, gdouble d1,
gdouble n2x, gdouble n2y, gdouble d2, gdouble * x, gdouble * y)
{
gdouble e, f, g, h;
inverse (n1x, n1y, n2x, n2y, &e, &f, &g, &h);
multiply (e, f, g, h, d1, d2, x, y);
}
/* draw an angle from the current point to b and then to c,
* with a rounded corner of the given radius.
*/
static void
rounded_corner (cairo_t * cr,
gdouble bx, gdouble by,
gdouble cx, gdouble cy, gdouble radius)
{
gdouble ax, ay;
gdouble n1x, n1y, d1;
gdouble n2x, n2y, d2;
gdouble pd1, pd2;
gdouble ix, iy;
gdouble dist1, dist2;
gdouble nx, ny, d;
gdouble a1x, a1y, c1x, c1y;
gdouble phi1, phi2;
cairo_get_current_point (cr, &ax, &ay);
#ifdef KBDRAW_DEBUG
printf (" current point: (%f, %f), radius %f:\n", ax, ay,
radius);
#endif
/* make sure radius is not too large */
dist1 = length (bx - ax, by - ay);
dist2 = length (cx - bx, cy - by);
radius = MIN (radius, MIN (dist1, dist2));
/* construct normal forms of the lines */
normal_form (ax, ay, bx, by, &n1x, &n1y, &d1);
normal_form (bx, by, cx, cy, &n2x, &n2y, &d2);
/* find which side of the line a,b the point c is on */
if (point_line_distance (cx, cy, n1x, n1y) < d1)
pd1 = d1 - radius;
else
pd1 = d1 + radius;
/* find which side of the line b,c the point a is on */
if (point_line_distance (ax, ay, n2x, n2y) < d2)
pd2 = d2 - radius;
else
pd2 = d2 + radius;
/* intersect the parallels to find the center of the arc */
intersect (n1x, n1y, pd1, n2x, n2y, pd2, &ix, &iy);
nx = (bx - ax) / dist1;
ny = (by - ay) / dist1;
d = point_line_distance (ix, iy, nx, ny);
/* a1 is the point on the line a-b where the arc starts */
intersect (n1x, n1y, d1, nx, ny, d, &a1x, &a1y);
nx = (cx - bx) / dist2;
ny = (cy - by) / dist2;
d = point_line_distance (ix, iy, nx, ny);
/* c1 is the point on the line b-c where the arc ends */
intersect (n2x, n2y, d2, nx, ny, d, &c1x, &c1y);
/* determine the first angle */
if (a1x - ix == 0)
phi1 = (a1y - iy > 0) ? M_PI_2 : 3 * M_PI_2;
else if (a1x - ix > 0)
phi1 = atan ((a1y - iy) / (a1x - ix));
else
phi1 = M_PI + atan ((a1y - iy) / (a1x - ix));
/* determine the second angle */
if (c1x - ix == 0)
phi2 = (c1y - iy > 0) ? M_PI_2 : 3 * M_PI_2;
else if (c1x - ix > 0)
phi2 = atan ((c1y - iy) / (c1x - ix));
else
phi2 = M_PI + atan ((c1y - iy) / (c1x - ix));
/* compute the difference between phi2 and phi1 mod 2pi */
d = phi2 - phi1;
while (d < 0)
d += 2 * M_PI;
while (d > 2 * M_PI)
d -= 2 * M_PI;
#ifdef KBDRAW_DEBUG
printf (" line 1 to: (%f, %f):\n", a1x, a1y);
#endif
if (!(isnan (a1x) || isnan (a1y)))
cairo_line_to (cr, a1x, a1y);
/* pick the short arc from phi1 to phi2 */
if (d < M_PI)
cairo_arc (cr, ix, iy, radius, phi1, phi2);
else
cairo_arc_negative (cr, ix, iy, radius, phi1, phi2);
#ifdef KBDRAW_DEBUG
printf (" line 2 to: (%f, %f):\n", cx, cy);
#endif
cairo_line_to (cr, cx, cy);
}
static void
draw_rounded_polygon (cairo_t *cr,
gboolean filled,
gdouble radius,
EekPoint *points,
gint num_points)
{
gint i, j;
cairo_move_to (cr,
(gdouble) (points[num_points - 1].x +
points[0].x) / 2,
(gdouble) (points[num_points - 1].y +
points[0].y) / 2);
#ifdef KBDRAW_DEBUG
printf (" rounded polygon of radius %f:\n", radius);
#endif
for (i = 0; i < num_points; i++) {
j = (i + 1) % num_points;
rounded_corner (cr, (gdouble) points[i].x,
(gdouble) points[i].y,
(gdouble) (points[i].x + points[j].x) / 2,
(gdouble) (points[i].y + points[j].y) / 2,
radius);
#ifdef KBDRAW_DEBUG
printf (" corner (%d, %d) -> (%d, %d):\n",
points[i].x, points[i].y, points[j].x,
points[j].y);
#endif
};
cairo_close_path (cr);
if (filled)
cairo_fill (cr);
else
cairo_stroke (cr);
}