summaryrefslogtreecommitdiffstats
path: root/src/circle.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/circle.c')
-rw-r--r--src/circle.c467
1 files changed, 467 insertions, 0 deletions
diff --git a/src/circle.c b/src/circle.c
new file mode 100644
index 0000000..a69c207
--- /dev/null
+++ b/src/circle.c
@@ -0,0 +1,467 @@
+/* This file is part of Ganv.
+ * Copyright 2007-2015 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 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 "ganv/canvas.h"
+#include "ganv/circle.h"
+
+#include "./color.h"
+#include "./boilerplate.h"
+#include "./gettext.h"
+#include "./ganv-private.h"
+
+G_DEFINE_TYPE_WITH_CODE(GanvCircle, ganv_circle, GANV_TYPE_NODE,
+ G_ADD_PRIVATE(GanvCircle))
+
+static GanvNodeClass* parent_class;
+
+enum {
+ PROP_0,
+ PROP_RADIUS,
+ PROP_RADIUS_EMS,
+ PROP_FIT_LABEL
+};
+
+static void
+ganv_circle_init(GanvCircle* circle)
+{
+ circle->impl = ganv_circle_get_instance_private(circle);
+
+ memset(&circle->impl->coords, '\0', sizeof(GanvCircleCoords));
+ circle->impl->coords.radius = 0.0;
+ circle->impl->coords.radius_ems = 1.0;
+ circle->impl->coords.width = 2.0;
+ circle->impl->old_coords = circle->impl->coords;
+ circle->impl->fit_label = TRUE;
+}
+
+static void
+ganv_circle_destroy(GtkObject* object)
+{
+ g_return_if_fail(object != NULL);
+ g_return_if_fail(GANV_IS_CIRCLE(object));
+
+ if (GTK_OBJECT_CLASS(parent_class)->destroy) {
+ (*GTK_OBJECT_CLASS(parent_class)->destroy)(object);
+ }
+}
+
+static void
+ganv_circle_set_property(GObject* object,
+ guint prop_id,
+ const GValue* value,
+ GParamSpec* pspec)
+{
+ g_return_if_fail(object != NULL);
+ g_return_if_fail(GANV_IS_CIRCLE(object));
+
+ GanvCircle* circle = GANV_CIRCLE(object);
+
+ switch (prop_id) {
+ SET_CASE(RADIUS, double, circle->impl->coords.radius);
+ SET_CASE(RADIUS_EMS, double, circle->impl->coords.radius_ems);
+ SET_CASE(FIT_LABEL, boolean, circle->impl->fit_label);
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+ break;
+ }
+
+ if (prop_id == PROP_RADIUS_EMS) {
+ ganv_circle_set_radius_ems(circle, circle->impl->coords.radius_ems);
+ }
+}
+
+static void
+ganv_circle_get_property(GObject* object,
+ guint prop_id,
+ GValue* value,
+ GParamSpec* pspec)
+{
+ g_return_if_fail(object != NULL);
+ g_return_if_fail(GANV_IS_CIRCLE(object));
+
+ GanvCircle* circle = GANV_CIRCLE(object);
+
+ switch (prop_id) {
+ GET_CASE(RADIUS, double, circle->impl->coords.radius);
+ GET_CASE(RADIUS_EMS, double, circle->impl->coords.radius_ems);
+ GET_CASE(FIT_LABEL, boolean, circle->impl->fit_label);
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+ganv_circle_resize(GanvNode* self)
+{
+ GanvNode* node = GANV_NODE(self);
+ GanvCircle* circle = GANV_CIRCLE(self);
+ GanvCanvas* canvas = GANV_CANVAS(GANV_ITEM(node)->impl->canvas);
+
+ if (node->impl->label) {
+ if (node->impl->label->impl->needs_layout) {
+ ganv_text_layout(node->impl->label);
+ }
+
+ const double label_w = node->impl->label->impl->coords.width;
+ const double label_h = node->impl->label->impl->coords.height;
+ if (circle->impl->fit_label) {
+ // Resize to fit text
+ const double radius = MAX(label_w, label_h) / 2.0 + 3.0;
+ if (radius != circle->impl->coords.radius) {
+ ganv_item_set(GANV_ITEM(self),
+ "radius", radius,
+ NULL);
+ }
+ }
+
+ // Center label
+ ganv_item_set(GANV_ITEM(node->impl->label),
+ "x", label_w / -2.0,
+ "y", label_h / -2.0,
+ NULL);
+ }
+
+ if (parent_class->resize) {
+ parent_class->resize(self);
+ }
+
+ ganv_canvas_for_each_edge_on(
+ canvas, node, (GanvEdgeFunc)ganv_edge_update_location, NULL);
+}
+
+static void
+ganv_circle_redraw_text(GanvNode* self)
+{
+ GanvCircle* circle = GANV_CIRCLE(self);
+ if (circle->impl->coords.radius_ems) {
+ ganv_circle_set_radius_ems(circle, circle->impl->coords.radius_ems);
+ }
+
+ if (parent_class->redraw_text) {
+ parent_class->redraw_text(self);
+ }
+}
+
+static gboolean
+ganv_circle_is_within(const GanvNode* self,
+ double x1,
+ double y1,
+ double x2,
+ double y2)
+{
+ const double x = GANV_ITEM(self)->impl->x;
+ const double y = GANV_ITEM(self)->impl->y;
+
+ return x >= x1
+ && x <= x2
+ && y >= y1
+ && y <= y2;
+}
+
+static void
+ganv_circle_vector(const GanvNode* self,
+ const GanvNode* other,
+ double* x,
+ double* y,
+ double* dx,
+ double* dy)
+{
+ GanvCircle* circle = GANV_CIRCLE(self);
+
+ const double cx = GANV_ITEM(self)->impl->x;
+ const double cy = GANV_ITEM(self)->impl->y;
+ const double other_x = GANV_ITEM(other)->impl->x;
+ const double other_y = GANV_ITEM(other)->impl->y;
+
+ const double border = circle->node.impl->border_width;
+ const double xdist = other_x - cx;
+ const double ydist = other_y - cy;
+ const double h = sqrt((xdist * xdist) + (ydist * ydist));
+ const double theta = asin(xdist / (h + DBL_EPSILON));
+ const double y_mod = (cy < other_y) ? 1 : -1;
+ const double ret_h = h - circle->impl->coords.radius - border / 2.0;
+ const double ret_x = other_x - sin(theta) * ret_h;
+ const double ret_y = other_y - cos(theta) * ret_h * y_mod;
+
+ *x = ret_x;
+ *y = ret_y;
+ *dx = 0.0;
+ *dy = 0.0;
+
+ ganv_item_i2w(GANV_ITEM(circle)->impl->parent, x, y);
+}
+
+static void
+request_redraw(GanvItem* item,
+ const GanvCircleCoords* coords,
+ gboolean world)
+{
+ const double w = coords->width;
+
+ double x1 = coords->x - coords->radius - w;
+ double y1 = coords->y - coords->radius - w;
+ double x2 = coords->x + coords->radius + w;
+ double y2 = coords->y + coords->radius + w;
+
+ if (!world) {
+ // Convert from parent-relative coordinates to world coordinates
+ ganv_item_i2w_pair(item, &x1, &y1, &x2, &y2);
+ }
+
+ ganv_canvas_request_redraw_w(item->impl->canvas, x1, y1, x2, y2);
+}
+
+static void
+coords_i2w(GanvItem* item, GanvCircleCoords* coords)
+{
+ ganv_item_i2w(item, &coords->x, &coords->y);
+}
+
+static void
+ganv_circle_bounds_item(GanvItem* item,
+ double* x1, double* y1,
+ double* x2, double* y2)
+{
+ const GanvCircle* circle = GANV_CIRCLE(item);
+ const GanvCircleCoords* coords = &circle->impl->coords;
+ *x1 = coords->x - coords->radius - coords->width;
+ *y1 = coords->y - coords->radius - coords->width;
+ *x2 = coords->x + coords->radius + coords->width;
+ *y2 = coords->y + coords->radius + coords->width;
+}
+
+static void
+ganv_circle_bounds(GanvItem* item,
+ double* x1, double* y1,
+ double* x2, double* y2)
+{
+ ganv_circle_bounds_item(item, x1, y1, x2, y2);
+}
+
+static void
+ganv_circle_update(GanvItem* item, int flags)
+{
+ GanvCircle* circle = GANV_CIRCLE(item);
+ GanvCirclePrivate* impl = circle->impl;
+ impl->coords.width = circle->node.impl->border_width;
+
+ // Request redraw of old location
+ request_redraw(item, &impl->old_coords, TRUE);
+
+ // Store old coordinates in world relative coordinates in case the
+ // group we are in moves between now and the next update
+ impl->old_coords = impl->coords;
+ coords_i2w(item, &impl->old_coords);
+
+ // Update world-relative bounding box
+ ganv_circle_bounds(item, &item->impl->x1, &item->impl->y1, &item->impl->x2, &item->impl->y2);
+ ganv_item_i2w_pair(item, &item->impl->x1, &item->impl->y1, &item->impl->x2, &item->impl->y2);
+
+ // Request redraw of new location
+ request_redraw(item, &impl->coords, FALSE);
+
+ GANV_ITEM_CLASS(parent_class)->update(item, flags);
+}
+
+static void
+ganv_circle_draw(GanvItem* item,
+ cairo_t* cr, double cx, double cy, double cw, double ch)
+{
+ GanvNode* node = GANV_NODE(item);
+ GanvCircle* circle = GANV_CIRCLE(item);
+ GanvCirclePrivate* impl = circle->impl;
+
+ double r, g, b, a;
+
+ double x = impl->coords.x;
+ double y = impl->coords.y;
+ ganv_item_i2w(item, &x, &y);
+
+ double dash_length, border_color, fill_color;
+ ganv_node_get_draw_properties(
+ &circle->node, &dash_length, &border_color, &fill_color);
+
+ // Fill
+ cairo_new_path(cr);
+ cairo_arc(cr,
+ x,
+ y,
+ impl->coords.radius + (impl->coords.width / 2.0),
+ 0, 2 * G_PI);
+ color_to_rgba(fill_color, &r, &g, &b, &a);
+ cairo_set_source_rgba(cr, r, g, b, a);
+ cairo_fill(cr);
+
+ // Border
+ cairo_arc(cr,
+ x,
+ y,
+ impl->coords.radius,
+ 0, 2 * G_PI);
+ color_to_rgba(border_color, &r, &g, &b, &a);
+ cairo_set_source_rgba(cr, r, g, b, a);
+ cairo_set_line_width(cr, impl->coords.width);
+ if (dash_length > 0) {
+ cairo_set_dash(cr, &dash_length, 1, circle->node.impl->dash_offset);
+ } else {
+ cairo_set_dash(cr, &dash_length, 0, 0);
+ }
+ cairo_stroke(cr);
+
+ // Draw label
+ if (node->impl->label) {
+ GanvItem* label_item = GANV_ITEM(node->impl->label);
+
+ if (label_item->object.flags & GANV_ITEM_VISIBLE) {
+ GANV_ITEM_GET_CLASS(label_item)->draw(
+ label_item, cr, cx, cy, cw, ch);
+ }
+ }
+}
+
+static double
+ganv_circle_point(GanvItem* item, double x, double y, GanvItem** actual_item)
+{
+ const GanvCircle* circle = GANV_CIRCLE(item);
+ const GanvCircleCoords* coords = &circle->impl->coords;
+
+ *actual_item = item;
+
+ const double dx = fabs(x - coords->x);
+ const double dy = fabs(y - coords->y);
+ const double d = sqrt((dx * dx) + (dy * dy));
+
+ if (d <= coords->radius + coords->width) {
+ // Point is inside the circle
+ return 0.0;
+ } else {
+ // Distance from the edge of the circle
+ return d - (coords->radius + coords->width);
+ }
+}
+
+static void
+ganv_circle_class_init(GanvCircleClass* klass)
+{
+ GObjectClass* gobject_class = (GObjectClass*)klass;
+ GtkObjectClass* object_class = (GtkObjectClass*)klass;
+ GanvItemClass* item_class = (GanvItemClass*)klass;
+ GanvNodeClass* node_class = (GanvNodeClass*)klass;
+
+ parent_class = GANV_NODE_CLASS(g_type_class_peek_parent(klass));
+
+ gobject_class->set_property = ganv_circle_set_property;
+ gobject_class->get_property = ganv_circle_get_property;
+
+ g_object_class_install_property(
+ gobject_class, PROP_RADIUS, g_param_spec_double(
+ "radius",
+ _("Radius"),
+ _("The radius of the circle."),
+ 0, G_MAXDOUBLE,
+ 0.0,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property(
+ gobject_class, PROP_RADIUS_EMS, g_param_spec_double(
+ "radius-ems",
+ _("Radius in ems"),
+ _("The radius of the circle in ems."),
+ 0, G_MAXDOUBLE,
+ 1.0,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property(
+ gobject_class, PROP_FIT_LABEL, g_param_spec_boolean(
+ "fit-label",
+ _("Fit label"),
+ _("If true, expand circle to fit its label"),
+ TRUE,
+ (GParamFlags)G_PARAM_READWRITE));
+
+ object_class->destroy = ganv_circle_destroy;
+
+ node_class->resize = ganv_circle_resize;
+ node_class->is_within = ganv_circle_is_within;
+ node_class->tail_vector = ganv_circle_vector;
+ node_class->head_vector = ganv_circle_vector;
+ node_class->redraw_text = ganv_circle_redraw_text;
+
+ item_class->update = ganv_circle_update;
+ item_class->bounds = ganv_circle_bounds;
+ item_class->point = ganv_circle_point;
+ item_class->draw = ganv_circle_draw;
+}
+
+GanvCircle*
+ganv_circle_new(GanvCanvas* canvas,
+ const char* first_property_name, ...)
+{
+ GanvCircle* circle = GANV_CIRCLE(
+ g_object_new(ganv_circle_get_type(), "canvas", canvas, NULL));
+
+ va_list args;
+ va_start(args, first_property_name);
+ g_object_set_valist(G_OBJECT(circle), first_property_name, args);
+ va_end(args);
+
+ return circle;
+}
+
+double
+ganv_circle_get_radius(const GanvCircle* circle)
+{
+ return circle->impl->coords.radius;
+}
+
+void
+ganv_circle_set_radius(GanvCircle* circle, double radius)
+{
+ circle->impl->coords.radius = radius;
+ ganv_item_request_update(GANV_ITEM(circle));
+}
+
+double
+ganv_circle_get_radius_ems(const GanvCircle* circle)
+{
+ return circle->impl->coords.radius_ems;
+}
+
+void
+ganv_circle_set_radius_ems(GanvCircle* circle, double ems)
+{
+ GanvCanvas* canvas = GANV_CANVAS(GANV_ITEM(circle)->impl->canvas);
+ const double points = ganv_canvas_get_font_size(canvas);
+ circle->impl->coords.radius_ems = ems;
+ circle->impl->coords.radius = points * ems;
+ ganv_item_request_update(GANV_ITEM(circle));
+}
+
+gboolean
+ganv_circle_get_fit_label(const GanvCircle* circle)
+{
+ return circle->impl->fit_label;
+}
+
+void
+ganv_circle_set_fit_label(GanvCircle* circle, gboolean fit_label)
+{
+ circle->impl->fit_label = fit_label;
+ ganv_item_request_update(GANV_ITEM(circle));
+}