diff options
Diffstat (limited to 'src/box.c')
-rw-r--r-- | src/box.c | 504 |
1 files changed, 504 insertions, 0 deletions
diff --git a/src/box.c b/src/box.c new file mode 100644 index 0000000..2a542bf --- /dev/null +++ b/src/box.c @@ -0,0 +1,504 @@ +/* This file is part of Ganv. + * Copyright 2007-2011 David Robillard <http://drobilla.net> + * + * Ganv is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * Ganv 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 General Public License for + * details. + * + * You should have received a copy of the GNU General Public License + * along with Ganv. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <math.h> +#include <string.h> + +#include <cairo.h> + +#include "ganv/box.h" + +#include "./boilerplate.h" +#include "./color.h" +#include "./gettext.h" + +static const double STACKED_OFFSET = 4.0; + +G_DEFINE_TYPE(GanvBox, ganv_box, GANV_TYPE_NODE) + +static GanvNodeClass* parent_class; + +enum { + PROP_0, + PROP_X1, + PROP_Y1, + PROP_X2, + PROP_Y2, + PROP_RADIUS_TL, + PROP_RADIUS_TR, + PROP_RADIUS_BR, + PROP_RADIUS_BL, + PROP_STACKED +}; + +static void +ganv_box_init(GanvBox* box) +{ + memset(&box->coords, '\0', sizeof(GanvBoxCoords)); + box->coords.border_width = box->node.border_width; + + box->old_coords = box->coords; + box->radius_tl = 0.0; + box->radius_tr = 0.0; + box->radius_br = 0.0; + box->radius_bl = 0.0; +} + +static void +ganv_box_destroy(GtkObject* object) +{ + g_return_if_fail(object != NULL); + g_return_if_fail(GANV_IS_BOX(object)); + + if (GTK_OBJECT_CLASS(parent_class)->destroy) { + (*GTK_OBJECT_CLASS(parent_class)->destroy)(object); + } +} + +static void +ganv_box_set_property(GObject* object, + guint prop_id, + const GValue* value, + GParamSpec* pspec) +{ + g_return_if_fail(object != NULL); + g_return_if_fail(GANV_IS_BOX(object)); + + GanvBox* box = GANV_BOX(object); + GanvBoxCoords* coords = &box->coords; + + switch (prop_id) { + SET_CASE(X1, double, coords->x1); + SET_CASE(Y1, double, coords->y1); + SET_CASE(X2, double, coords->x2); + SET_CASE(Y2, double, coords->y2); + SET_CASE(RADIUS_TL, double, box->radius_tl); + SET_CASE(RADIUS_TR, double, box->radius_tr); + SET_CASE(RADIUS_BR, double, box->radius_br); + SET_CASE(RADIUS_BL, double, box->radius_bl); + SET_CASE(STACKED, boolean, coords->stacked); + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void +ganv_box_get_property(GObject* object, + guint prop_id, + GValue* value, + GParamSpec* pspec) +{ + g_return_if_fail(object != NULL); + g_return_if_fail(GANV_IS_BOX(object)); + + GanvBox* box = GANV_BOX(object); + + switch (prop_id) { + GET_CASE(X1, double, box->coords.x1); + GET_CASE(X2, double, box->coords.x2); + GET_CASE(Y1, double, box->coords.y1); + GET_CASE(Y2, double, box->coords.y2); + GET_CASE(RADIUS_TL, double, box->radius_tl); + GET_CASE(RADIUS_TR, double, box->radius_tr); + GET_CASE(RADIUS_BR, double, box->radius_br); + GET_CASE(RADIUS_BL, double, box->radius_bl); + GET_CASE(STACKED, boolean, box->coords.stacked); + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void +ganv_box_bounds_item(const GanvBoxCoords* coords, + double* x1, double* y1, + double* x2, double* y2) +{ + *x1 = MIN(coords->x1, coords->x2) - coords->border_width; + *y1 = MIN(coords->y1, coords->y2) - coords->border_width; + *x2 = MAX(coords->x1, coords->x2) + coords->border_width; + *y2 = MAX(coords->y1, coords->y2) + coords->border_width; + + if (coords->stacked) { + *x2 += STACKED_OFFSET; + *y2 += STACKED_OFFSET; + } +} + + +static void +request_redraw(GnomeCanvasItem* item, + const GanvBoxCoords* coords, + gboolean world) +{ + double x1, y1, x2, y2; + ganv_box_bounds_item(coords, &x1, &y1, &x2, &y2); + + if (!world) { + // Convert from parent-relative coordinates to world coordinates + gnome_canvas_item_i2w(item, &x1, &y1); + gnome_canvas_item_i2w(item, &x2, &y2); + } + + gnome_canvas_request_redraw(item->canvas, x1, y1, x2, y2); +} + +static void +coords_i2w(GnomeCanvasItem* item, GanvBoxCoords* coords) +{ + gnome_canvas_item_i2w(item, &coords->x1, &coords->y1); + gnome_canvas_item_i2w(item, &coords->x2, &coords->y2); +} + +static void +ganv_box_bounds(GnomeCanvasItem* item, + double* x1, double* y1, + double* x2, double* y2) +{ + // Note this will not be correct if children are outside the box bounds + GanvBox* box = GANV_BOX(item); + ganv_box_bounds_item(&box->coords, x1, y1, x2, y2); + gnome_canvas_item_i2w(item, x1, y1); + gnome_canvas_item_i2w(item, x2, y2); +} + +static void +ganv_box_update(GnomeCanvasItem* item, + double* affine, + ArtSVP* clip_path, + int flags) +{ + GanvBox* box = GANV_BOX(item); + box->coords.border_width = box->node.border_width; + + // Request redraw of old location + request_redraw(item, &box->old_coords, TRUE); + + GnomeCanvasItemClass* item_class = GNOME_CANVAS_ITEM_CLASS(parent_class); + item_class->update(item, affine, clip_path, flags); + + // Store old coordinates in world relative coordinates in case the + // group we are in moves between now and the next update + box->old_coords = box->coords; + coords_i2w(item, &box->old_coords); + + // Get bounding box + double x1, x2, y1, y2; + ganv_box_bounds(item, &x1, &y1, &x2, &y2); + + // Update item canvas coordinates + gnome_canvas_w2c_d(GNOME_CANVAS(item->canvas), x1, y1, &item->x1, &item->y1); + gnome_canvas_w2c_d(GNOME_CANVAS(item->canvas), x2, y2, &item->x2, &item->y2); + + // Request redraw of new location + request_redraw(item, &box->coords, FALSE); + +} + +static void +ganv_box_render(GnomeCanvasItem* item, + GnomeCanvasBuf* buf) +{ + // Not implemented +} + +static void +ganv_box_draw(GnomeCanvasItem* item, + GdkDrawable* drawable, + int cx, int cy, + int width, int height) +{ + GanvBox* me = GANV_BOX(item); + cairo_t* cr = gdk_cairo_create(drawable); + + double x1 = me->coords.x1; + double y1 = me->coords.y1; + double x2 = me->coords.x2; + double y2 = me->coords.y2; + gnome_canvas_item_i2w(item, &x1, &y1); + gnome_canvas_item_i2w(item, &x2, &y2); + + double dash_length, border_color, fill_color; + ganv_node_get_draw_properties( + &me->node, &dash_length, &border_color, &fill_color); + + double r, g, b, a; + + double degrees = M_PI / 180.0; + + for (int i = (me->coords.stacked ? 1 : 0); i >= 0; --i) { + const int x = cx - (STACKED_OFFSET * i); + const int y = cy - (STACKED_OFFSET * i); + + if (me->radius_tl == 0.0 && me->radius_tr == 0.0 + && me->radius_br == 0.0 && me->radius_bl == 0.0) { + // Simple rectangle + cairo_rectangle(cr, x1 - x, y1 - y, x2 - x1, y2 - y1); + } else { + // Rounded rectangle + cairo_new_sub_path(cr); + cairo_arc(cr, + x2 - x - me->radius_tr, + y1 - y + me->radius_tr, + me->radius_tr, -90 * degrees, 0 * degrees); + cairo_arc(cr, + x2 - x - me->radius_br, y2 - y - me->radius_br, + me->radius_br, 0 * degrees, 90 * degrees); + cairo_arc(cr, + x1 - x + me->radius_bl, y2 - y - me->radius_bl, + me->radius_bl, 90 * degrees, 180 * degrees); + cairo_arc(cr, + x1 - x + me->radius_tl, y1 - y + me->radius_tl, + me->radius_tl, 180 * degrees, 270 * degrees); + cairo_close_path(cr); + } + + // Fill + color_to_rgba(fill_color, &r, &g, &b, &a); + cairo_set_source_rgba(cr, r, g, b, a); + cairo_fill_preserve(cr); + + // Border + if (me->coords.border_width > 0.0) { + color_to_rgba(border_color, &r, &g, &b, &a); + cairo_set_source_rgba(cr, r, g, b, a); + cairo_set_line_width(cr, me->coords.border_width); + if (dash_length > 0) { + cairo_set_dash(cr, &dash_length, 1, me->node.dash_offset); + } + } + cairo_stroke(cr); + } + + GnomeCanvasItemClass* item_class = GNOME_CANVAS_ITEM_CLASS(parent_class); + item_class->draw(item, drawable, cx, cy, width, height); + + cairo_destroy(cr); +} + +static double +ganv_box_point(GnomeCanvasItem* item, + double x, double y, + int cx, int cy, + GnomeCanvasItem** actual_item) +{ + GanvBox* box = GANV_BOX(item); + + *actual_item = NULL; + + double x1, y1, x2, y2; + ganv_box_bounds_item(&box->coords, &x1, &y1, &x2, &y2); + + // Point is inside the box (distance 0) + if ((x >= x1) && (y >= y1) && (x <= x2) && (y <= y2)) { + GnomeCanvasItemClass* item_class = GNOME_CANVAS_ITEM_CLASS(parent_class); + double d = item_class->point(item, x, y, cx, cy, actual_item); + if (*actual_item) { + return d; + } else { + *actual_item = item; + return 0.0; + } + } + + // Point is outside the box + double dx, dy; + + // Find horizontal distance to nearest edge + if (x < x1) { + dx = x1 - x; + } else if (x > x2) { + dx = x - x2; + } else { + dx = 0.0; + } + + // Find vertical distance to nearest edge + if (y < y1) { + dy = y1 - y; + } else if (y > y2) { + dy = y - y2; + } else { + dy = 0.0; + } + + return sqrt((dx * dx) + (dy * dy)); +} + +static gboolean +ganv_box_is_within(const GanvNode* self, + double x1, + double y1, + double x2, + double y2) +{ + double bx1, by1, bx2, by2; + g_object_get(G_OBJECT(self), + "x1", &bx1, + "y1", &by1, + "x2", &bx2, + "y2", &by2, + NULL); + + gnome_canvas_item_i2w(GNOME_CANVAS_ITEM(self), &bx1, &by1); + gnome_canvas_item_i2w(GNOME_CANVAS_ITEM(self), &bx2, &by2); + + return ( bx1 >= x1 + && by2 >= y1 + && bx2 <= x2 + && by2 <= y2); +} + +static void +ganv_box_default_set_width(GanvBox* box, + double width) +{ + gnome_canvas_item_set(GNOME_CANVAS_ITEM(box), + "x2", ganv_box_get_x1(box) + width, + NULL); +} + +static void +ganv_box_default_set_height(GanvBox* box, + double height) +{ + gnome_canvas_item_set(GNOME_CANVAS_ITEM(box), + "y2", ganv_box_get_y1(box) + height, + NULL); +} + +static void +ganv_box_class_init(GanvBoxClass* class) +{ + GObjectClass* gobject_class = (GObjectClass*)class; + GtkObjectClass* object_class = (GtkObjectClass*)class; + GnomeCanvasItemClass* item_class = (GnomeCanvasItemClass*)class; + GanvNodeClass* node_class = (GanvNodeClass*)class; + + parent_class = GANV_NODE_CLASS(g_type_class_peek_parent(class)); + + gobject_class->set_property = ganv_box_set_property; + gobject_class->get_property = ganv_box_get_property; + + g_object_class_install_property( + gobject_class, PROP_X1, g_param_spec_double( + "x1", + _("x1"), + _("top left x coordinate"), + -G_MAXDOUBLE, G_MAXDOUBLE, + 0.0, + G_PARAM_READWRITE)); + + g_object_class_install_property( + gobject_class, PROP_Y1, g_param_spec_double( + "y1", + _("y1"), + _("top left y coordinate"), + -G_MAXDOUBLE, G_MAXDOUBLE, + 0.0, + G_PARAM_READWRITE)); + + g_object_class_install_property( + gobject_class, PROP_X2, g_param_spec_double( + "x2", + _("x2"), + _("bottom right x coordinate"), + -G_MAXDOUBLE, G_MAXDOUBLE, + 0.0, + G_PARAM_READWRITE)); + + g_object_class_install_property( + gobject_class, PROP_Y2, g_param_spec_double( + "y2", + _("y2"), + _("bottom right y coordinate"), + -G_MAXDOUBLE, G_MAXDOUBLE, + 0.0, + G_PARAM_READWRITE)); + + g_object_class_install_property( + gobject_class, PROP_RADIUS_TL, g_param_spec_double( + "radius-tl", + _("top left corner radius"), + _("top left corner radius"), + 0.0, G_MAXDOUBLE, + 0.0, + G_PARAM_READWRITE)); + + g_object_class_install_property( + gobject_class, PROP_RADIUS_TR, g_param_spec_double( + "radius-tr", + _("top right corner radius"), + _("top right corner radius"), + 0.0, G_MAXDOUBLE, + 0.0, + G_PARAM_READWRITE)); + + g_object_class_install_property( + gobject_class, PROP_RADIUS_BR, g_param_spec_double( + "radius-br", + _("bottom right corner radius"), + _("bottom right corner radius"), + 0.0, G_MAXDOUBLE, + 0.0, + G_PARAM_READWRITE)); + + g_object_class_install_property( + gobject_class, PROP_RADIUS_BL, g_param_spec_double( + "radius-bl", + _("bottom left corner radius"), + _("bottom left corner radius"), + 0.0, G_MAXDOUBLE, + 0.0, + G_PARAM_READWRITE)); + + g_object_class_install_property( + gobject_class, PROP_STACKED, g_param_spec_boolean( + "stacked", + _("stacked"), + _("Show the box with a stacked appearance."), + FALSE, + G_PARAM_READWRITE)); + + object_class->destroy = ganv_box_destroy; + + item_class->update = ganv_box_update; + item_class->bounds = ganv_box_bounds; + item_class->point = ganv_box_point; + item_class->render = ganv_box_render; + item_class->draw = ganv_box_draw; + + node_class->is_within = ganv_box_is_within; + + class->set_width = ganv_box_default_set_width; + class->set_height = ganv_box_default_set_height; +} + +void +ganv_box_normalize(GanvBox* box) +{ + const double x1 = box->coords.x1; + const double y1 = box->coords.y1; + const double x2 = box->coords.x2; + const double y2 = box->coords.y2; + + box->coords.x1 = MIN(x1, x2); + box->coords.y1 = MIN(y1, y2); + box->coords.x2 = MAX(x1, x2); + box->coords.y2 = MAX(y1, y2); +} |