1/* GTK - The GIMP Toolkit
2 * Copyright (C) 2012 Red Hat, Inc.
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18#include "config.h"
19
20#include "gtkcolorplaneprivate.h"
21
22#include "gtkadjustment.h"
23#include "gtkcolorutils.h"
24#include "gtkgesturedrag.h"
25#include "gtkgesturelongpress.h"
26#include "gtkintl.h"
27#include "gtksnapshot.h"
28#include "gtkprivate.h"
29#include "gtkeventcontrollerkey.h"
30#include "gtkshortcutcontroller.h"
31#include "gtkshortcuttrigger.h"
32#include "gtkshortcutaction.h"
33#include "gtkshortcut.h"
34
35struct _GtkColorPlane
36{
37 GtkWidget parent_instance;
38
39 GtkAdjustment *h_adj;
40 GtkAdjustment *s_adj;
41 GtkAdjustment *v_adj;
42
43 GdkTexture *texture;
44};
45
46typedef struct
47{
48 GtkWidgetClass parent_class;
49} GtkColorPlaneClass;
50
51enum {
52 PROP_0,
53 PROP_H_ADJUSTMENT,
54 PROP_S_ADJUSTMENT,
55 PROP_V_ADJUSTMENT
56};
57
58G_DEFINE_TYPE (GtkColorPlane, gtk_color_plane, GTK_TYPE_WIDGET)
59
60static void
61sv_to_xy (GtkColorPlane *plane,
62 int *x,
63 int *y)
64{
65 double s, v;
66 int width, height;
67
68 width = gtk_widget_get_width (GTK_WIDGET (plane));
69 height = gtk_widget_get_height (GTK_WIDGET (plane));
70
71 s = gtk_adjustment_get_value (adjustment: plane->s_adj);
72 v = gtk_adjustment_get_value (adjustment: plane->v_adj);
73
74 *x = CLAMP (width * v, 0, width - 1);
75 *y = CLAMP (height * (1 - s), 0, height - 1);
76}
77
78static void
79plane_snapshot (GtkWidget *widget,
80 GtkSnapshot *snapshot)
81{
82 GtkColorPlane *plane = GTK_COLOR_PLANE (widget);
83 int x, y;
84 int width, height;
85
86 sv_to_xy (plane, x: &x, y: &y);
87 width = gtk_widget_get_width (widget);
88 height = gtk_widget_get_height (widget);
89
90 gtk_snapshot_append_texture (snapshot,
91 texture: plane->texture,
92 bounds: &GRAPHENE_RECT_INIT (0, 0, width, height));
93 if (gtk_widget_has_visible_focus (widget))
94 {
95 const GdkRGBA c1 = { 1.0, 1.0, 1.0, 0.6 };
96 const GdkRGBA c2 = { 0.0, 0.0, 0.0, 0.8 };
97
98 /* Crosshair border */
99 gtk_snapshot_append_color (snapshot, color: &c1,
100 bounds: &GRAPHENE_RECT_INIT (0, y - 1.5, width, 3));
101 gtk_snapshot_append_color (snapshot, color: &c1,
102 bounds: &GRAPHENE_RECT_INIT (x - 1.5, 0, 3, height));
103
104 /* Actual crosshair */
105 gtk_snapshot_append_color (snapshot, color: &c2,
106 bounds: &GRAPHENE_RECT_INIT (0, y - 0.5, width, 1));
107 gtk_snapshot_append_color (snapshot, color: &c2,
108 bounds: &GRAPHENE_RECT_INIT (x - 0.5, 0, 1, height));
109 }
110 else
111 {
112 const GdkRGBA c = { 0.8, 0.8, 0.8, 0.8 };
113
114 /* Horizontal */
115 gtk_snapshot_append_color (snapshot, color: &c,
116 bounds: &GRAPHENE_RECT_INIT (0, y - 0.5, width, 1));
117 /* Vertical */
118 gtk_snapshot_append_color (snapshot, color: &c,
119 bounds: &GRAPHENE_RECT_INIT (x - 0.5, 0, 1, height));
120 }
121}
122
123static void
124create_texture (GtkWidget *widget)
125{
126 const int width = gtk_widget_get_width (widget);
127 const int height = gtk_widget_get_height (widget);
128 const int stride = width * 3;
129 GtkColorPlane *plane = GTK_COLOR_PLANE (widget);
130 GBytes *bytes;
131 guint8 *data;
132
133 if (!gtk_widget_get_mapped (widget))
134 return;
135
136 if (width == 0 || height == 0)
137 return;
138
139 g_clear_object (&plane->texture);
140
141 data = g_malloc (n_bytes: height * stride);
142 if (width > 1 && height > 1)
143 {
144 const float h = gtk_adjustment_get_value (adjustment: plane->h_adj);
145 int x, y;
146
147 for (y = 0; y < height; y++)
148 {
149 const float s = 1.0 - (float)y / (height - 1);
150
151 for (x = 0; x < width; x++)
152 {
153 const float v = (float)x / (width - 1);
154 float r, g, b;
155
156 gtk_hsv_to_rgb (h, s, v, r: &r, g: &g, b: &b);
157
158 data[(y * stride) + (x * 3) + 0] = r * 255;
159 data[(y * stride) + (x * 3) + 1] = g * 255;
160 data[(y * stride) + (x * 3) + 2] = b * 255;
161 }
162 }
163 }
164 else
165 {
166 memset (s: data, c: 0, n: height * stride);
167 }
168
169 bytes = g_bytes_new_take (data, size: height * stride);
170 plane->texture = gdk_memory_texture_new (width, height,
171 format: GDK_MEMORY_R8G8B8,
172 bytes,
173 stride);
174 g_bytes_unref (bytes);
175}
176
177static void
178plane_size_allocate (GtkWidget *widget,
179 int width,
180 int height,
181 int baseline)
182{
183 create_texture (widget);
184}
185
186static void
187set_cross_cursor (GtkWidget *widget,
188 gboolean enabled)
189{
190 if (enabled)
191 gtk_widget_set_cursor_from_name (widget, name: "crosshair");
192 else
193 gtk_widget_set_cursor (widget, NULL);
194}
195
196static void
197h_changed (GtkWidget *plane)
198{
199 create_texture (widget: plane);
200 gtk_widget_queue_draw (widget: plane);
201}
202
203static void
204sv_changed (GtkColorPlane *plane)
205{
206 gtk_widget_queue_draw (GTK_WIDGET (plane));
207}
208
209static void
210update_color (GtkColorPlane *plane,
211 int x,
212 int y)
213{
214 GtkWidget *widget = GTK_WIDGET (plane);
215 double s, v;
216
217 s = CLAMP (1 - y * (1.0 / gtk_widget_get_height (widget)), 0, 1);
218 v = CLAMP (x * (1.0 / gtk_widget_get_width (widget)), 0, 1);
219 gtk_adjustment_set_value (adjustment: plane->s_adj, value: s);
220 gtk_adjustment_set_value (adjustment: plane->v_adj, value: v);
221
222 gtk_widget_queue_draw (widget);
223}
224
225static void
226hold_action (GtkGestureLongPress *gesture,
227 double x,
228 double y,
229 GtkWidget *plane)
230{
231 gtk_widget_activate_action (widget: plane,
232 name: "color.edit",
233 format_string: "s", gtk_widget_get_name (widget: plane));
234}
235
236static void
237sv_move (GtkColorPlane *plane,
238 double ds,
239 double dv)
240{
241 double s, v;
242
243 s = gtk_adjustment_get_value (adjustment: plane->s_adj);
244 v = gtk_adjustment_get_value (adjustment: plane->v_adj);
245
246 if (s + ds > 1)
247 {
248 if (s < 1)
249 s = 1;
250 else
251 goto error;
252 }
253 else if (s + ds < 0)
254 {
255 if (s > 0)
256 s = 0;
257 else
258 goto error;
259 }
260 else
261 {
262 s += ds;
263 }
264
265 if (v + dv > 1)
266 {
267 if (v < 1)
268 v = 1;
269 else
270 goto error;
271 }
272 else if (v + dv < 0)
273 {
274 if (v > 0)
275 v = 0;
276 else
277 goto error;
278 }
279 else
280 {
281 v += dv;
282 }
283
284 gtk_adjustment_set_value (adjustment: plane->s_adj, value: s);
285 gtk_adjustment_set_value (adjustment: plane->v_adj, value: v);
286 return;
287
288error:
289 gtk_widget_error_bell (GTK_WIDGET (plane));
290}
291
292static gboolean
293key_controller_key_pressed (GtkEventControllerKey *controller,
294 guint keyval,
295 guint keycode,
296 GdkModifierType state,
297 GtkWidget *widget)
298{
299 GtkColorPlane *plane = GTK_COLOR_PLANE (widget);
300 double step;
301
302 if ((state & GDK_ALT_MASK) != 0)
303 step = 0.1;
304 else
305 step = 0.01;
306
307 if (keyval == GDK_KEY_Up ||
308 keyval == GDK_KEY_KP_Up)
309 sv_move (plane, ds: step, dv: 0);
310 else if (keyval == GDK_KEY_Down ||
311 keyval == GDK_KEY_KP_Down)
312 sv_move (plane, ds: -step, dv: 0);
313 else if (keyval == GDK_KEY_Left ||
314 keyval == GDK_KEY_KP_Left)
315 sv_move (plane, ds: 0, dv: -step);
316 else if (keyval == GDK_KEY_Right ||
317 keyval == GDK_KEY_KP_Right)
318 sv_move (plane, ds: 0, dv: step);
319 else
320 return FALSE;
321
322 return TRUE;
323}
324
325static void
326plane_drag_gesture_begin (GtkGestureDrag *gesture,
327 double start_x,
328 double start_y,
329 GtkWidget *plane)
330{
331 guint button;
332
333 button = gtk_gesture_single_get_current_button (GTK_GESTURE_SINGLE (gesture));
334
335 if (button == GDK_BUTTON_SECONDARY)
336 {
337 gtk_widget_activate_action (widget: plane,
338 name: "color.edit",
339 format_string: "s", gtk_widget_get_name (widget: plane));
340 }
341
342 if (button != GDK_BUTTON_PRIMARY)
343 {
344 gtk_gesture_set_state (GTK_GESTURE (gesture), state: GTK_EVENT_SEQUENCE_DENIED);
345 return;
346 }
347
348 set_cross_cursor (widget: plane, TRUE);
349 update_color (GTK_COLOR_PLANE (plane), x: start_x, y: start_y);
350 gtk_widget_grab_focus (widget: plane);
351 gtk_gesture_set_state (GTK_GESTURE (gesture), state: GTK_EVENT_SEQUENCE_CLAIMED);
352}
353
354static void
355plane_drag_gesture_update (GtkGestureDrag *gesture,
356 double offset_x,
357 double offset_y,
358 GtkColorPlane *plane)
359{
360 double start_x, start_y;
361
362 gtk_gesture_drag_get_start_point (GTK_GESTURE_DRAG (gesture),
363 x: &start_x, y: &start_y);
364 update_color (plane, x: start_x + offset_x, y: start_y + offset_y);
365}
366
367static void
368plane_drag_gesture_end (GtkGestureDrag *gesture,
369 double offset_x,
370 double offset_y,
371 GtkColorPlane *plane)
372{
373 set_cross_cursor (GTK_WIDGET (plane), FALSE);
374}
375
376static void
377gtk_color_plane_init (GtkColorPlane *plane)
378{
379 GtkEventController *controller;
380 GtkGesture *gesture;
381 GtkShortcutTrigger *trigger;
382 GtkShortcutAction *action;
383 GtkShortcut *shortcut;
384
385 gtk_widget_set_focusable (GTK_WIDGET (plane), TRUE);
386
387 gesture = gtk_gesture_drag_new ();
388 g_signal_connect (gesture, "drag-begin",
389 G_CALLBACK (plane_drag_gesture_begin), plane);
390 g_signal_connect (gesture, "drag-update",
391 G_CALLBACK (plane_drag_gesture_update), plane);
392 g_signal_connect (gesture, "drag-end",
393 G_CALLBACK (plane_drag_gesture_end), plane);
394 gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (gesture), button: 0);
395 gtk_widget_add_controller (GTK_WIDGET (plane), GTK_EVENT_CONTROLLER (gesture));
396
397 gesture = gtk_gesture_long_press_new ();
398 g_signal_connect (gesture, "pressed",
399 G_CALLBACK (hold_action), plane);
400 gtk_gesture_single_set_touch_only (GTK_GESTURE_SINGLE (gesture),
401 TRUE);
402 gtk_widget_add_controller (GTK_WIDGET (plane), GTK_EVENT_CONTROLLER (gesture));
403
404 controller = gtk_event_controller_key_new ();
405 g_signal_connect (controller, "key-pressed",
406 G_CALLBACK (key_controller_key_pressed), plane);
407 gtk_widget_add_controller (GTK_WIDGET (plane), controller);
408
409 controller = gtk_shortcut_controller_new ();
410 trigger = gtk_alternative_trigger_new (first: gtk_keyval_trigger_new (GDK_KEY_F10, modifiers: GDK_SHIFT_MASK),
411 second: gtk_keyval_trigger_new (GDK_KEY_Menu, modifiers: 0));
412 action = gtk_named_action_new (name: "color.edit");
413 shortcut = gtk_shortcut_new_with_arguments (trigger, action, format_string: "s", "sv");
414 gtk_shortcut_controller_add_shortcut (GTK_SHORTCUT_CONTROLLER (controller), shortcut);
415 gtk_widget_add_controller (GTK_WIDGET (plane), controller);
416}
417
418static void
419plane_finalize (GObject *object)
420{
421 GtkColorPlane *plane = GTK_COLOR_PLANE (object);
422
423 g_clear_object (&plane->texture);
424
425 g_clear_object (&plane->h_adj);
426 g_clear_object (&plane->s_adj);
427 g_clear_object (&plane->v_adj);
428
429 G_OBJECT_CLASS (gtk_color_plane_parent_class)->finalize (object);
430}
431
432static void
433plane_set_property (GObject *object,
434 guint prop_id,
435 const GValue *value,
436 GParamSpec *pspec)
437{
438 GtkColorPlane *plane = GTK_COLOR_PLANE (object);
439 GtkAdjustment *adjustment;
440
441 /* Construct only properties can only be set once, these are created
442 * only in order to be properly buildable from gtkcoloreditor.ui
443 */
444 switch (prop_id)
445 {
446 case PROP_H_ADJUSTMENT:
447 adjustment = g_value_get_object (value);
448 if (adjustment)
449 {
450 plane->h_adj = g_object_ref_sink (adjustment);
451 g_signal_connect_swapped (adjustment, "value-changed", G_CALLBACK (h_changed), plane);
452 }
453 break;
454 case PROP_S_ADJUSTMENT:
455 adjustment = g_value_get_object (value);
456 if (adjustment)
457 {
458 plane->s_adj = g_object_ref_sink (adjustment);
459 g_signal_connect_swapped (adjustment, "value-changed", G_CALLBACK (sv_changed), plane);
460 }
461 break;
462 case PROP_V_ADJUSTMENT:
463 adjustment = g_value_get_object (value);
464 if (adjustment)
465 {
466 plane->v_adj = g_object_ref_sink (adjustment);
467 g_signal_connect_swapped (adjustment, "value-changed", G_CALLBACK (sv_changed), plane);
468 }
469 break;
470 default:
471 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
472 break;
473 }
474}
475
476static void
477gtk_color_plane_class_init (GtkColorPlaneClass *class)
478{
479 GObjectClass *object_class = G_OBJECT_CLASS (class);
480 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
481
482 object_class->finalize = plane_finalize;
483 object_class->set_property = plane_set_property;
484
485 widget_class->snapshot = plane_snapshot;
486 widget_class->size_allocate = plane_size_allocate;
487
488 g_object_class_install_property (oclass: object_class,
489 property_id: PROP_H_ADJUSTMENT,
490 pspec: g_param_spec_object (name: "h-adjustment",
491 nick: "Hue Adjustment",
492 blurb: "Hue Adjustment",
493 GTK_TYPE_ADJUSTMENT,
494 GTK_PARAM_WRITABLE |
495 G_PARAM_CONSTRUCT_ONLY));
496
497 g_object_class_install_property (oclass: object_class,
498 property_id: PROP_S_ADJUSTMENT,
499 pspec: g_param_spec_object (name: "s-adjustment",
500 nick: "Saturation Adjustment",
501 blurb: "Saturation Adjustment",
502 GTK_TYPE_ADJUSTMENT,
503 GTK_PARAM_WRITABLE |
504 G_PARAM_CONSTRUCT_ONLY));
505
506 g_object_class_install_property (oclass: object_class,
507 property_id: PROP_V_ADJUSTMENT,
508 pspec: g_param_spec_object (name: "v-adjustment",
509 nick: "Value Adjustment",
510 blurb: "Value Adjustment",
511 GTK_TYPE_ADJUSTMENT,
512 GTK_PARAM_WRITABLE |
513 G_PARAM_CONSTRUCT_ONLY));
514
515 gtk_widget_class_set_css_name (widget_class, name: "plane");
516}
517
518GtkWidget *
519gtk_color_plane_new (GtkAdjustment *h_adj,
520 GtkAdjustment *s_adj,
521 GtkAdjustment *v_adj)
522{
523 return g_object_new (GTK_TYPE_COLOR_PLANE,
524 first_property_name: "h-adjustment", h_adj,
525 "s-adjustment", s_adj,
526 "v-adjustment", v_adj,
527 NULL);
528}
529

source code of gtk/gtk/gtkcolorplane.c