1 /* HSV color selector for GTK+
3 * Copyright (C) 1999 The Free Software Foundation
5 * Authors: Simon Budig <Simon.Budig@unix-ag.org> (original code)
6 * Federico Mena-Quintero <federico@gimp.org> (cleanup for GTK+)
7 * Jonathan Blandford <jrb@redhat.com> (cleanup for GTK+)
9 * This library is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU Lesser General Public
11 * License as published by the Free Software Foundation; either
12 * version 2 of the License, or (at your option) any later version.
14 * This library is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 * Lesser General Public License for more details.
19 * You should have received a copy of the GNU Lesser General Public
20 * License along with this library; if not, write to the
21 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
22 * Boston, MA 02111-1307, USA.
26 * Modified by the GTK+ Team and others 1997-2000. See the AUTHORS
27 * file for a list of people on the GTK+ Team. See the ChangeLog
28 * files for a list of changes. These files are distributed with
29 * GTK+ at ftp://ftp.gtk.org/pub/gtk/.
38 #include "gtkbindings.h"
39 #include "gtkmarshalers.h"
40 #include "gtktypebuiltins.h"
46 * @Short_description: A 'color wheel' widget
48 * @See_also: #GtkColorSelection, #GtkColorSelectionDialog
50 * #GtkHSV is the 'color wheel' part of a complete color selector widget.
51 * It allows to select a color by determining its HSV components in an
52 * intuitive way. Moving the selection around the outer ring changes the hue,
53 * and moving the selection point inside the inner triangle changes value and
58 /* Default width/height */
59 #define DEFAULT_SIZE 100
61 /* Default ring width */
62 #define DEFAULT_RING_WIDTH 10
72 /* Private part of the GtkHSV structure */
80 /* Size and ring width */
84 /* Window for capturing events */
90 guint focus_on_ring : 1;
102 static void gtk_hsv_destroy (GtkWidget *widget);
103 static void gtk_hsv_realize (GtkWidget *widget);
104 static void gtk_hsv_unrealize (GtkWidget *widget);
105 static void gtk_hsv_get_preferred_width (GtkWidget *widget,
108 static void gtk_hsv_get_preferred_height (GtkWidget *widget,
111 static void gtk_hsv_size_allocate (GtkWidget *widget,
112 GtkAllocation *allocation);
113 static gboolean gtk_hsv_button_press (GtkWidget *widget,
114 GdkEventButton *event);
115 static gboolean gtk_hsv_button_release (GtkWidget *widget,
116 GdkEventButton *event);
117 static gboolean gtk_hsv_motion (GtkWidget *widget,
118 GdkEventMotion *event);
119 static gboolean gtk_hsv_draw (GtkWidget *widget,
121 static gboolean gtk_hsv_grab_broken (GtkWidget *widget,
122 GdkEventGrabBroken *event);
123 static gboolean gtk_hsv_focus (GtkWidget *widget,
124 GtkDirectionType direction);
125 static void gtk_hsv_move (GtkHSV *hsv,
126 GtkDirectionType dir);
128 static guint hsv_signals[LAST_SIGNAL];
130 G_DEFINE_TYPE (GtkHSV, gtk_hsv, GTK_TYPE_WIDGET)
132 /* Class initialization function for the HSV color selector */
134 gtk_hsv_class_init (GtkHSVClass *class)
136 GObjectClass *gobject_class;
137 GtkWidgetClass *widget_class;
138 GtkHSVClass *hsv_class;
139 GtkBindingSet *binding_set;
141 gobject_class = (GObjectClass *) class;
142 widget_class = (GtkWidgetClass *) class;
143 hsv_class = GTK_HSV_CLASS (class);
145 widget_class->destroy = gtk_hsv_destroy;
146 widget_class->realize = gtk_hsv_realize;
147 widget_class->unrealize = gtk_hsv_unrealize;
148 widget_class->get_preferred_width = gtk_hsv_get_preferred_width;
149 widget_class->get_preferred_height = gtk_hsv_get_preferred_height;
150 widget_class->size_allocate = gtk_hsv_size_allocate;
151 widget_class->button_press_event = gtk_hsv_button_press;
152 widget_class->button_release_event = gtk_hsv_button_release;
153 widget_class->motion_notify_event = gtk_hsv_motion;
154 widget_class->draw = gtk_hsv_draw;
155 widget_class->focus = gtk_hsv_focus;
156 widget_class->grab_broken_event = gtk_hsv_grab_broken;
158 gtk_widget_class_set_accessible_role (widget_class, ATK_ROLE_COLOR_CHOOSER);
160 hsv_class->move = gtk_hsv_move;
162 hsv_signals[CHANGED] =
163 g_signal_new (I_("changed"),
164 G_OBJECT_CLASS_TYPE (gobject_class),
166 G_STRUCT_OFFSET (GtkHSVClass, changed),
168 _gtk_marshal_VOID__VOID,
172 g_signal_new (I_("move"),
173 G_OBJECT_CLASS_TYPE (gobject_class),
174 G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
175 G_STRUCT_OFFSET (GtkHSVClass, move),
177 _gtk_marshal_VOID__ENUM,
179 GTK_TYPE_DIRECTION_TYPE);
181 binding_set = gtk_binding_set_by_class (class);
183 gtk_binding_entry_add_signal (binding_set, GDK_KEY_Up, 0,
185 G_TYPE_ENUM, GTK_DIR_UP);
186 gtk_binding_entry_add_signal (binding_set, GDK_KEY_KP_Up, 0,
188 G_TYPE_ENUM, GTK_DIR_UP);
189 gtk_binding_entry_add_signal (binding_set, GDK_KEY_Down, 0,
191 G_TYPE_ENUM, GTK_DIR_DOWN);
192 gtk_binding_entry_add_signal (binding_set, GDK_KEY_KP_Down, 0,
194 G_TYPE_ENUM, GTK_DIR_DOWN);
195 gtk_binding_entry_add_signal (binding_set, GDK_KEY_Right, 0,
197 G_TYPE_ENUM, GTK_DIR_RIGHT);
198 gtk_binding_entry_add_signal (binding_set, GDK_KEY_KP_Right, 0,
200 G_TYPE_ENUM, GTK_DIR_RIGHT);
201 gtk_binding_entry_add_signal (binding_set, GDK_KEY_Left, 0,
203 G_TYPE_ENUM, GTK_DIR_LEFT);
204 gtk_binding_entry_add_signal (binding_set, GDK_KEY_KP_Left, 0,
206 G_TYPE_ENUM, GTK_DIR_LEFT);
208 g_type_class_add_private (gobject_class, sizeof (GtkHSVPrivate));
212 gtk_hsv_init (GtkHSV *hsv)
216 priv = G_TYPE_INSTANCE_GET_PRIVATE (hsv, GTK_TYPE_HSV, GtkHSVPrivate);
220 gtk_widget_set_has_window (GTK_WIDGET (hsv), FALSE);
221 gtk_widget_set_can_focus (GTK_WIDGET (hsv), TRUE);
227 priv->size = DEFAULT_SIZE;
228 priv->ring_width = DEFAULT_RING_WIDTH;
232 gtk_hsv_destroy (GtkWidget *widget)
234 GTK_WIDGET_CLASS (gtk_hsv_parent_class)->destroy (widget);
238 gtk_hsv_realize (GtkWidget *widget)
240 GtkHSV *hsv = GTK_HSV (widget);
241 GtkHSVPrivate *priv = hsv->priv;
242 GtkAllocation allocation;
243 GdkWindow *parent_window;
247 gtk_widget_set_realized (widget, TRUE);
249 gtk_widget_get_allocation (widget, &allocation);
251 attr.window_type = GDK_WINDOW_CHILD;
252 attr.x = allocation.x;
253 attr.y = allocation.y;
254 attr.width = allocation.width;
255 attr.height = allocation.height;
256 attr.wclass = GDK_INPUT_ONLY;
257 attr.event_mask = gtk_widget_get_events (widget);
258 attr.event_mask |= (GDK_KEY_PRESS_MASK
259 | GDK_BUTTON_PRESS_MASK
260 | GDK_BUTTON_RELEASE_MASK
261 | GDK_POINTER_MOTION_MASK
262 | GDK_ENTER_NOTIFY_MASK
263 | GDK_LEAVE_NOTIFY_MASK);
264 attr_mask = GDK_WA_X | GDK_WA_Y;
266 parent_window = gtk_widget_get_parent_window (widget);
267 gtk_widget_set_window (widget, parent_window);
268 g_object_ref (parent_window);
270 priv->window = gdk_window_new (parent_window, &attr, attr_mask);
271 gdk_window_set_user_data (priv->window, hsv);
272 gdk_window_show (priv->window);
276 gtk_hsv_unrealize (GtkWidget *widget)
278 GtkHSV *hsv = GTK_HSV (widget);
279 GtkHSVPrivate *priv = hsv->priv;
281 gdk_window_set_user_data (priv->window, NULL);
282 gdk_window_destroy (priv->window);
285 GTK_WIDGET_CLASS (gtk_hsv_parent_class)->unrealize (widget);
289 gtk_hsv_get_preferred_width (GtkWidget *widget,
293 GtkHSV *hsv = GTK_HSV (widget);
294 GtkHSVPrivate *priv = hsv->priv;
298 gtk_widget_style_get (widget,
299 "focus-line-width", &focus_width,
300 "focus-padding", &focus_pad,
303 *minimum = priv->size + 2 * (focus_width + focus_pad);
304 *natural = priv->size + 2 * (focus_width + focus_pad);
308 gtk_hsv_get_preferred_height (GtkWidget *widget,
312 GtkHSV *hsv = GTK_HSV (widget);
313 GtkHSVPrivate *priv = hsv->priv;
317 gtk_widget_style_get (widget,
318 "focus-line-width", &focus_width,
319 "focus-padding", &focus_pad,
322 *minimum = priv->size + 2 * (focus_width + focus_pad);
323 *natural = priv->size + 2 * (focus_width + focus_pad);
327 gtk_hsv_size_allocate (GtkWidget *widget,
328 GtkAllocation *allocation)
330 GtkHSV *hsv = GTK_HSV (widget);
331 GtkHSVPrivate *priv = hsv->priv;
333 gtk_widget_set_allocation (widget, allocation);
335 if (gtk_widget_get_realized (widget))
336 gdk_window_move_resize (priv->window,
344 /* Utility functions */
346 #define INTENSITY(r, g, b) ((r) * 0.30 + (g) * 0.59 + (b) * 0.11)
348 /* Converts from HSV to RGB */
350 hsv_to_rgb (gdouble *h,
354 gdouble hue, saturation, value;
373 p = value * (1.0 - saturation);
374 q = value * (1.0 - saturation * f);
375 t = value * (1.0 - saturation * (1.0 - f));
416 g_assert_not_reached ();
421 /* Converts from RGB to HSV */
423 rgb_to_hsv (gdouble *r,
427 gdouble red, green, blue;
466 s = (max - min) / max;
477 h = (green - blue) / delta;
478 else if (green == max)
479 h = 2 + (blue - red) / delta;
480 else if (blue == max)
481 h = 4 + (red - green) / delta;
496 /* Computes the vertices of the saturation/value triangle */
498 compute_triangle (GtkHSV *hsv,
506 GtkHSVPrivate *priv = hsv->priv;
507 GtkWidget *widget = GTK_WIDGET (hsv);
510 gdouble inner, outer;
513 center_x = gtk_widget_get_allocated_width (widget) / 2.0;
514 center_y = gtk_widget_get_allocated_height (widget) / 2.0;
515 outer = priv->size / 2.0;
516 inner = outer - priv->ring_width;
517 angle = priv->h * 2.0 * G_PI;
519 *hx = floor (center_x + cos (angle) * inner + 0.5);
520 *hy = floor (center_y - sin (angle) * inner + 0.5);
521 *sx = floor (center_x + cos (angle + 2.0 * G_PI / 3.0) * inner + 0.5);
522 *sy = floor (center_y - sin (angle + 2.0 * G_PI / 3.0) * inner + 0.5);
523 *vx = floor (center_x + cos (angle + 4.0 * G_PI / 3.0) * inner + 0.5);
524 *vy = floor (center_y - sin (angle + 4.0 * G_PI / 3.0) * inner + 0.5);
527 /* Computes whether a point is inside the hue ring */
529 is_in_ring (GtkHSV *hsv,
533 GtkHSVPrivate *priv = hsv->priv;
534 GtkWidget *widget = GTK_WIDGET (hsv);
535 gdouble dx, dy, dist;
538 gdouble inner, outer;
540 center_x = gtk_widget_get_allocated_width (widget) / 2.0;
541 center_y = gtk_widget_get_allocated_height (widget) / 2.0;
542 outer = priv->size / 2.0;
543 inner = outer - priv->ring_width;
547 dist = dx * dx + dy * dy;
549 return (dist >= inner * inner && dist <= outer * outer);
552 /* Computes a saturation/value pair based on the mouse coordinates */
554 compute_sv (GtkHSV *hsv,
560 GtkWidget *widget = GTK_WIDGET (hsv);
561 int ihx, ihy, isx, isy, ivx, ivy;
562 double hx, hy, sx, sy, vx, vy;
566 compute_triangle (hsv, &ihx, &ihy, &isx, &isy, &ivx, &ivy);
567 center_x = gtk_widget_get_allocated_width (widget) / 2.0;
568 center_y = gtk_widget_get_allocated_height (widget) / 2.0;
578 if (vx * (x - sx) + vy * (y - sy) < 0.0)
581 *v = (((x - sx) * (hx - sx) + (y - sy) * (hy-sy))
582 / ((hx - sx) * (hx - sx) + (hy - sy) * (hy - sy)));
589 else if (hx * (x - sx) + hy * (y - sy) < 0.0)
592 *v = (((x - sx) * (vx - sx) + (y - sy) * (vy - sy))
593 / ((vx - sx) * (vx - sx) + (vy - sy) * (vy - sy)));
600 else if (sx * (x - hx) + sy * (y - hy) < 0.0)
603 *s = (((x - vx) * (hx - vx) + (y - vy) * (hy - vy)) /
604 ((hx - vx) * (hx - vx) + (hy - vy) * (hy - vy)));
613 *v = (((x - sx) * (hy - vy) - (y - sy) * (hx - vx))
614 / ((vx - sx) * (hy - vy) - (vy - sy) * (hx - vx)));
626 if (fabs (hy - vy) < fabs (hx - vx))
627 *s = (x - sx - *v * (vx - sx)) / (*v * (hx - vx));
629 *s = (y - sy - *v * (vy - sy)) / (*v * (hy - vy));
639 /* Computes whether a point is inside the saturation/value triangle */
641 is_in_triangle (GtkHSV *hsv,
645 int hx, hy, sx, sy, vx, vy;
648 compute_triangle (hsv, &hx, &hy, &sx, &sy, &vx, &vy);
650 det = (vx - sx) * (hy - sy) - (vy - sy) * (hx - sx);
652 s = ((x - sx) * (hy - sy) - (y - sy) * (hx - sx)) / det;
653 v = ((vx - sx) * (y - sy) - (vy - sy) * (x - sx)) / det;
655 return (s >= 0.0 && v >= 0.0 && s + v <= 1.0);
658 /* Computes a value based on the mouse coordinates */
660 compute_v (GtkHSV *hsv,
664 GtkWidget *widget = GTK_WIDGET (hsv);
670 center_x = gtk_widget_get_allocated_width (widget) / 2.0;
671 center_y = gtk_widget_get_allocated_height (widget) / 2.0;
675 angle = atan2 (dy, dx);
679 return angle / (2.0 * G_PI);
685 set_cross_grab (GtkHSV *hsv,
689 GtkHSVPrivate *priv = hsv->priv;
692 cursor = gdk_cursor_new_for_display (gtk_widget_get_display (GTK_WIDGET (hsv)),
694 gdk_device_grab (device,
698 GDK_POINTER_MOTION_MASK
699 | GDK_POINTER_MOTION_HINT_MASK
700 | GDK_BUTTON_RELEASE_MASK,
703 g_object_unref (cursor);
707 gtk_hsv_grab_broken (GtkWidget *widget,
708 GdkEventGrabBroken *event)
710 GtkHSV *hsv = GTK_HSV (widget);
711 GtkHSVPrivate *priv = hsv->priv;
713 priv->mode = DRAG_NONE;
719 gtk_hsv_button_press (GtkWidget *widget,
720 GdkEventButton *event)
722 GtkHSV *hsv = GTK_HSV (widget);
723 GtkHSVPrivate *priv = hsv->priv;
726 if (priv->mode != DRAG_NONE || event->button != GDK_BUTTON_PRIMARY)
732 if (is_in_ring (hsv, x, y))
735 set_cross_grab (hsv, gdk_event_get_device ((GdkEvent *) event), event->time);
737 gtk_hsv_set_color (hsv,
738 compute_v (hsv, x, y),
742 gtk_widget_grab_focus (widget);
743 priv->focus_on_ring = TRUE;
748 if (is_in_triangle (hsv, x, y))
752 priv->mode = DRAG_SV;
753 set_cross_grab (hsv, gdk_event_get_device ((GdkEvent *) event), event->time);
755 compute_sv (hsv, x, y, &s, &v);
756 gtk_hsv_set_color (hsv, priv->h, s, v);
758 gtk_widget_grab_focus (widget);
759 priv->focus_on_ring = FALSE;
768 gtk_hsv_button_release (GtkWidget *widget,
769 GdkEventButton *event)
771 GtkHSV *hsv = GTK_HSV (widget);
772 GtkHSVPrivate *priv = hsv->priv;
776 if (priv->mode == DRAG_NONE || event->button != GDK_BUTTON_PRIMARY)
779 /* Set the drag mode to DRAG_NONE so that signal handlers for "catched"
780 * can see that this is the final color state.
783 priv->mode = DRAG_NONE;
790 gtk_hsv_set_color (hsv, compute_v (hsv, x, y), priv->s, priv->v);
792 else if (mode == DRAG_SV)
796 compute_sv (hsv, x, y, &s, &v);
797 gtk_hsv_set_color (hsv, priv->h, s, v);
801 g_assert_not_reached ();
804 gdk_device_ungrab (gdk_event_get_device ((GdkEvent *) event), event->time);
810 gtk_hsv_motion (GtkWidget *widget,
811 GdkEventMotion *event)
813 GtkHSV *hsv = GTK_HSV (widget);
814 GtkHSVPrivate *priv = hsv->priv;
817 if (priv->mode == DRAG_NONE)
820 gdk_event_request_motions (event);
824 if (priv->mode == DRAG_H)
826 gtk_hsv_set_color (hsv, compute_v (hsv, x, y), priv->s, priv->v);
829 else if (priv->mode == DRAG_SV)
833 compute_sv (hsv, x, y, &s, &v);
834 gtk_hsv_set_color (hsv, priv->h, s, v);
838 g_assert_not_reached ();
846 /* Paints the hue ring */
848 paint_ring (GtkHSV *hsv,
851 GtkHSVPrivate *priv = hsv->priv;
852 GtkWidget *widget = GTK_WIDGET (hsv);
853 int xx, yy, width, height;
854 gdouble dx, dy, dist;
857 gdouble inner, outer;
862 cairo_surface_t *source;
866 width = gtk_widget_get_allocated_width (widget);
867 height = gtk_widget_get_allocated_height (widget);
869 center_x = width / 2.0;
870 center_y = height / 2.0;
872 outer = priv->size / 2.0;
873 inner = outer - priv->ring_width;
875 /* Create an image initialized with the ring colors */
877 stride = cairo_format_stride_for_width (CAIRO_FORMAT_RGB24, width);
878 buf = g_new (guint32, height * stride / 4);
880 for (yy = 0; yy < height; yy++)
882 p = buf + yy * width;
884 dy = -(yy - center_y);
886 for (xx = 0; xx < width; xx++)
890 dist = dx * dx + dy * dy;
891 if (dist < ((inner-1) * (inner-1)) || dist > ((outer+1) * (outer+1)))
897 angle = atan2 (dy, dx);
901 hue = angle / (2.0 * G_PI);
906 hsv_to_rgb (&r, &g, &b);
908 *p++ = (((int)floor (r * 255 + 0.5) << 16) |
909 ((int)floor (g * 255 + 0.5) << 8) |
910 (int)floor (b * 255 + 0.5));
914 source = cairo_image_surface_create_for_data ((unsigned char *)buf,
916 width, height, stride);
918 /* Now draw the value marker onto the source image, so that it
919 * will get properly clipped at the edges of the ring
921 source_cr = cairo_create (source);
926 hsv_to_rgb (&r, &g, &b);
928 if (INTENSITY (r, g, b) > 0.5)
929 cairo_set_source_rgb (source_cr, 0., 0., 0.);
931 cairo_set_source_rgb (source_cr, 1., 1., 1.);
933 cairo_move_to (source_cr, center_x, center_y);
934 cairo_line_to (source_cr,
935 center_x + cos (priv->h * 2.0 * G_PI) * priv->size / 2,
936 center_y - sin (priv->h * 2.0 * G_PI) * priv->size / 2);
937 cairo_stroke (source_cr);
938 cairo_destroy (source_cr);
940 /* Draw the ring using the source image */
944 cairo_set_source_surface (cr, source, 0, 0);
945 cairo_surface_destroy (source);
947 cairo_set_line_width (cr, priv->ring_width);
951 priv->size / 2. - priv->ring_width / 2.,
960 /* Converts an HSV triplet to an integer RGB triplet */
962 get_color (gdouble h,
969 hsv_to_rgb (&h, &s, &v);
971 *r = floor (h * 255 + 0.5);
972 *g = floor (s * 255 + 0.5);
973 *b = floor (v * 255 + 0.5);
976 #define SWAP(a, b, t) ((t) = (a), (a) = (b), (b) = (t))
978 #define LERP(a, b, v1, v2, i) (((v2) - (v1) != 0) \
979 ? ((a) + ((b) - (a)) * ((i) - (v1)) / ((v2) - (v1))) \
982 /* Number of pixels we extend out from the edges when creating
983 * color source to avoid artifacts
987 /* Paints the HSV triangle */
989 paint_triangle (GtkHSV *hsv,
993 GtkHSVPrivate *priv = hsv->priv;
994 GtkWidget *widget = GTK_WIDGET (hsv);
995 gint hx, hy, sx, sy, vx, vy; /* HSV vertices */
996 gint x1, y1, r1, g1, b1; /* First vertex in scanline order */
997 gint x2, y2, r2, g2, b2; /* Second vertex */
998 gint x3, y3, r3, g3, b3; /* Third vertex */
1000 guint32 *buf, *p, c;
1001 gint xl, xr, rl, rr, gl, gr, bl, br; /* Scanline data */
1003 gint x_interp, y_interp;
1004 gint x_start, x_end;
1005 cairo_surface_t *source;
1009 GtkStyleContext *context;
1012 width = gtk_widget_get_allocated_width (widget);
1013 height = gtk_widget_get_allocated_height (widget);
1014 /* Compute triangle's vertices */
1016 compute_triangle (hsv, &hx, &hy, &sx, &sy, &vx, &vy);
1020 get_color (priv->h, 1.0, 1.0, &r1, &g1, &b1);
1024 get_color (priv->h, 1.0, 0.0, &r2, &g2, &b2);
1028 get_color (priv->h, 0.0, 1.0, &r3, &g3, &b3);
1057 /* Shade the triangle */
1059 stride = cairo_format_stride_for_width (CAIRO_FORMAT_RGB24, width);
1060 buf = g_new (guint32, height * stride / 4);
1062 for (yy = 0; yy < height; yy++)
1064 p = buf + yy * width;
1066 if (yy >= y1 - PAD && yy < y3 + PAD) {
1067 y_interp = CLAMP (yy, y1, y3);
1071 xl = LERP (x1, x2, y1, y2, y_interp);
1073 rl = LERP (r1, r2, y1, y2, y_interp);
1074 gl = LERP (g1, g2, y1, y2, y_interp);
1075 bl = LERP (b1, b2, y1, y2, y_interp);
1079 xl = LERP (x2, x3, y2, y3, y_interp);
1081 rl = LERP (r2, r3, y2, y3, y_interp);
1082 gl = LERP (g2, g3, y2, y3, y_interp);
1083 bl = LERP (b2, b3, y2, y3, y_interp);
1086 xr = LERP (x1, x3, y1, y3, y_interp);
1088 rr = LERP (r1, r3, y1, y3, y_interp);
1089 gr = LERP (g1, g3, y1, y3, y_interp);
1090 br = LERP (b1, b3, y1, y3, y_interp);
1100 x_start = MAX (xl - PAD, 0);
1101 x_end = MIN (xr + PAD, width);
1102 x_start = MIN (x_start, x_end);
1104 c = (rl << 16) | (gl << 8) | bl;
1106 for (xx = 0; xx < x_start; xx++)
1109 for (; xx < x_end; xx++)
1111 x_interp = CLAMP (xx, xl, xr);
1113 *p++ = ((LERP (rl, rr, xl, xr, x_interp) << 16) |
1114 (LERP (gl, gr, xl, xr, x_interp) << 8) |
1115 LERP (bl, br, xl, xr, x_interp));
1118 c = (rr << 16) | (gr << 8) | br;
1120 for (; xx < width; xx++)
1125 source = cairo_image_surface_create_for_data ((unsigned char *)buf,
1127 width, height, stride);
1129 /* Draw a triangle with the image as a source */
1131 cairo_set_source_surface (cr, source, 0, 0);
1132 cairo_surface_destroy (source);
1134 cairo_move_to (cr, x1, y1);
1135 cairo_line_to (cr, x2, y2);
1136 cairo_line_to (cr, x3, y3);
1137 cairo_close_path (cr);
1142 /* Draw value marker */
1144 xx = floor (sx + (vx - sx) * priv->v + (hx - vx) * priv->s * priv->v + 0.5);
1145 yy = floor (sy + (vy - sy) * priv->v + (hy - vy) * priv->s * priv->v + 0.5);
1150 hsv_to_rgb (&r, &g, &b);
1152 context = gtk_widget_get_style_context (widget);
1154 gtk_style_context_save (context);
1156 if (INTENSITY (r, g, b) > 0.5)
1158 gtk_style_context_add_class (context, "light-area-focus");
1159 cairo_set_source_rgb (cr, 0., 0., 0.);
1163 gtk_style_context_add_class (context, "dark-area-focus");
1164 cairo_set_source_rgb (cr, 1., 1., 1.);
1168 #define FOCUS_RADIUS 6
1170 cairo_new_path (cr);
1171 cairo_arc (cr, xx, yy, RADIUS, 0, 2 * G_PI);
1174 /* Draw focus outline */
1176 if (draw_focus && !priv->focus_on_ring)
1181 gtk_widget_style_get (widget,
1182 "focus-line-width", &focus_width,
1183 "focus-padding", &focus_pad,
1186 gtk_render_focus (context, cr,
1187 xx - FOCUS_RADIUS - focus_width - focus_pad,
1188 yy - FOCUS_RADIUS - focus_width - focus_pad,
1189 2 * (FOCUS_RADIUS + focus_width + focus_pad),
1190 2 * (FOCUS_RADIUS + focus_width + focus_pad));
1193 gtk_style_context_restore (context);
1196 /* Paints the contents of the HSV color selector */
1198 gtk_hsv_draw (GtkWidget *widget,
1201 GtkHSV *hsv = GTK_HSV (widget);
1202 GtkHSVPrivate *priv = hsv->priv;
1203 gboolean draw_focus;
1205 draw_focus = gtk_widget_has_visible_focus (widget);
1207 paint_ring (hsv, cr);
1208 paint_triangle (hsv, cr, draw_focus);
1211 if (draw_focus && priv->focus_on_ring)
1213 GtkStyleContext *context;
1215 context = gtk_widget_get_style_context (widget);
1217 gtk_render_focus (context, cr, 0, 0,
1218 gtk_widget_get_allocated_width (widget),
1219 gtk_widget_get_allocated_height (widget));
1226 gtk_hsv_focus (GtkWidget *widget,
1227 GtkDirectionType dir)
1229 GtkHSV *hsv = GTK_HSV (widget);
1230 GtkHSVPrivate *priv = hsv->priv;
1232 if (!gtk_widget_has_focus (widget))
1234 if (dir == GTK_DIR_TAB_BACKWARD)
1235 priv->focus_on_ring = FALSE;
1237 priv->focus_on_ring = TRUE;
1239 gtk_widget_grab_focus (GTK_WIDGET (hsv));
1246 if (priv->focus_on_ring)
1249 priv->focus_on_ring = TRUE;
1253 if (priv->focus_on_ring)
1254 priv->focus_on_ring = FALSE;
1260 case GTK_DIR_TAB_BACKWARD:
1261 if (priv->focus_on_ring)
1264 priv->focus_on_ring = TRUE;
1268 case GTK_DIR_TAB_FORWARD:
1269 if (priv->focus_on_ring)
1270 priv->focus_on_ring = FALSE;
1276 gtk_widget_queue_draw (GTK_WIDGET (hsv));
1284 * Creates a new HSV color selector.
1286 * Return value: A newly-created HSV color selector.
1293 return g_object_new (GTK_TYPE_HSV, NULL);
1297 * gtk_hsv_set_color:
1298 * @hsv: An HSV color selector
1303 * Sets the current color in an HSV color selector.
1304 * Color component values must be in the [0.0, 1.0] range.
1309 gtk_hsv_set_color (GtkHSV *hsv,
1314 GtkHSVPrivate *priv;
1316 g_return_if_fail (GTK_IS_HSV (hsv));
1317 g_return_if_fail (h >= 0.0 && h <= 1.0);
1318 g_return_if_fail (s >= 0.0 && s <= 1.0);
1319 g_return_if_fail (v >= 0.0 && v <= 1.0);
1327 g_signal_emit (hsv, hsv_signals[CHANGED], 0);
1329 gtk_widget_queue_draw (GTK_WIDGET (hsv));
1333 * gtk_hsv_get_color:
1334 * @hsv: An HSV color selector
1335 * @h: (out): Return value for the hue
1336 * @s: (out): Return value for the saturation
1337 * @v: (out): Return value for the value
1339 * Queries the current color in an HSV color selector.
1340 * Returned values will be in the [0.0, 1.0] range.
1345 gtk_hsv_get_color (GtkHSV *hsv,
1350 GtkHSVPrivate *priv;
1352 g_return_if_fail (GTK_IS_HSV (hsv));
1367 * gtk_hsv_set_metrics:
1368 * @hsv: An HSV color selector
1369 * @size: Diameter for the hue ring
1370 * @ring_width: Width of the hue ring
1372 * Sets the size and ring width of an HSV color selector.
1377 gtk_hsv_set_metrics (GtkHSV *hsv,
1381 GtkHSVPrivate *priv;
1384 g_return_if_fail (GTK_IS_HSV (hsv));
1385 g_return_if_fail (size > 0);
1386 g_return_if_fail (ring_width > 0);
1387 g_return_if_fail (2 * ring_width + 1 <= size);
1391 same_size = (priv->size == size);
1394 priv->ring_width = ring_width;
1397 gtk_widget_queue_draw (GTK_WIDGET (hsv));
1399 gtk_widget_queue_resize (GTK_WIDGET (hsv));
1403 * gtk_hsv_get_metrics:
1404 * @hsv: An HSV color selector
1405 * @size: (out): Return value for the diameter of the hue ring
1406 * @ring_width: (out): Return value for the width of the hue ring
1408 * Queries the size and ring width of an HSV color selector.
1413 gtk_hsv_get_metrics (GtkHSV *hsv,
1417 GtkHSVPrivate *priv;
1419 g_return_if_fail (GTK_IS_HSV (hsv));
1427 *ring_width = priv->ring_width;
1431 * gtk_hsv_is_adjusting:
1434 * An HSV color selector can be said to be adjusting if multiple rapid
1435 * changes are being made to its value, for example, when the user is
1436 * adjusting the value with the mouse. This function queries whether
1437 * the HSV color selector is being adjusted or not.
1439 * Return value: %TRUE if clients can ignore changes to the color value,
1440 * since they may be transitory, or %FALSE if they should consider
1441 * the color value status to be final.
1446 gtk_hsv_is_adjusting (GtkHSV *hsv)
1448 GtkHSVPrivate *priv;
1450 g_return_val_if_fail (GTK_IS_HSV (hsv), FALSE);
1454 return priv->mode != DRAG_NONE;
1462 * @r: (out): Return value for the red component
1463 * @g: (out): Return value for the green component
1464 * @b: (out): Return value for the blue component
1466 * Converts a color from HSV space to RGB.
1467 * Input values must be in the [0.0, 1.0] range;
1468 * output values will be in the same range.
1473 gtk_hsv_to_rgb (gdouble h,
1480 g_return_if_fail (h >= 0.0 && h <= 1.0);
1481 g_return_if_fail (s >= 0.0 && s <= 1.0);
1482 g_return_if_fail (v >= 0.0 && v <= 1.0);
1484 hsv_to_rgb (&h, &s, &v);
1501 * @h: (out): Return value for the hue component
1502 * @s: (out): Return value for the saturation component
1503 * @v: (out): Return value for the value component
1505 * Converts a color from RGB space to HSV.
1506 * Input values must be in the [0.0, 1.0] range;
1507 * output values will be in the same range.
1512 gtk_rgb_to_hsv (gdouble r,
1519 g_return_if_fail (r >= 0.0 && r <= 1.0);
1520 g_return_if_fail (g >= 0.0 && g <= 1.0);
1521 g_return_if_fail (b >= 0.0 && b <= 1.0);
1523 rgb_to_hsv (&r, &g, &b);
1536 gtk_hsv_move (GtkHSV *hsv,
1537 GtkDirectionType dir)
1539 GtkHSVPrivate *priv = hsv->priv;
1540 gdouble hue, sat, val;
1541 gint hx, hy, sx, sy, vx, vy; /* HSV vertices */
1542 gint x, y; /* position in triangle */
1548 compute_triangle (hsv, &hx, &hy, &sx, &sy, &vx, &vy);
1550 x = floor (sx + (vx - sx) * priv->v + (hx - vx) * priv->s * priv->v + 0.5);
1551 y = floor (sy + (vy - sy) * priv->v + (hy - vy) * priv->s * priv->v + 0.5);
1553 #define HUE_DELTA 0.002
1557 if (priv->focus_on_ring)
1562 compute_sv (hsv, x, y, &sat, &val);
1567 if (priv->focus_on_ring)
1572 compute_sv (hsv, x, y, &sat, &val);
1577 if (priv->focus_on_ring)
1582 compute_sv (hsv, x, y, &sat, &val);
1587 if (priv->focus_on_ring)
1593 compute_sv (hsv, x, y, &sat, &val);
1598 /* we don't care about the tab directions */
1608 gtk_hsv_set_color (hsv, hue, sat, val);