Re-implement Cairo-based rendering.
This commit is contained in:
@ -1,466 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2006 Sergey V. Udaltsov <svu@gnome.org>
|
||||
* Copyright (C) 2010 Daiki Ueno <ueno@unixuser.org>
|
||||
* Copyright (C) 2010 Red Hat, Inc.
|
||||
*
|
||||
* This library 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 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that 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 library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||
* 02110-1301 USA
|
||||
|
||||
*/
|
||||
|
||||
#include <math.h>
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif /* HAVE_CONFIG_H */
|
||||
|
||||
#include "eek-keyboard.h"
|
||||
#include "eek-key.h"
|
||||
#include "eek-drawing.h"
|
||||
#include "eek-keysym.h"
|
||||
|
||||
|
||||
void
|
||||
eek_draw_text_on_layout (PangoLayout *layout,
|
||||
const gchar *text)
|
||||
{
|
||||
/* pango_layout_set_alignment (layout, PANGO_ALIGN_CENTER); */
|
||||
pango_layout_set_text (layout, text, -1);
|
||||
}
|
||||
|
||||
struct _GetFontSizeCallbackData
|
||||
{
|
||||
PangoLayout *layout;
|
||||
EekKeysymCategory category;
|
||||
gint minimum_font_size;
|
||||
gint font_size;
|
||||
};
|
||||
typedef struct _GetFontSizeCallbackData GetFontSizeCallbackData;
|
||||
|
||||
static gint
|
||||
get_font_size (const gchar *text,
|
||||
EekBounds *bounds,
|
||||
PangoLayout *layout)
|
||||
{
|
||||
gdouble scale_x, scale_y;
|
||||
const PangoFontDescription *base_font_desc;
|
||||
PangoFontDescription *font_desc;
|
||||
PangoRectangle logical_rect = { 0, };
|
||||
gint font_size;
|
||||
|
||||
layout = pango_layout_copy (layout);
|
||||
base_font_desc = pango_layout_get_font_description (layout);
|
||||
font_desc = pango_font_description_copy (base_font_desc);
|
||||
|
||||
font_size = eek_bounds_long_side (bounds) * PANGO_SCALE;
|
||||
pango_font_description_set_size (font_desc, font_size);
|
||||
pango_layout_set_font_description (layout, font_desc);
|
||||
pango_font_description_free (font_desc);
|
||||
|
||||
eek_draw_text_on_layout (layout, text);
|
||||
pango_layout_get_extents (layout, NULL, &logical_rect);
|
||||
|
||||
scale_x = scale_y = 1.0;
|
||||
if (logical_rect.width > bounds->width * PANGO_SCALE)
|
||||
scale_x = bounds->width * PANGO_SCALE / logical_rect.width;
|
||||
if (logical_rect.height > bounds->height * PANGO_SCALE)
|
||||
scale_y = bounds->height * PANGO_SCALE / logical_rect.height;
|
||||
g_object_unref (layout);
|
||||
return font_size * (scale_x < scale_y ? scale_x : scale_y);
|
||||
}
|
||||
|
||||
static void
|
||||
egf_key_callback (EekElement *element,
|
||||
gpointer user_data)
|
||||
{
|
||||
EekKey *key = EEK_KEY(element);
|
||||
GetFontSizeCallbackData *data = user_data;
|
||||
gdouble font_size;
|
||||
guint keysym;
|
||||
EekBounds bounds;
|
||||
gchar *label;
|
||||
|
||||
keysym = eek_key_get_keysym (key);
|
||||
if (keysym == EEK_INVALID_KEYSYM ||
|
||||
eek_keysym_get_category (keysym) != data->category)
|
||||
return;
|
||||
|
||||
eek_element_get_bounds (EEK_ELEMENT(key), &bounds);
|
||||
label = eek_keysym_to_string (keysym);
|
||||
if (!label)
|
||||
return;
|
||||
font_size = get_font_size (label, &bounds, data->layout);
|
||||
if (font_size < data->font_size && font_size >= data->minimum_font_size)
|
||||
data->font_size = font_size;
|
||||
g_free (label);
|
||||
}
|
||||
|
||||
static void
|
||||
egf_section_callback (EekElement *element,
|
||||
gpointer user_data)
|
||||
{
|
||||
eek_container_foreach_child (EEK_CONTAINER(element),
|
||||
egf_key_callback,
|
||||
user_data);
|
||||
}
|
||||
|
||||
static PangoFontDescription *
|
||||
get_font_for_category (EekKeyboard *keyboard,
|
||||
EekKeysymCategory category,
|
||||
PangoLayout *layout,
|
||||
gdouble minimum_font_size,
|
||||
gdouble maximum_font_size)
|
||||
{
|
||||
GetFontSizeCallbackData data;
|
||||
PangoFontDescription *font_desc;
|
||||
const PangoFontDescription *base_font_desc;
|
||||
|
||||
data.layout = layout;
|
||||
data.category = category;
|
||||
data.minimum_font_size = minimum_font_size;
|
||||
data.font_size = maximum_font_size;
|
||||
|
||||
eek_container_foreach_child (EEK_CONTAINER(keyboard),
|
||||
egf_section_callback,
|
||||
&data);
|
||||
|
||||
base_font_desc = pango_layout_get_font_description (layout);
|
||||
font_desc = pango_font_description_copy (base_font_desc);
|
||||
pango_font_description_set_size (font_desc, data.font_size);
|
||||
|
||||
return font_desc;
|
||||
}
|
||||
|
||||
void
|
||||
eek_get_fonts (EekKeyboard *keyboard,
|
||||
PangoLayout *layout,
|
||||
PangoFontDescription **fonts)
|
||||
{
|
||||
EekBounds bounds;
|
||||
PangoFontDescription *font_desc;
|
||||
gint font_size;
|
||||
|
||||
/* font for EEK_KEYSYM_CATEGORY_LETTER */
|
||||
eek_element_get_bounds (EEK_ELEMENT(keyboard), &bounds);
|
||||
font_desc = get_font_for_category (keyboard,
|
||||
EEK_KEYSYM_CATEGORY_LETTER,
|
||||
layout,
|
||||
0,
|
||||
eek_bounds_long_side (&bounds) *
|
||||
PANGO_SCALE);
|
||||
font_size = pango_font_description_get_size (font_desc);
|
||||
fonts[EEK_KEYSYM_CATEGORY_LETTER] = font_desc;
|
||||
|
||||
/* font for EEK_KEYSYM_CATEGORY_FUNCTION */
|
||||
font_desc = get_font_for_category (keyboard,
|
||||
EEK_KEYSYM_CATEGORY_FUNCTION,
|
||||
layout,
|
||||
font_size * 0.3,
|
||||
font_size);
|
||||
fonts[EEK_KEYSYM_CATEGORY_FUNCTION] = font_desc;
|
||||
|
||||
/* font for EEK_KEYSYM_CATEGORY_KEYNAME */
|
||||
font_desc = get_font_for_category (keyboard,
|
||||
EEK_KEYSYM_CATEGORY_KEYNAME,
|
||||
layout,
|
||||
font_size * 0.3,
|
||||
font_size);
|
||||
fonts[EEK_KEYSYM_CATEGORY_KEYNAME] = font_desc;
|
||||
}
|
||||
|
||||
void
|
||||
eek_draw_outline (cairo_t *cr, EekOutline *outline)
|
||||
{
|
||||
cairo_pattern_t *pat;
|
||||
|
||||
cairo_set_line_width (cr, 1);
|
||||
cairo_set_line_join (cr, CAIRO_LINE_JOIN_ROUND);
|
||||
|
||||
pat = cairo_pattern_create_linear (0.0, 0.0, 0.0, 256.0);
|
||||
cairo_pattern_add_color_stop_rgba (pat, 1, 0.5, 0.5, 0.5, 1);
|
||||
cairo_pattern_add_color_stop_rgba (pat, 0, 1, 1, 1, 1);
|
||||
|
||||
cairo_set_source (cr, pat);
|
||||
|
||||
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);
|
||||
eek_draw_rounded_polygon (cr,
|
||||
FALSE,
|
||||
outline->corner_radius,
|
||||
outline->points,
|
||||
outline->num_points);
|
||||
}
|
||||
|
||||
void
|
||||
eek_draw_key_label (cairo_t *cr,
|
||||
EekKey *key,
|
||||
PangoFontDescription **fonts)
|
||||
{
|
||||
guint keysym;
|
||||
EekKeysymCategory category;
|
||||
gchar *label;
|
||||
PangoLayout *layout;
|
||||
PangoRectangle logical_rect = { 0, };
|
||||
EekBounds bounds;
|
||||
|
||||
keysym = eek_key_get_keysym (key);
|
||||
if (keysym == EEK_INVALID_KEYSYM)
|
||||
return;
|
||||
|
||||
category = eek_keysym_get_category (keysym);
|
||||
if (category == EEK_KEYSYM_CATEGORY_UNKNOWN)
|
||||
return;
|
||||
|
||||
label = eek_keysym_to_string (keysym);
|
||||
if (!label)
|
||||
return;
|
||||
|
||||
eek_element_get_bounds (EEK_ELEMENT(key), &bounds);
|
||||
layout = pango_cairo_create_layout (cr);
|
||||
pango_layout_set_font_description (layout, fonts[category]);
|
||||
pango_layout_set_width (layout, PANGO_SCALE * bounds.width);
|
||||
pango_layout_set_ellipsize (layout, PANGO_ELLIPSIZE_END);
|
||||
pango_layout_set_text (layout, label, -1);
|
||||
pango_layout_get_extents (layout, NULL, &logical_rect);
|
||||
cairo_rel_move_to (cr,
|
||||
(bounds.width - logical_rect.width / PANGO_SCALE) / 2,
|
||||
(bounds.height - logical_rect.height / PANGO_SCALE) / 2);
|
||||
pango_cairo_show_layout (cr, layout);
|
||||
g_free (label);
|
||||
g_object_unref (layout);
|
||||
}
|
||||
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
|
||||
void
|
||||
eek_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);
|
||||
}
|
||||
230
eek/eek-keyboard-drawing.c
Normal file
230
eek/eek-keyboard-drawing.c
Normal file
@ -0,0 +1,230 @@
|
||||
/*
|
||||
* Copyright (C) 2006 Sergey V. Udaltsov <svu@gnome.org>
|
||||
*
|
||||
* This library 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 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that 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 library; if not, write to the
|
||||
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
* Boston, MA 02111-1307, USA.
|
||||
*/
|
||||
|
||||
#include <math.h>
|
||||
#include <pango/pangocairo.h>
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif /* HAVE_CONFIG_H */
|
||||
|
||||
#include "eek-types.h"
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
/* renamed from rounded_polygon, use EekPoint instead of GdkPoint not
|
||||
to depend on GTK+, and exported */
|
||||
void
|
||||
_eek_rounded_polygon (cairo_t *cr,
|
||||
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);
|
||||
}
|
||||
886
eek/eek-renderer.c
Normal file
886
eek/eek-renderer.c
Normal file
@ -0,0 +1,886 @@
|
||||
#include <math.h>
|
||||
|
||||
#include "eek-key.h"
|
||||
#include "eek-section.h"
|
||||
#include "eek-renderer.h"
|
||||
|
||||
G_DEFINE_TYPE (EekRenderer, eek_renderer, G_TYPE_OBJECT);
|
||||
|
||||
#define EEK_RENDERER_GET_PRIVATE(obj) \
|
||||
(G_TYPE_INSTANCE_GET_PRIVATE ((obj), EEK_TYPE_RENDERER, EekRendererPrivate))
|
||||
|
||||
struct _EekRendererPrivate
|
||||
{
|
||||
EekKeyboard *keyboard;
|
||||
PangoContext *pcontext;
|
||||
|
||||
EekColor *foreground;
|
||||
EekColor *background;
|
||||
gdouble border_width;
|
||||
|
||||
gdouble scale;
|
||||
PangoFontDescription *font;
|
||||
GHashTable *outline_surface_cache;
|
||||
cairo_surface_t *keyboard_surface;
|
||||
};
|
||||
|
||||
struct {
|
||||
gint category;
|
||||
gdouble scale;
|
||||
} keysym_category_scale_factors[EEK_KEYSYM_CATEGORY_LAST] = {
|
||||
{ EEK_KEYSYM_CATEGORY_LETTER, 1.0 },
|
||||
{ EEK_KEYSYM_CATEGORY_FUNCTION, 0.5 },
|
||||
{ EEK_KEYSYM_CATEGORY_KEYNAME, 0.5 }
|
||||
};
|
||||
|
||||
/* eek-keyboard-drawing.c */
|
||||
extern void _eek_rounded_polygon (cairo_t *cr,
|
||||
gdouble radius,
|
||||
EekPoint *points,
|
||||
gint num_points);
|
||||
|
||||
static void invalidate (EekRenderer *renderer);
|
||||
static void render_key (EekRenderer *self,
|
||||
cairo_t *cr,
|
||||
EekKey *key);
|
||||
|
||||
struct _CreateKeyboardSurfaceCallbackData {
|
||||
cairo_t *cr;
|
||||
EekRenderer *renderer;
|
||||
};
|
||||
typedef struct _CreateKeyboardSurfaceCallbackData CreateKeyboardSurfaceCallbackData;
|
||||
|
||||
static void
|
||||
create_keyboard_surface_key_callback (EekElement *element,
|
||||
gpointer user_data)
|
||||
{
|
||||
CreateKeyboardSurfaceCallbackData *data = user_data;
|
||||
EekRendererPrivate *priv = EEK_RENDERER_GET_PRIVATE(data->renderer);
|
||||
EekBounds bounds;
|
||||
|
||||
cairo_save (data->cr);
|
||||
|
||||
eek_element_get_bounds (element, &bounds);
|
||||
cairo_translate (data->cr, bounds.x * priv->scale, bounds.y * priv->scale);
|
||||
cairo_rectangle (data->cr,
|
||||
0.0,
|
||||
0.0,
|
||||
bounds.width * priv->scale,
|
||||
bounds.height * priv->scale);
|
||||
cairo_clip (data->cr);
|
||||
render_key (data->renderer, data->cr, EEK_KEY(element));
|
||||
|
||||
cairo_restore (data->cr);
|
||||
}
|
||||
|
||||
static void
|
||||
create_keyboard_surface_section_callback (EekElement *element,
|
||||
gpointer user_data)
|
||||
{
|
||||
CreateKeyboardSurfaceCallbackData *data = user_data;
|
||||
EekRendererPrivate *priv = EEK_RENDERER_GET_PRIVATE(data->renderer);
|
||||
EekBounds bounds;
|
||||
gint angle;
|
||||
|
||||
cairo_save (data->cr);
|
||||
|
||||
eek_element_get_bounds (element, &bounds);
|
||||
cairo_translate (data->cr, bounds.x * priv->scale, bounds.y * priv->scale);
|
||||
|
||||
angle = eek_section_get_angle (EEK_SECTION(element));
|
||||
cairo_rotate (data->cr, angle * G_PI / 180);
|
||||
|
||||
eek_container_foreach_child (EEK_CONTAINER(element),
|
||||
create_keyboard_surface_key_callback,
|
||||
data);
|
||||
|
||||
cairo_restore (data->cr);
|
||||
}
|
||||
|
||||
static cairo_surface_t *
|
||||
create_keyboard_surface (EekRenderer *renderer)
|
||||
{
|
||||
EekRendererPrivate *priv = EEK_RENDERER_GET_PRIVATE(renderer);
|
||||
EekBounds bounds;
|
||||
cairo_surface_t *keyboard_surface;
|
||||
CreateKeyboardSurfaceCallbackData data;
|
||||
|
||||
eek_element_get_bounds (EEK_ELEMENT(priv->keyboard), &bounds);
|
||||
keyboard_surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
|
||||
bounds.width * priv->scale,
|
||||
bounds.height * priv->scale);
|
||||
data.cr = cairo_create (keyboard_surface);
|
||||
data.renderer = renderer;
|
||||
|
||||
cairo_translate (data.cr, bounds.x * priv->scale, bounds.y * priv->scale);
|
||||
|
||||
/* blank background */
|
||||
cairo_set_source_rgba (data.cr, 0, 0, 0, 0);
|
||||
cairo_rectangle (data.cr,
|
||||
0.0,
|
||||
0.0,
|
||||
cairo_image_surface_get_width (keyboard_surface),
|
||||
cairo_image_surface_get_height (keyboard_surface));
|
||||
cairo_fill (data.cr);
|
||||
|
||||
/* draw sections */
|
||||
eek_container_foreach_child (EEK_CONTAINER(priv->keyboard),
|
||||
create_keyboard_surface_section_callback,
|
||||
&data);
|
||||
cairo_destroy (data.cr);
|
||||
return keyboard_surface;
|
||||
}
|
||||
|
||||
static void
|
||||
render_key_outline (EekRenderer *renderer,
|
||||
cairo_t *cr,
|
||||
EekKey *key)
|
||||
{
|
||||
EekRendererPrivate *priv = EEK_RENDERER_GET_PRIVATE(renderer);
|
||||
EekOutline *outline;
|
||||
EekBounds bounds;
|
||||
cairo_pattern_t *pat;
|
||||
EekPoint *points;
|
||||
gdouble scale;
|
||||
gint i;
|
||||
|
||||
/* need to rescale so that the border fit inside the clipping
|
||||
region */
|
||||
eek_element_get_bounds (EEK_ELEMENT(key), &bounds);
|
||||
scale = MIN((bounds.width - priv->border_width) / bounds.width,
|
||||
(bounds.height - priv->border_width) / bounds.height);
|
||||
|
||||
outline = eek_key_get_outline (key);
|
||||
points = g_slice_copy (sizeof (EekPoint) * outline->num_points,
|
||||
outline->points);
|
||||
for (i = 0; i < outline->num_points; i++) {
|
||||
points[i].x *= priv->scale * scale;
|
||||
points[i].y *= priv->scale * scale;
|
||||
}
|
||||
|
||||
cairo_translate (cr,
|
||||
priv->border_width / 2 * priv->scale,
|
||||
priv->border_width / 2 * priv->scale);
|
||||
|
||||
/* paint the background with gradient */
|
||||
pat = cairo_pattern_create_linear (0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
bounds.height * priv->scale * 5.0);
|
||||
cairo_pattern_add_color_stop_rgba (pat,
|
||||
1,
|
||||
priv->background->red * 0.5,
|
||||
priv->background->green * 0.5,
|
||||
priv->background->blue * 0.5,
|
||||
priv->background->alpha);
|
||||
cairo_pattern_add_color_stop_rgba (pat,
|
||||
0,
|
||||
priv->background->red,
|
||||
priv->background->green,
|
||||
priv->background->blue,
|
||||
priv->background->alpha);
|
||||
|
||||
cairo_set_source (cr, pat);
|
||||
_eek_rounded_polygon (cr,
|
||||
outline->corner_radius,
|
||||
points,
|
||||
outline->num_points);
|
||||
cairo_fill (cr);
|
||||
|
||||
cairo_pattern_destroy (pat);
|
||||
|
||||
/* paint the border */
|
||||
cairo_set_line_width (cr, priv->border_width * priv->scale);
|
||||
cairo_set_line_join (cr, CAIRO_LINE_JOIN_ROUND);
|
||||
|
||||
cairo_set_source_rgba
|
||||
(cr,
|
||||
ABS(priv->background->red - priv->foreground->red) * 0.7,
|
||||
ABS(priv->background->green - priv->foreground->green) * 0.7,
|
||||
ABS(priv->background->blue - priv->foreground->blue) * 0.7,
|
||||
priv->foreground->alpha);
|
||||
|
||||
_eek_rounded_polygon (cr,
|
||||
outline->corner_radius,
|
||||
points,
|
||||
outline->num_points);
|
||||
cairo_stroke (cr);
|
||||
|
||||
g_slice_free1 (sizeof (EekPoint) * outline->num_points, points);
|
||||
}
|
||||
|
||||
struct _CalculateFontSizeCallbackData {
|
||||
gdouble size;
|
||||
EekRenderer *renderer;
|
||||
};
|
||||
typedef struct _CalculateFontSizeCallbackData CalculateFontSizeCallbackData;
|
||||
|
||||
static void
|
||||
calculate_font_size_key_callback (EekElement *element, gpointer user_data)
|
||||
{
|
||||
CalculateFontSizeCallbackData *data = user_data;
|
||||
EekRendererPrivate *priv = EEK_RENDERER_GET_PRIVATE(data->renderer);
|
||||
gdouble sx, sy;
|
||||
PangoFontDescription *font;
|
||||
const PangoFontDescription *base_font;
|
||||
PangoRectangle extents = { 0, };
|
||||
PangoLayout *layout;
|
||||
gdouble size;
|
||||
guint keysym;
|
||||
EekBounds bounds;
|
||||
gchar *label;
|
||||
|
||||
keysym = eek_key_get_keysym (EEK_KEY(element));
|
||||
if (keysym == EEK_INVALID_KEYSYM ||
|
||||
eek_keysym_get_category (keysym) != EEK_KEYSYM_CATEGORY_LETTER)
|
||||
return;
|
||||
|
||||
label = eek_keysym_to_string (keysym);
|
||||
if (!label)
|
||||
return;
|
||||
|
||||
base_font = pango_context_get_font_description (priv->pcontext);
|
||||
font = pango_font_description_copy (base_font);
|
||||
|
||||
eek_element_get_bounds (element, &bounds);
|
||||
size = eek_bounds_long_side (&bounds) * PANGO_SCALE;
|
||||
pango_font_description_set_size (font, size);
|
||||
layout = pango_layout_new (priv->pcontext);
|
||||
pango_layout_set_font_description (layout, font);
|
||||
pango_font_description_free (font);
|
||||
|
||||
pango_layout_set_text (layout, label, -1);
|
||||
g_free (label);
|
||||
|
||||
pango_layout_get_extents (layout, NULL, &extents);
|
||||
|
||||
sx = sy = 1.0;
|
||||
if (extents.width > bounds.width * PANGO_SCALE)
|
||||
sx = bounds.width * PANGO_SCALE / extents.width;
|
||||
if (extents.height > bounds.height * PANGO_SCALE)
|
||||
sy = bounds.height * PANGO_SCALE / extents.height;
|
||||
g_object_unref (layout);
|
||||
|
||||
size *= MIN(sx, sy);
|
||||
if (size < data->size &&
|
||||
size >= pango_font_description_get_size (base_font))
|
||||
data->size = size;
|
||||
}
|
||||
|
||||
static void
|
||||
calculate_font_size_section_callback (EekElement *element, gpointer user_data)
|
||||
{
|
||||
eek_container_foreach_child (EEK_CONTAINER(element),
|
||||
calculate_font_size_key_callback,
|
||||
user_data);
|
||||
}
|
||||
|
||||
static gdouble
|
||||
calculate_font_size (EekRenderer *renderer)
|
||||
{
|
||||
EekRendererPrivate *priv = EEK_RENDERER_GET_PRIVATE(renderer);
|
||||
CalculateFontSizeCallbackData data;
|
||||
PangoFontDescription *base_font;
|
||||
|
||||
base_font = pango_context_get_font_description (priv->pcontext);
|
||||
data.size = pango_font_description_get_size (base_font);
|
||||
data.renderer = renderer;
|
||||
eek_container_foreach_child (EEK_CONTAINER(priv->keyboard),
|
||||
calculate_font_size_section_callback,
|
||||
&data);
|
||||
return data.size;
|
||||
}
|
||||
|
||||
static void
|
||||
render_key_label (EekRenderer *renderer,
|
||||
cairo_t *cr,
|
||||
EekKey *key)
|
||||
{
|
||||
EekRendererPrivate *priv = EEK_RENDERER_GET_PRIVATE(renderer);
|
||||
guint keysym;
|
||||
EekKeysymCategory category;
|
||||
gchar *label;
|
||||
PangoLayout *layout;
|
||||
PangoRectangle extents = { 0, };
|
||||
EekBounds bounds;
|
||||
PangoFontDescription *font;
|
||||
gdouble size, scale;
|
||||
gint i;
|
||||
|
||||
keysym = eek_key_get_keysym (key);
|
||||
if (keysym == EEK_INVALID_KEYSYM)
|
||||
return;
|
||||
|
||||
category = eek_keysym_get_category (keysym);
|
||||
if (category == EEK_KEYSYM_CATEGORY_UNKNOWN)
|
||||
return;
|
||||
|
||||
label = eek_keysym_to_string (keysym);
|
||||
if (!label)
|
||||
return;
|
||||
|
||||
eek_element_get_bounds (EEK_ELEMENT(key), &bounds);
|
||||
scale = MIN((bounds.width - priv->border_width) / bounds.width,
|
||||
(bounds.height - priv->border_width) / bounds.height);
|
||||
|
||||
layout = pango_cairo_create_layout (cr);
|
||||
font = pango_font_description_copy (priv->font);
|
||||
size = pango_font_description_get_size (font);
|
||||
for (i = 0; i < G_N_ELEMENTS(keysym_category_scale_factors); i++)
|
||||
if (keysym_category_scale_factors[i].category == category) {
|
||||
size *= keysym_category_scale_factors[i].scale;
|
||||
break;
|
||||
}
|
||||
pango_font_description_set_size (font, size * priv->scale * scale);
|
||||
pango_layout_set_font_description (layout, font);
|
||||
pango_layout_set_text (layout, label, -1);
|
||||
g_free (label);
|
||||
pango_layout_set_width (layout,
|
||||
PANGO_SCALE * bounds.width * priv->scale * scale);
|
||||
pango_layout_set_ellipsize (layout, PANGO_ELLIPSIZE_END);
|
||||
pango_layout_get_extents (layout, NULL, &extents);
|
||||
|
||||
cairo_save (cr);
|
||||
cairo_move_to
|
||||
(cr,
|
||||
(bounds.width * priv->scale - extents.width / PANGO_SCALE) / 2,
|
||||
(bounds.height * priv->scale - extents.height / PANGO_SCALE) / 2);
|
||||
cairo_set_source_rgba (cr,
|
||||
priv->foreground->red,
|
||||
priv->foreground->green,
|
||||
priv->foreground->blue,
|
||||
priv->foreground->alpha);
|
||||
pango_cairo_show_layout (cr, layout);
|
||||
cairo_restore (cr);
|
||||
g_object_unref (layout);
|
||||
}
|
||||
|
||||
static void
|
||||
render_key (EekRenderer *self,
|
||||
cairo_t *cr,
|
||||
EekKey *key)
|
||||
{
|
||||
EekRendererPrivate *priv = EEK_RENDERER_GET_PRIVATE(self);
|
||||
EekOutline *outline;
|
||||
cairo_surface_t *outline_surface;
|
||||
EekBounds bounds;
|
||||
|
||||
outline = eek_key_get_outline (key);
|
||||
|
||||
outline_surface = g_hash_table_lookup (priv->outline_surface_cache,
|
||||
outline);
|
||||
if (!outline_surface) {
|
||||
cairo_t *cr;
|
||||
|
||||
eek_element_get_bounds (EEK_ELEMENT(key), &bounds);
|
||||
outline_surface =
|
||||
cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
|
||||
bounds.width * priv->scale,
|
||||
bounds.height * priv->scale);
|
||||
cr = cairo_create (outline_surface);
|
||||
|
||||
/* blank background */
|
||||
eek_element_get_bounds (EEK_ELEMENT(key), &bounds);
|
||||
cairo_set_source_rgba (cr, 0.0, 0.0, 0.0, 0.0);
|
||||
cairo_rectangle (cr,
|
||||
0.0,
|
||||
0.0,
|
||||
bounds.width * priv->scale,
|
||||
bounds.height * priv->scale);
|
||||
cairo_fill (cr);
|
||||
|
||||
render_key_outline (self, cr, key);
|
||||
|
||||
g_hash_table_insert (priv->outline_surface_cache,
|
||||
outline,
|
||||
outline_surface);
|
||||
}
|
||||
|
||||
if (!priv->font) {
|
||||
PangoFontDescription *base_font;
|
||||
gdouble size;
|
||||
|
||||
size = calculate_font_size (self);
|
||||
base_font = pango_context_get_font_description (priv->pcontext);
|
||||
priv->font = pango_font_description_copy (base_font);
|
||||
pango_font_description_set_size (priv->font, size);
|
||||
}
|
||||
|
||||
cairo_set_source_surface (cr, outline_surface, 0.0, 0.0);
|
||||
cairo_paint (cr);
|
||||
|
||||
render_key_label (self, cr, key);
|
||||
}
|
||||
|
||||
static void
|
||||
eek_renderer_real_render_key (EekRenderer *self,
|
||||
cairo_t *cr,
|
||||
EekKey *key,
|
||||
gdouble scale)
|
||||
{
|
||||
EekElement *section;
|
||||
EekBounds bounds, rotated_bounds;
|
||||
gint angle;
|
||||
gdouble s;
|
||||
|
||||
eek_renderer_get_key_bounds (self, key, &bounds, FALSE);
|
||||
eek_renderer_get_key_bounds (self, key, &rotated_bounds, TRUE);
|
||||
|
||||
section = eek_element_get_parent (EEK_ELEMENT(key));
|
||||
angle = eek_section_get_angle (EEK_SECTION(section));
|
||||
|
||||
cairo_save (cr);
|
||||
cairo_scale (cr, scale, scale);
|
||||
#if DEBUG
|
||||
cairo_set_source_rgba (cr, 1.0, 0.0, 0.0, 1.0);
|
||||
cairo_rectangle (cr, 0, 0,
|
||||
rotated_bounds.width, rotated_bounds.height);
|
||||
cairo_stroke (cr);
|
||||
cairo_set_source_rgba (cr, 0.0, 1.0, 0.0, 1.0);
|
||||
#endif /* DEBUG */
|
||||
s = sin (angle * G_PI / 180);
|
||||
if (s < 0)
|
||||
cairo_translate (cr, 0, - bounds.width * s);
|
||||
else
|
||||
cairo_translate (cr, bounds.height * s, 0);
|
||||
cairo_rotate (cr, angle * G_PI / 180);
|
||||
#if DEBUG
|
||||
cairo_rectangle (cr,
|
||||
0,
|
||||
0,
|
||||
bounds.width, bounds.height);
|
||||
cairo_stroke (cr);
|
||||
#endif /* DEBUG */
|
||||
render_key (self, cr, key);
|
||||
cairo_restore (cr);
|
||||
}
|
||||
|
||||
static void
|
||||
eek_renderer_real_render_keyboard (EekRenderer *self,
|
||||
cairo_t *cr)
|
||||
{
|
||||
EekRendererPrivate *priv = EEK_RENDERER_GET_PRIVATE(self);
|
||||
|
||||
if (!priv->keyboard_surface)
|
||||
priv->keyboard_surface = create_keyboard_surface (self);
|
||||
|
||||
cairo_set_source_surface (cr, priv->keyboard_surface, 0.0, 0.0);
|
||||
cairo_paint (cr);
|
||||
}
|
||||
|
||||
static void
|
||||
eek_renderer_dispose (GObject *object)
|
||||
{
|
||||
EekRendererPrivate *priv = EEK_RENDERER_GET_PRIVATE(object);
|
||||
|
||||
if (priv->keyboard) {
|
||||
if (g_object_is_floating (priv->keyboard))
|
||||
g_object_unref (priv->keyboard);
|
||||
priv->keyboard = NULL;
|
||||
}
|
||||
if (priv->pcontext) {
|
||||
g_object_unref (priv->pcontext);
|
||||
priv->pcontext = NULL;
|
||||
}
|
||||
|
||||
/* this will release all allocated surfaces and font if any */
|
||||
invalidate (EEK_RENDERER(object));
|
||||
|
||||
G_OBJECT_CLASS (eek_renderer_parent_class)->dispose (object);
|
||||
}
|
||||
|
||||
static void
|
||||
eek_renderer_finalize (GObject *object)
|
||||
{
|
||||
EekRendererPrivate *priv = EEK_RENDERER_GET_PRIVATE(object);
|
||||
|
||||
g_hash_table_destroy (priv->outline_surface_cache);
|
||||
G_OBJECT_CLASS (eek_renderer_parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
static void
|
||||
eek_renderer_class_init (EekRendererClass *klass)
|
||||
{
|
||||
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
|
||||
|
||||
g_type_class_add_private (gobject_class,
|
||||
sizeof (EekRendererPrivate));
|
||||
|
||||
klass->render_key = eek_renderer_real_render_key;
|
||||
klass->render_keyboard = eek_renderer_real_render_keyboard;
|
||||
|
||||
gobject_class->dispose = eek_renderer_dispose;
|
||||
gobject_class->finalize = eek_renderer_finalize;
|
||||
}
|
||||
|
||||
static void
|
||||
free_surface (gpointer data)
|
||||
{
|
||||
cairo_surface_destroy (data);
|
||||
}
|
||||
|
||||
static void
|
||||
eek_renderer_init (EekRenderer *self)
|
||||
{
|
||||
EekRendererPrivate *priv;
|
||||
|
||||
priv = self->priv = EEK_RENDERER_GET_PRIVATE(self);
|
||||
priv->keyboard = NULL;
|
||||
priv->pcontext = NULL;
|
||||
priv->foreground = eek_color_new (0.3, 0.3, 0.3, 1.0);
|
||||
priv->background = eek_color_new (1.0, 1.0, 1.0, 1.0);
|
||||
priv->border_width = 3.0;
|
||||
priv->scale = 1.0;
|
||||
priv->font = NULL;
|
||||
priv->outline_surface_cache =
|
||||
g_hash_table_new_full (g_direct_hash,
|
||||
g_direct_equal,
|
||||
NULL,
|
||||
free_surface);
|
||||
priv->keyboard_surface = NULL;
|
||||
}
|
||||
|
||||
static void
|
||||
invalidate (EekRenderer *renderer)
|
||||
{
|
||||
EekRendererPrivate *priv = EEK_RENDERER_GET_PRIVATE(renderer);
|
||||
|
||||
if (priv->outline_surface_cache)
|
||||
g_hash_table_remove_all (priv->outline_surface_cache);
|
||||
|
||||
if (priv->keyboard_surface) {
|
||||
cairo_surface_destroy (priv->keyboard_surface);
|
||||
priv->keyboard_surface = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
on_keysym_index_changed (EekKeyboard *keyboard,
|
||||
gint group,
|
||||
gint level,
|
||||
gpointer user_data)
|
||||
{
|
||||
EekRenderer *renderer = user_data;
|
||||
invalidate (renderer);
|
||||
}
|
||||
|
||||
EekRenderer *
|
||||
eek_renderer_new (EekKeyboard *keyboard,
|
||||
PangoContext *pcontext)
|
||||
{
|
||||
EekRenderer *renderer;
|
||||
EekRendererPrivate *priv;
|
||||
|
||||
renderer = g_object_new (EEK_TYPE_RENDERER, NULL);
|
||||
priv = EEK_RENDERER_GET_PRIVATE(renderer);
|
||||
priv->keyboard = g_object_ref_sink (keyboard);
|
||||
priv->pcontext = g_object_ref (pcontext);
|
||||
|
||||
g_signal_connect (keyboard, "keysym-index-changed",
|
||||
G_CALLBACK(on_keysym_index_changed),
|
||||
renderer);
|
||||
|
||||
return renderer;
|
||||
}
|
||||
|
||||
void
|
||||
eek_renderer_set_preferred_size (EekRenderer *renderer,
|
||||
gdouble width,
|
||||
gdouble height)
|
||||
{
|
||||
EekRendererPrivate *priv;
|
||||
EekBounds bounds;
|
||||
gdouble scale;
|
||||
|
||||
g_return_if_fail (EEK_IS_RENDERER(renderer));
|
||||
g_return_if_fail (width > 0.0 && height > 0.0);
|
||||
|
||||
priv = EEK_RENDERER_GET_PRIVATE(renderer);
|
||||
|
||||
eek_element_get_bounds (EEK_ELEMENT(priv->keyboard), &bounds);
|
||||
scale = width > height ? height / bounds.height :
|
||||
width / bounds.width;
|
||||
|
||||
if (scale != priv->scale) {
|
||||
priv->scale = scale;
|
||||
invalidate (renderer);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
eek_renderer_get_key_bounds (EekRenderer *renderer,
|
||||
EekKey *key,
|
||||
EekBounds *bounds,
|
||||
gboolean rotate)
|
||||
{
|
||||
EekRendererPrivate *priv;
|
||||
EekElement *section;
|
||||
EekBounds section_bounds, keyboard_bounds;
|
||||
gint angle = 0;
|
||||
EekPoint points[4], min, max;
|
||||
gint i;
|
||||
|
||||
g_return_if_fail (EEK_IS_RENDERER(renderer));
|
||||
g_return_if_fail (EEK_IS_KEY(key));
|
||||
g_return_if_fail (bounds != NULL);
|
||||
|
||||
priv = EEK_RENDERER_GET_PRIVATE(renderer);
|
||||
|
||||
section = eek_element_get_parent (EEK_ELEMENT(key));
|
||||
|
||||
eek_element_get_bounds (EEK_ELEMENT(key), bounds);
|
||||
eek_element_get_bounds (section, §ion_bounds);
|
||||
eek_element_get_bounds (EEK_ELEMENT(priv->keyboard), &keyboard_bounds);
|
||||
|
||||
if (!rotate) {
|
||||
bounds->x += keyboard_bounds.x + section_bounds.x;
|
||||
bounds->y += keyboard_bounds.y + section_bounds.y;
|
||||
bounds->x *= priv->scale;
|
||||
bounds->y *= priv->scale;
|
||||
bounds->width *= priv->scale;
|
||||
bounds->height *= priv->scale;
|
||||
return;
|
||||
}
|
||||
points[0].x = bounds->x;
|
||||
points[0].y = bounds->y;
|
||||
points[1].x = points[0].x + bounds->width;
|
||||
points[1].y = points[0].y;
|
||||
points[2].x = points[1].x;
|
||||
points[2].y = points[1].y + bounds->height;
|
||||
points[3].x = points[0].x;
|
||||
points[3].y = points[2].y;
|
||||
|
||||
if (rotate)
|
||||
angle = eek_section_get_angle (EEK_SECTION(section));
|
||||
|
||||
min = points[2];
|
||||
max = points[0];
|
||||
for (i = 0; i < G_N_ELEMENTS(points); i++) {
|
||||
eek_point_rotate (&points[i], angle);
|
||||
if (points[i].x < min.x)
|
||||
min.x = points[i].x;
|
||||
if (points[i].x > max.x)
|
||||
max.x = points[i].x;
|
||||
if (points[i].y < min.y)
|
||||
min.y = points[i].y;
|
||||
if (points[i].y > max.y)
|
||||
max.y = points[i].y;
|
||||
}
|
||||
bounds->x = keyboard_bounds.x + section_bounds.x + min.x;
|
||||
bounds->y = keyboard_bounds.y + section_bounds.y + min.y;
|
||||
bounds->width = (max.x - min.x);
|
||||
bounds->height = (max.y - min.y);
|
||||
bounds->x *= priv->scale;
|
||||
bounds->y *= priv->scale;
|
||||
bounds->width *= priv->scale;
|
||||
bounds->height *= priv->scale;
|
||||
}
|
||||
|
||||
gdouble
|
||||
eek_renderer_get_scale (EekRenderer *renderer)
|
||||
{
|
||||
EekRendererPrivate *priv;
|
||||
|
||||
g_assert (EEK_IS_RENDERER(renderer));
|
||||
|
||||
priv = EEK_RENDERER_GET_PRIVATE(renderer);
|
||||
|
||||
return priv->scale;
|
||||
}
|
||||
|
||||
void
|
||||
eek_renderer_render_key (EekRenderer *renderer,
|
||||
cairo_t *cr,
|
||||
EekKey *key,
|
||||
gdouble scale)
|
||||
{
|
||||
g_return_if_fail (EEK_IS_RENDERER(renderer));
|
||||
g_return_if_fail (scale >= 0.0);
|
||||
|
||||
EEK_RENDERER_GET_CLASS(renderer)->render_key (renderer, cr, key, scale);
|
||||
}
|
||||
|
||||
void
|
||||
eek_renderer_render_keyboard (EekRenderer *renderer,
|
||||
cairo_t *cr)
|
||||
{
|
||||
g_return_if_fail (EEK_IS_RENDERER(renderer));
|
||||
EEK_RENDERER_GET_CLASS(renderer)->render_keyboard (renderer, cr);
|
||||
}
|
||||
|
||||
void
|
||||
eek_renderer_set_foreground (EekRenderer *renderer,
|
||||
EekColor *foreground)
|
||||
{
|
||||
EekRendererPrivate *priv;
|
||||
|
||||
g_return_if_fail (EEK_IS_RENDERER(renderer));
|
||||
g_return_if_fail (foreground);
|
||||
|
||||
priv = EEK_RENDERER_GET_PRIVATE(renderer);
|
||||
priv->foreground = g_boxed_copy (EEK_TYPE_COLOR, foreground);
|
||||
}
|
||||
|
||||
void
|
||||
eek_renderer_set_background (EekRenderer *renderer,
|
||||
EekColor *background)
|
||||
{
|
||||
EekRendererPrivate *priv;
|
||||
|
||||
g_return_if_fail (EEK_IS_RENDERER(renderer));
|
||||
g_return_if_fail (background);
|
||||
|
||||
priv = EEK_RENDERER_GET_PRIVATE(renderer);
|
||||
priv->background = g_boxed_copy (EEK_TYPE_COLOR, background);
|
||||
}
|
||||
|
||||
void
|
||||
eek_renderer_get_foreground (EekRenderer *renderer,
|
||||
EekColor *foreground)
|
||||
{
|
||||
EekRendererPrivate *priv;
|
||||
|
||||
g_return_if_fail (EEK_IS_RENDERER(renderer));
|
||||
g_return_if_fail (foreground);
|
||||
|
||||
priv = EEK_RENDERER_GET_PRIVATE(renderer);
|
||||
*foreground = *priv->foreground;
|
||||
}
|
||||
|
||||
void
|
||||
eek_renderer_get_background (EekRenderer *renderer,
|
||||
EekColor *background)
|
||||
{
|
||||
EekRendererPrivate *priv;
|
||||
|
||||
g_return_if_fail (EEK_IS_RENDERER(renderer));
|
||||
g_return_if_fail (background);
|
||||
|
||||
priv = EEK_RENDERER_GET_PRIVATE(renderer);
|
||||
*background = *priv->background;
|
||||
}
|
||||
|
||||
struct _FindKeyByPositionCallbackData {
|
||||
EekPoint point;
|
||||
EekPoint origin;
|
||||
gint angle;
|
||||
EekKey *key;
|
||||
EekRenderer *renderer;
|
||||
};
|
||||
typedef struct _FindKeyByPositionCallbackData FindKeyByPositionCallbackData;
|
||||
|
||||
static gboolean
|
||||
sign (EekPoint *p1, EekPoint *p2, EekPoint *p3)
|
||||
{
|
||||
return (p1->x - p3->x) * (p2->y - p3->y) -
|
||||
(p2->x - p3->x) * (p1->y - p3->y);
|
||||
}
|
||||
|
||||
static gint
|
||||
find_key_by_position_key_callback (EekElement *element,
|
||||
gpointer user_data)
|
||||
{
|
||||
FindKeyByPositionCallbackData *data = user_data;
|
||||
EekBounds bounds;
|
||||
EekRendererPrivate *priv;
|
||||
EekPoint points[4];
|
||||
gint i;
|
||||
gboolean b1, b2, b3;
|
||||
|
||||
priv = EEK_RENDERER_GET_PRIVATE(data->renderer);
|
||||
|
||||
eek_element_get_bounds (element, &bounds);
|
||||
|
||||
points[0].x = bounds.x;
|
||||
points[0].y = bounds.y;
|
||||
points[1].x = points[0].x + bounds.width;
|
||||
points[1].y = points[0].y;
|
||||
points[2].x = points[1].x;
|
||||
points[2].y = points[1].y + bounds.height;
|
||||
points[3].x = points[0].x;
|
||||
points[3].y = points[2].y;
|
||||
|
||||
for (i = 0; i < G_N_ELEMENTS(points); i++) {
|
||||
eek_point_rotate (&points[i], data->angle);
|
||||
points[i].x += data->origin.x;
|
||||
points[i].y += data->origin.y;
|
||||
points[i].x *= priv->scale;
|
||||
points[i].y *= priv->scale;
|
||||
}
|
||||
|
||||
b1 = sign (&data->point, &points[0], &points[1]) < 0.0;
|
||||
b2 = sign (&data->point, &points[1], &points[2]) < 0.0;
|
||||
b3 = sign (&data->point, &points[2], &points[0]) < 0.0;
|
||||
|
||||
if (b1 == b2 && b2 == b3) {
|
||||
data->key = EEK_KEY(element);
|
||||
return 0;
|
||||
}
|
||||
|
||||
b1 = sign (&data->point, &points[2], &points[3]) < 0.0;
|
||||
b2 = sign (&data->point, &points[3], &points[0]) < 0.0;
|
||||
b3 = sign (&data->point, &points[0], &points[2]) < 0.0;
|
||||
|
||||
if (b1 == b2 && b2 == b3) {
|
||||
data->key = EEK_KEY(element);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
static gint
|
||||
find_key_by_position_section_callback (EekElement *element,
|
||||
gpointer user_data)
|
||||
{
|
||||
FindKeyByPositionCallbackData *data = user_data;
|
||||
EekBounds bounds;
|
||||
EekRendererPrivate *priv;
|
||||
EekPoint origin;
|
||||
|
||||
priv = EEK_RENDERER_GET_PRIVATE(data->renderer);
|
||||
|
||||
origin = data->origin;
|
||||
eek_element_get_bounds (element, &bounds);
|
||||
data->origin.x += bounds.x;
|
||||
data->origin.y += bounds.y;
|
||||
data->angle = eek_section_get_angle (EEK_SECTION(element));
|
||||
|
||||
eek_container_find (EEK_CONTAINER(element),
|
||||
find_key_by_position_key_callback,
|
||||
data);
|
||||
data->origin = origin;
|
||||
return data->key ? 0 : -1;
|
||||
}
|
||||
|
||||
EekKey *
|
||||
eek_renderer_find_key_by_position (EekRenderer *renderer,
|
||||
gdouble x,
|
||||
gdouble y)
|
||||
{
|
||||
EekRendererPrivate *priv;
|
||||
EekBounds bounds;
|
||||
FindKeyByPositionCallbackData data;
|
||||
|
||||
g_assert (EEK_IS_RENDERER(renderer));
|
||||
|
||||
priv = EEK_RENDERER_GET_PRIVATE(renderer);
|
||||
eek_element_get_bounds (EEK_ELEMENT(priv->keyboard), &bounds);
|
||||
|
||||
g_assert (x >= bounds.x * priv->scale);
|
||||
g_assert (y >= bounds.y * priv->scale);
|
||||
g_assert (x <= bounds.width * priv->scale);
|
||||
g_assert (y <= bounds.height * priv->scale);
|
||||
|
||||
data.point.x = x;
|
||||
data.point.y = y;
|
||||
data.origin.x = bounds.x;
|
||||
data.origin.y = bounds.y;
|
||||
data.key = NULL;
|
||||
data.renderer = renderer;
|
||||
|
||||
eek_container_find (EEK_CONTAINER(priv->keyboard),
|
||||
find_key_by_position_section_callback,
|
||||
&data);
|
||||
return data.key;
|
||||
}
|
||||
83
eek/eek-renderer.h
Normal file
83
eek/eek-renderer.h
Normal file
@ -0,0 +1,83 @@
|
||||
#ifndef EEK_RENDERER_H
|
||||
#define EEK_RENDERER_H 1
|
||||
|
||||
#include <pango/pangocairo.h>
|
||||
#include "eek-keyboard.h"
|
||||
#include "eek-keysym.h"
|
||||
#include "eek-types.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define EEK_TYPE_RENDERER (eek_renderer_get_type())
|
||||
#define EEK_RENDERER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), EEK_TYPE_RENDERER, EekRenderer))
|
||||
#define EEK_RENDERER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), EEK_TYPE_RENDERER, EekRendererClass))
|
||||
#define EEK_IS_RENDERER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), EEK_TYPE_RENDERER))
|
||||
#define EEK_IS_RENDERER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), EEK_TYPE_RENDERER))
|
||||
#define EEK_RENDERER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), EEK_TYPE_RENDERER, EekRendererClass))
|
||||
|
||||
typedef struct _EekRenderer EekRenderer;
|
||||
typedef struct _EekRendererClass EekRendererClass;
|
||||
typedef struct _EekRendererPrivate EekRendererPrivate;
|
||||
|
||||
struct _EekRenderer {
|
||||
GObject parent;
|
||||
|
||||
EekRendererPrivate *priv;
|
||||
};
|
||||
|
||||
struct _EekRendererClass
|
||||
{
|
||||
GObjectClass parent_class;
|
||||
|
||||
void (* render_key) (EekRenderer *self,
|
||||
cairo_t *cr,
|
||||
EekKey *key,
|
||||
gdouble scale);
|
||||
|
||||
void (* render_keyboard) (EekRenderer *self,
|
||||
cairo_t *cr);
|
||||
|
||||
/*< private >*/
|
||||
/* padding */
|
||||
gpointer pdummy[24];
|
||||
};
|
||||
|
||||
GType eek_renderer_get_type (void) G_GNUC_CONST;
|
||||
EekRenderer *eek_renderer_new (EekKeyboard *keyboard,
|
||||
PangoContext *pcontext);
|
||||
void eek_renderer_set_preferred_size (EekRenderer *renderer,
|
||||
gdouble width,
|
||||
gdouble height);
|
||||
void eek_renderer_get_size (EekRenderer *renderer,
|
||||
gdouble *width,
|
||||
gdouble *height);
|
||||
void eek_renderer_get_key_bounds (EekRenderer *renderer,
|
||||
EekKey *key,
|
||||
EekBounds *bounds,
|
||||
gboolean rotate);
|
||||
gdouble eek_renderer_get_scale (EekRenderer *renderer);
|
||||
|
||||
void eek_renderer_render_key (EekRenderer *renderer,
|
||||
cairo_t *cr,
|
||||
EekKey *key,
|
||||
gdouble scale);
|
||||
|
||||
void eek_renderer_render_keyboard (EekRenderer *renderer,
|
||||
cairo_t *cr);
|
||||
|
||||
void eek_renderer_set_foreground (EekRenderer *renderer,
|
||||
EekColor *foreground);
|
||||
void eek_renderer_set_background (EekRenderer *renderer,
|
||||
EekColor *background);
|
||||
void eek_renderer_get_foreground (EekRenderer *renderer,
|
||||
EekColor *foreground);
|
||||
void eek_renderer_get_background (EekRenderer *renderer,
|
||||
EekColor *background);
|
||||
void eek_renderer_set_border_width (EekRenderer *renderer,
|
||||
gdouble border_width);
|
||||
EekKey *eek_renderer_find_key_by_position (EekRenderer *renderer,
|
||||
gdouble x,
|
||||
gdouble y);
|
||||
|
||||
G_END_DECLS
|
||||
#endif /* EEK_RENDERER_H */
|
||||
Reference in New Issue
Block a user