Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
gradientslider.c
Go to the documentation of this file.
1/*
2 This file is part of darktable,
3 Copyright (C) 2010-2011 Henrik Andersson.
4 Copyright (C) 2011-2012 johannes hanika.
5 Copyright (C) 2012 Jean-Sébastien Pédron.
6 Copyright (C) 2012 Richard Wonka.
7 Copyright (C) 2012-2016, 2018 Tobias Ellinghaus.
8 Copyright (C) 2012, 2014, 2019 Ulrich Pegelow.
9 Copyright (C) 2013-2016 Roman Lebedev.
10 Copyright (C) 2013 Simon Spannagel.
11 Copyright (C) 2017-2018 Dan Torop.
12 Copyright (C) 2019, 2023, 2025 Aurélien PIERRE.
13 Copyright (C) 2019 Edgardo Hoszowski.
14 Copyright (C) 2019-2020 parafin.
15 Copyright (C) 2020-2022 Diederik Ter Rahe.
16 Copyright (C) 2020 Marco.
17 Copyright (C) 2020-2021 Pascal Obry.
18 Copyright (C) 2020-2021 Ralf Brown.
19 Copyright (C) 2021 Harold le Clément de Saint-Marcq.
20 Copyright (C) 2021 Hubert Kowalski.
21 Copyright (C) 2021 lhietal.
22 Copyright (C) 2022 Martin Bařinka.
23 Copyright (C) 2022 Nicolas Auffray.
24
25 darktable is free software: you can redistribute it and/or modify
26 it under the terms of the GNU General Public License as published by
27 the Free Software Foundation, either version 3 of the License, or
28 (at your option) any later version.
29
30 darktable is distributed in the hope that it will be useful,
31 but WITHOUT ANY WARRANTY; without even the implied warranty of
32 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
33 GNU General Public License for more details.
34
35 You should have received a copy of the GNU General Public License
36 along with darktable. If not, see <http://www.gnu.org/licenses/>.
37*/
38
39#include <assert.h>
40#include <string.h>
41
42#include "system/macros.h"
43#include <math.h>
45#include "system/mem_alloc.h"
46#include "widgets/gdkkeys.h"
47#include "gradientslider.h"
50#include "widgets/paint.h"
51
52#define DTGTK_GRADIENT_SLIDER_VALUE_CHANGED_DELAY_MAX 50
53#define DTGTK_GRADIENT_SLIDER_VALUE_CHANGED_DELAY_MIN 10
54#define DTGTK_GRADIENT_SLIDER_DEFAULT_INCREMENT 0.01
55
56// define GTypes
57G_DEFINE_TYPE(GtkDarktableGradientSlider, _gradient_slider, GTK_TYPE_DRAWING_AREA);
58#define parent_class _gradient_slider_parent_class
59
60// Class overrides
61static void _gradient_slider_get_preferred_height(GtkWidget *widget, gint *min_height, gint *nat_height);
62static void _gradient_slider_get_preferred_width(GtkWidget *widget, gint *min_width, gint *nat_width);
63static gboolean _gradient_slider_draw(GtkWidget *widget, cairo_t *cr);
64static void _gradient_slider_destroy(GtkWidget *widget);
65
66// Events
67static gboolean _gradient_slider_enter_notify_event(GtkWidget *widget, GdkEventCrossing *event);
68static gboolean _gradient_slider_button_press(GtkWidget *widget, GdkEventButton *event);
69static gboolean _gradient_slider_button_release(GtkWidget *widget, GdkEventButton *event);
70static gboolean _gradient_slider_motion_notify(GtkWidget *widget, GdkEventMotion *event);
71static gboolean _gradient_slider_scroll_event(GtkWidget *widget, GdkEventScroll *event);
72static gboolean _gradient_slider_key_press_event(GtkWidget *widget, GdkEventKey *event);
73
74enum
75{
79};
80
81static guint _signals[LAST_SIGNAL] = { 0 };
82
83static gboolean _gradient_slider_postponed_value_change(gpointer data)
84{
85 if(!GTK_IS_WIDGET(data)) return 0;
86
87 if(DTGTK_GRADIENT_SLIDER(data)->is_changed)
88 {
89 g_signal_emit_by_name(G_OBJECT(data), "value-changed");
90 DTGTK_GRADIENT_SLIDER(data)->is_changed = FALSE;
91 }
92
93 DTGTK_GRADIENT_SLIDER(data)->timeout_handle = 0;
94 return G_SOURCE_REMOVE; // This is called by the gtk mainloop and is threadsafe
95}
96
97static inline gboolean _test_if_marker_is_upper_or_down(const gint marker, const gboolean up)
98{
99 if(up && (marker == GRADIENT_SLIDER_MARKER_LOWER_OPEN ||
103 return FALSE;
104 else if(!up && (marker == GRADIENT_SLIDER_MARKER_UPPER_OPEN ||
108 return FALSE;
109 else
110 return TRUE; // must be a DOUBLE
111}
112
113static inline gdouble _screen_to_scale(GtkWidget *widget, gint screen)
114{
116
117 GtkAllocation allocation;
118 gtk_widget_get_allocation(widget, &allocation);
119 return ((gdouble)screen - gslider->margin_left) / ((gdouble)allocation.width - gslider->margin_left - gslider->margin_right);
120}
121
122static inline gint _scale_to_screen(GtkWidget *widget, gdouble scale)
123{
125
126 GtkAllocation allocation;
127 gtk_widget_get_allocation(widget, &allocation);
128 return (gint)(scale * (allocation.width - gslider->margin_left - gslider->margin_right) + gslider->margin_left);
129}
130
131static inline gdouble _get_position_from_screen(GtkWidget *widget, const gdouble x)
132{
134 gdouble position = roundf(_screen_to_scale(widget, x) / gslider->increment) * gslider->increment;
135 return CLAMP(position, 0., 1.);
136}
137
139{
140 return (gslider->selected >= 0) ? gslider->selected : gslider->active;
141}
142
143static inline void _clamp_marker(GtkDarktableGradientSlider *gslider, const gint selected)
144{
145 g_return_if_fail(!IS_NULL_PTR(gslider));
146
147 const gdouble min = (selected == 0) ? 0.0f : gslider->position[selected - 1];
148 const gdouble max = (selected == gslider->positions - 1) ? 1.0f : gslider->position[selected + 1];
149 gslider->position[selected] = CLAMP(gslider->position[selected], min, max);
150}
151
152static gint _get_active_marker_internal(GtkWidget *widget, const gdouble x, const gboolean up)
153{
155 gint lselected = -1;
156 const gdouble newposition = _get_position_from_screen(widget, x);
157
158 assert(gslider->positions > 0);
159
160 for(int k = 0; k < gslider->positions; k++)
161 if(_test_if_marker_is_upper_or_down(gslider->marker[k], up))
162 {
163 if(lselected < 0) lselected = k;
164 if(fabs(newposition - gslider->position[k]) < fabs(newposition - gslider->position[lselected]))
165 lselected = k;
166 }
167
168 return lselected;
169}
170
171static gint _get_active_marker_from_screen(GtkWidget *widget, const gdouble x, const gdouble y)
172{
173 GtkAllocation allocation;
174 gtk_widget_get_allocation(widget, &allocation);
175
176 gboolean up = (y <= allocation.height / 2.f);
177 gint lselected = _get_active_marker_internal(widget, x, up);
178 if(lselected < 0) lselected = _get_active_marker_internal(widget, x, !up);
179
180 assert(lselected >= 0);
181
182 return lselected;
183}
184
185static gdouble _slider_move(GtkWidget *widget, gint k, gdouble value, gint direction)
186{
187 g_return_val_if_fail(DTGTK_IS_GRADIENT_SLIDER(widget), value);
188
190
191 gdouble newvalue = value;
192 gdouble leftnext, rightnext, ms;
193
194 switch(gslider->markers_type)
195 {
196 case FREE_MARKERS:
197 {
198 leftnext = (k == 0) ? 0.0f : gslider->position[k - 1];
199 rightnext = (k == gslider->positions - 1) ? 1.0f : gslider->position[k + 1];
200 ms = gslider->min_spacing;
201 switch(direction)
202 {
203 case MOVE_LEFT:
204 if(value < leftnext + ms)
205 newvalue = (k == 0) ? fmax(value, 0.0f) : _slider_move(widget, k - 1, value - ms, direction) + ms;
206 break;
207 case MOVE_RIGHT:
208 if(value > rightnext - ms)
209 newvalue = (k == gslider->positions - 1) ? fmin(value, 1.0f) : _slider_move(widget, k + 1, value + ms, direction) - ms;
210 break;
211 }
212 break;
213 }
215 {
216 ms = fmax(gslider->min_spacing, 1.0e-6);
217 const double vmin = ((k == 0) ? 0.0f : gslider->position[0]);
218 const double vmax = ((k == gslider->positions - 1) ? 1.0f : gslider->position[gslider->positions - 1]);
219
220 newvalue = CLAMP(value, vmin + ms * k, vmax - ms * (gslider->positions - 1 - k));
221 const double rl = (newvalue - gslider->position[0]) / (gslider->position[k] - gslider->position[0]);
222 const double rh = (gslider->position[gslider->positions - 1] - newvalue) /
223 (gslider->position[gslider->positions - 1] - gslider->position[k]);
224
225 for(int i = 1; i < k; i++)
226 gslider->position[i] = rl * (gslider->position[i] - gslider->position[0]) + gslider->position[0];
227
228 for(int i = k + 1; i < gslider->positions; i++)
229 gslider->position[i] = gslider->position[gslider->positions - 1] -
230 rh * (gslider->position[gslider->positions - 1] - gslider->position[i]);
231 break;
232 }
233 }
234 gslider->position[k] = newvalue;
235 return newvalue;
236}
237
238static gboolean _gradient_slider_add_delta_internal(GtkWidget *widget, gdouble delta, guint state, const gint selected)
239{
240 g_return_val_if_fail(DTGTK_IS_GRADIENT_SLIDER(widget), TRUE);
241
243
244 if(selected == -1) return TRUE;
245
246 gslider->position[selected] = gslider->position[selected] + delta;
247 _clamp_marker(gslider, selected);
248 gslider->is_changed = TRUE;
249
250 gtk_widget_queue_draw(widget);
251
252 if(gslider->timeout_handle)
253 {
254 g_source_remove(gslider->timeout_handle);
255 gslider->timeout_handle = 0;
256 }
257 gslider->timeout_handle = g_timeout_add(350, _gradient_slider_postponed_value_change, widget);
258
259 return TRUE;
260}
261
262static float _default_linear_scale_callback(GtkWidget *self, float value, int dir)
263{
264 // regardless of dir: input <-> output
265 return value;
266}
267
268static gboolean _gradient_slider_enter_notify_event(GtkWidget *widget, GdkEventCrossing *event)
269{
270 g_return_val_if_fail(DTGTK_IS_GRADIENT_SLIDER(widget), FALSE);
271
273 gtk_widget_set_state_flags(widget, GTK_STATE_FLAG_PRELIGHT, TRUE);
274 gslider->is_entered = TRUE;
275 gtk_widget_queue_draw(widget);
276 return FALSE;
277}
278
279static gboolean _gradient_slider_leave_notify_event(GtkWidget *widget, GdkEventCrossing *event)
280{
281 g_return_val_if_fail(DTGTK_IS_GRADIENT_SLIDER(widget), FALSE);
282
284 if(!(gslider->is_dragging))
285 {
286 gtk_widget_set_state_flags(widget, GTK_STATE_FLAG_NORMAL, TRUE);
287 gslider->is_entered = FALSE;
288 gslider->active = -1;
289 gtk_widget_queue_draw(widget);
290 }
291 return FALSE;
292}
293
294static gboolean _gradient_slider_button_press(GtkWidget *widget, GdkEventButton *event)
295{
296 g_return_val_if_fail(DTGTK_IS_GRADIENT_SLIDER(widget), FALSE);
297
300
301 // reset slider
302 if(event->button == 1 && event->type == GDK_2BUTTON_PRESS && gslider->is_resettable)
303 {
304 gslider->is_dragging = FALSE;
305 gslider->do_reset = TRUE;
306 gslider->selected = -1;
307 for(int k = 0; k < gslider->positions; k++) gslider->position[k] = gslider->resetvalue[k];
308 gtk_widget_queue_draw(widget);
309 g_signal_emit_by_name(G_OBJECT(widget), "value-changed");
310 g_signal_emit_by_name(G_OBJECT(widget), "value-reset");
311 }
312 else if((event->button == 1 || event->button == 3) && event->type == GDK_BUTTON_PRESS)
313 {
314 const gint lselected = _get_active_marker_from_screen(widget, event->x, event->y);
315
316 assert(lselected >= 0);
317 assert(lselected <= gslider->positions - 1);
318
319 if(event->button == 1) // left mouse button : select and start dragging
320 {
321 gslider->selected = lselected;
322 gslider->do_reset = FALSE;
323
324 const gdouble newposition = _get_position_from_screen(widget, event->x);
325 const gint direction = gslider->position[gslider->selected] <= newposition ? MOVE_RIGHT : MOVE_LEFT;
326
327 _slider_move(widget, gslider->selected, newposition, direction);
328
329 gslider->is_changed = TRUE;
330 gslider->is_dragging = TRUE;
331 }
332 else if(gslider->positions > 1) // right mouse button: switch on/off selection (only if we have more than one marker)
333 {
334 gslider->is_dragging = FALSE;
335 gslider->do_reset = FALSE;
336
337 if(gslider->selected != lselected)
338 gslider->selected = lselected;
339 else
340 gslider->selected = -1;
341
342 gtk_widget_queue_draw(widget);
343 }
344 }
345
346 return TRUE;
347}
348
349static gboolean _gradient_slider_motion_notify(GtkWidget *widget, GdkEventMotion *event)
350{
351 g_return_val_if_fail(DTGTK_IS_GRADIENT_SLIDER(widget), FALSE);
352
354
355 if(gslider->is_dragging == TRUE && gslider->selected != -1 && gslider->do_reset == FALSE)
356 {
357 const gdouble newposition = _get_position_from_screen(widget, event->x);
358 const gint direction = gslider->position[gslider->selected] <= newposition ? MOVE_RIGHT : MOVE_LEFT;
359
360 _slider_move(widget, gslider->selected, newposition, direction);
361
362 gslider->is_changed = TRUE;
363
364 gtk_widget_queue_draw(widget);
365
366 // timeout_handle should always be zero here, but check just in case
367 if(gslider->timeout_handle)
368 {
369 g_source_remove(gslider->timeout_handle);
370 gslider->timeout_handle = 0;
371 }
372 gslider->timeout_handle = g_timeout_add(350, _gradient_slider_postponed_value_change, widget);
373 }
374 else
375 {
376 gslider->active = _get_active_marker_from_screen(widget, event->x, event->y);
377 }
378
379 if(gslider->selected != -1) gtk_widget_grab_focus(widget);
380
381 return TRUE;
382}
383
384static gboolean _gradient_slider_button_release(GtkWidget *widget, GdkEventButton *event)
385{
386 g_return_val_if_fail(DTGTK_IS_GRADIENT_SLIDER(widget), FALSE);
387
389 const gint selected = _get_active_marker(gslider);
390
391 if(event->button == 1 && selected != -1 && gslider->do_reset == FALSE)
392 {
393 // First get some dimension info
394 gslider->is_changed = TRUE;
395 const gdouble newposition = _get_position_from_screen(widget, event->x);
396 const gint direction = gslider->position[selected] <= newposition ? MOVE_RIGHT : MOVE_LEFT;
397
398 _slider_move(widget, selected, newposition, direction);
399
400 gtk_widget_queue_draw(widget);
402
403 gslider->is_dragging = FALSE;
404 g_signal_emit_by_name(G_OBJECT(widget), "value-changed");
405 }
406 return TRUE;
407}
408
409static gboolean _gradient_slider_scroll_event(GtkWidget *widget, GdkEventScroll *event)
410{
411 g_return_val_if_fail(DTGTK_IS_GRADIENT_SLIDER(widget), TRUE);
412
413 if(dt_widget_scroll_focus() != widget) return FALSE;
414
416 const gint selected = _get_active_marker(gslider);
417 if(selected == -1) return TRUE;
418
419 gtk_widget_grab_focus(widget);
420
421 int delta_y;
422 if(dt_gui_get_scroll_unit_delta(event, &delta_y))
423 {
424 gdouble delta = delta_y * -gslider->increment;
425 return _gradient_slider_add_delta_internal(widget, delta, event->state, selected);
426 }
427
428 return TRUE;
429}
430
431static gboolean _gradient_slider_key_press_event(GtkWidget *widget, GdkEventKey *event)
432{
433 g_return_val_if_fail(DTGTK_IS_GRADIENT_SLIDER(widget), TRUE);
434
436
437 int handled = FALSE;
438 float delta = -gslider->increment;
439 guint key = dt_keys_mainpad_alternatives(event->keyval);
440
441 switch(key)
442 {
443 case GDK_KEY_Up:
444 case GDK_KEY_Right:
445 delta = gslider->increment;
446 case GDK_KEY_Down:
447 case GDK_KEY_Left:
448 handled = TRUE;
449 }
450
451 if(!handled) return FALSE;
452
453 const gint selected = _get_active_marker(gslider);
454 if(selected == -1) return TRUE;
455
456 return _gradient_slider_add_delta_internal(widget, delta, event->state, selected);
457}
458
460{
461 GtkWidgetClass *widget_class = (GtkWidgetClass *)klass;
462
463 widget_class->get_preferred_height = _gradient_slider_get_preferred_height;
464 widget_class->get_preferred_width = _gradient_slider_get_preferred_width;
465 widget_class->draw = _gradient_slider_draw;
466 widget_class->destroy = _gradient_slider_destroy;
467
468 widget_class->enter_notify_event = _gradient_slider_enter_notify_event;
469 widget_class->leave_notify_event = _gradient_slider_leave_notify_event;
470 widget_class->button_press_event = _gradient_slider_button_press;
471 widget_class->button_release_event = _gradient_slider_button_release;
472 widget_class->motion_notify_event = _gradient_slider_motion_notify;
473 widget_class->scroll_event = _gradient_slider_scroll_event;
474 widget_class->key_press_event = _gradient_slider_key_press_event;
475
476 _signals[VALUE_CHANGED] = g_signal_new("value-changed", G_TYPE_FROM_CLASS(klass), G_SIGNAL_RUN_LAST, 0,
477 NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
478 _signals[VALUE_RESET] = g_signal_new("value-reset", G_TYPE_FROM_CLASS(klass), G_SIGNAL_RUN_LAST, 0,
479 NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
480}
481
483{
484 g_return_if_fail(!IS_NULL_PTR(gslider));
485
486 GtkWidget *widget = GTK_WIDGET(gslider);
487 gtk_widget_add_events(widget,
488 GDK_EXPOSURE_MASK |
489 GDK_BUTTON_PRESS_MASK |
490 GDK_BUTTON_RELEASE_MASK |
491 GDK_ENTER_NOTIFY_MASK |
492 GDK_LEAVE_NOTIFY_MASK |
493 GDK_KEY_PRESS_MASK |
494 GDK_KEY_RELEASE_MASK |
495 GDK_POINTER_MOTION_MASK |
497
498 gtk_widget_set_has_window(widget, TRUE);
499 gtk_widget_set_can_focus(widget, TRUE);
500}
501
502static void _gradient_slider_get_preferred_height(GtkWidget *widget, gint *min_height, gint *nat_height)
503{
504 g_return_if_fail(!IS_NULL_PTR(widget));
505
506 GtkStyleContext *context = gtk_widget_get_style_context(widget);
507 GtkStateFlags state = gtk_widget_get_state_flags(widget);
508
509 GtkBorder margin, border, padding;
510 int css_min_height;
511 gtk_style_context_get (context, state, "min-height", &css_min_height, NULL);
512 gtk_style_context_get_margin(context, state, &margin);
513 gtk_style_context_get_border(context, state, &border);
514 gtk_style_context_get_padding(context, state, &padding);
515 *min_height = *nat_height = css_min_height + padding.top + padding.bottom + border.top + border.bottom + margin.top + margin.bottom;
516}
517
518static void _gradient_slider_get_preferred_width(GtkWidget *widget, gint *min_width, gint *nat_width)
519{
520 g_return_if_fail(DTGTK_IS_GRADIENT_SLIDER(widget));
521
522 GtkStyleContext *context = gtk_widget_get_style_context(widget);
523 GtkStateFlags state = gtk_widget_get_state_flags(widget);
524
525 GtkBorder margin, border, padding;
526 int css_min_width;
527 gtk_style_context_get (context, state, "min-width", &css_min_width, NULL);
528 gtk_style_context_get_margin(context, state, &margin);
529 gtk_style_context_get_border(context, state, &border);
530 gtk_style_context_get_padding(context, state, &padding);
531 *min_width = *nat_width = css_min_width + padding.left + padding.right + border.left + border.right + margin.left + margin.right;
532
533 DTGTK_GRADIENT_SLIDER(widget)->margin_left = padding.left + border.left + margin.left;
534 DTGTK_GRADIENT_SLIDER(widget)->margin_right = padding.right + border.right + margin.right;
535}
536
538{
539 g_return_if_fail(DTGTK_IS_GRADIENT_SLIDER(widget));
540
542
543 if(gslider->timeout_handle)
544 {
545 g_source_remove(gslider->timeout_handle);
546 gslider->timeout_handle = 0;
547 }
548
549 if(gslider->colors)
550 g_list_free_full(gslider->colors, dt_free_gpointer);
551
552 gslider->colors = NULL;
553
554 GTK_WIDGET_CLASS(parent_class)->destroy(widget);
555}
556
557static gboolean _gradient_slider_draw(GtkWidget *widget, cairo_t *cr)
558{
559 g_return_val_if_fail(DTGTK_IS_GRADIENT_SLIDER(widget), FALSE);
561
562 assert(gslider->position > 0);
563
564 GtkStyleContext *context = gtk_widget_get_style_context(widget);
565 GtkStateFlags state = gtk_widget_get_state_flags(widget);
566
567 GdkRGBA color;
568 gtk_style_context_get_color(context, state, &color);
569
570 GtkAllocation allocation;
571 GtkBorder margin, border, padding;
572 gtk_widget_get_allocation(widget, &allocation);
573 gtk_style_context_get_margin(context, state, &margin);
574 gtk_style_context_get_border(context, state, &border);
575 gtk_style_context_get_padding(context, state, &padding);
576
577 // Begin cairo drawing
578 // for frame and background, we remove css margin from allocation
579 int startx = margin.left;
580 int starty = margin.top;
581 int cwidth = allocation.width - margin.left - margin.right;
582 int cheight = allocation.height - margin.top - margin.bottom;
583 gtk_render_background(context, cr, startx, starty, cwidth, cheight);
584 gtk_render_frame(context, cr, startx, starty, cwidth, cheight);
585
586 // then we draw the content
587 startx += padding.left + border.left;
588 starty += padding.top + border.top;
589 cwidth -= padding.left + padding.right + border.left + border.right;
590 cheight -= padding.top + padding.bottom + border.top + border.bottom;
591 const int y1 = round(0.3f * cheight);
592 const int gheight = cheight - 2 * y1;
593
594 // First build the cairo gradient and then fill the gradient
595 if(gslider->colors)
596 {
597 cairo_pattern_t *gradient = cairo_pattern_create_linear(0, 0, cwidth, 0);
598 for(GList *current = gslider->colors; current; current = g_list_next(current))
599 {
600 _gradient_slider_stop_t *stop = (_gradient_slider_stop_t *)current->data;
601 cairo_pattern_add_color_stop_rgba(gradient, stop->position, stop->color.red, stop->color.green,
602 stop->color.blue, stop->color.alpha);
603 }
604 if(!IS_NULL_PTR(gradient)) // Do we got a gradient, lets draw it
605 {
606 cairo_set_line_width(cr, 0.1);
607 cairo_set_line_cap(cr, CAIRO_LINE_CAP_ROUND);
608 cairo_translate(cr, 0, starty);
609 cairo_set_source(cr, gradient);
610 cairo_rectangle(cr, startx, y1, cwidth, gheight);
611 cairo_fill(cr);
612 cairo_stroke(cr);
613 cairo_pattern_destroy(gradient);
614 }
615 }
616
617 // Lets draw position arrows
618 cairo_set_source_rgba(cr, color.red, color.green, color.blue, 1.0);
619
620 // do we have a picker value to draw?
621 if(!isnan(gslider->picker[0]))
622 {
623 int vx_min = _scale_to_screen(widget, CLAMP(gslider->picker[1], 0.0, 1.0));
624 int vx_max = _scale_to_screen(widget, CLAMP(gslider->picker[2], 0.0, 1.0));
625 int vx_avg = _scale_to_screen(widget, CLAMP(gslider->picker[0], 0.0, 1.0));
626
627 cairo_set_source_rgba(cr, color.red, color.green, color.blue, 0.33);
628
629 cairo_rectangle(cr, vx_min, y1, fmax((float)vx_max - vx_min, 0.0f), gheight);
630 cairo_fill(cr);
631
632 cairo_set_source_rgba(cr, color.red, color.green, color.blue, 1.0);
633
634 cairo_move_to(cr, vx_avg, y1);
635 cairo_rel_line_to(cr, 0, gheight);
636 cairo_set_antialias(cr, CAIRO_ANTIALIAS_NONE);
637 cairo_set_line_width(cr, 1.0);
638 cairo_stroke(cr);
639 }
640
641 for(int k = 0; k < gslider->positions; k++)
642 {
643 const int vx = _scale_to_screen(widget, gslider->position[k]);
644 const int mk = gslider->marker[k];
645 const int sz = round((mk & (1 << 3)) ? 1.9f * y1 : 1.4f * y1); // big or small marker?
646
647 if(k == gslider->selected && gslider->is_entered) // highlight the active marker
648 cairo_set_source_rgba(cr, color.red, color.green, color.blue, 1.0);
649 else
650 cairo_set_source_rgba(cr, color.red * 0.8, color.green * 0.8, color.blue * 0.8, 1.0);
651
652 cairo_set_antialias(cr, CAIRO_ANTIALIAS_DEFAULT);
653
654 if(mk & 0x04) /* upper arrow */
655 {
656 if(mk & 0x01) /* filled */
657 dtgtk_cairo_paint_solid_triangle(cr, round(vx - 0.5f * sz), round((float)y1 - 0.55f * sz), sz, sz, CPF_DIRECTION_DOWN, NULL);
658 else
659 dtgtk_cairo_paint_triangle(cr, round(vx - 0.5f * sz), round((float)y1 - 0.55f * sz), sz, sz, CPF_DIRECTION_DOWN, NULL);
660 }
661
662 if(mk & 0x02) /* lower arrow */
663 {
664 if(mk & 0x01) /* filled */
665 dtgtk_cairo_paint_solid_triangle(cr, round(vx - 0.5f * sz), round((float)cheight - y1 - 0.45f * sz), sz, sz, CPF_DIRECTION_UP, NULL);
666 else
667 dtgtk_cairo_paint_triangle(cr, round(vx - 0.5f * sz), round((float)cheight - y1 - 0.45f * sz), sz, sz, CPF_DIRECTION_UP, NULL);
668 }
669 }
670
671 return FALSE;
672}
673
674gint _list_find_by_position(gconstpointer a, gconstpointer b)
675{
677 gfloat position = *((gfloat *)b);
678 return (gint)((stop->position * 100.0) - (position * 100.0));
679}
680
682{
683 g_return_if_fail(!IS_NULL_PTR(gslider));
684
685 gslider->is_dragging = gslider->is_changed = gslider->do_reset = gslider->is_entered = 0;
686 gslider->timeout_handle = 0;
687 gslider->selected = gslider->positions == 1 ? 0 : -1;
688 gslider->active = -1;
690 gslider->is_resettable = FALSE;
691 gslider->is_entered = FALSE;
692 gslider->picker[0] = gslider->picker[1] = gslider->picker[2] = NAN;
695 gslider->markers_type = FREE_MARKERS;
696 gslider->colors = NULL;
697 gslider->min_spacing = 0;
698 for(int k = 0; k < gslider->positions; k++)
699 {
700 gslider->position[k] = 0.0;
701 gslider->resetvalue[k] = 0.0;
703 }
704}
705
706// Public functions for multivalue type
708{
709 assert(positions <= GRADIENT_SLIDER_MAX_POSITIONS);
710
712 gslider = g_object_new(_gradient_slider_get_type(), NULL);
713 gslider->positions = positions;
715 dt_gui_add_class(GTK_WIDGET(gslider), "dt_gslider_multivalue");
716 return (GtkWidget *)gslider;
717}
718
720{
721 GtkWidget *widget = GTK_WIDGET(dtgtk_gradient_slider_multivalue_new(positions));
722 if (name) gtk_widget_set_name(widget, name);
723
724 return widget;
725}
726
727GtkWidget *dtgtk_gradient_slider_multivalue_new_with_color(GdkRGBA start, GdkRGBA end, gint positions)
728{
729 assert(positions <= GRADIENT_SLIDER_MAX_POSITIONS);
730
732 gslider = g_object_new(_gradient_slider_get_type(), NULL);
733 gslider->positions = positions;
735
736 // Construct gradient start color
738 gc->position = 0.0;
739 memcpy(&gc->color, &start, sizeof(GdkRGBA));
740 gslider->colors = g_list_append(gslider->colors, gc);
741
742 // Construct gradient stop color
743 gc = (_gradient_slider_stop_t *)g_malloc(sizeof(_gradient_slider_stop_t));
744 gc->position = 1.0;
745 memcpy(&gc->color, &end, sizeof(GdkRGBA));
746 gslider->colors = g_list_append(gslider->colors, gc);
747 dt_gui_add_class(GTK_WIDGET(gslider), "dt_gslider_multivalue");
748 return (GtkWidget *)gslider;
749}
750
751GtkWidget *dtgtk_gradient_slider_multivalue_new_with_color_and_name(GdkRGBA start, GdkRGBA end, gint positions, gchar *name)
752{
753 GtkWidget *widget = GTK_WIDGET(dtgtk_gradient_slider_multivalue_new_with_color(start, end, positions));
754 if (name) gtk_widget_set_name(widget, name);
755
756 return widget;
757}
758
760 GdkRGBA color)
761{
762 g_return_if_fail(!IS_NULL_PTR(gslider));
763 const gfloat rawposition = gslider->scale_callback((GtkWidget *)gslider, position, GRADIENT_SLIDER_SET);
764 // First find color at position, if exists update color, otherwise create a new stop at position.
765 GList *current = g_list_find_custom(gslider->colors, (gpointer)&rawposition, _list_find_by_position);
766 if(!IS_NULL_PTR(current))
767 {
768 memcpy(&((_gradient_slider_stop_t *)current->data)->color, &color, sizeof(GdkRGBA));
769 }
770 else
771 {
772 // stop didn't exist lets add it
774 gc->position = rawposition;
775 memcpy(&gc->color, &color, sizeof(GdkRGBA));
776 gslider->colors = g_list_append(gslider->colors, gc);
777 }
778}
779
781{
782 g_return_if_fail(!IS_NULL_PTR(gslider));
783 g_list_free_full(gslider->colors, dt_free_gpointer);
784 gslider->colors = NULL;
785}
786
788{
789 return _gradient_slider_get_type();
790}
791
793{
794 assert(pos <= gslider->positions);
795
796 return gslider->scale_callback((GtkWidget *)gslider, gslider->position[pos], GRADIENT_SLIDER_GET);
797}
798
800{
801 g_return_if_fail(!IS_NULL_PTR(gslider));
802 for(int k = 0; k < gslider->positions; k++)
803 values[k] = gslider->scale_callback((GtkWidget *)gslider, gslider->position[k], GRADIENT_SLIDER_GET);
804}
805
807{
808 g_return_if_fail(!IS_NULL_PTR(gslider));
809 assert(pos <= gslider->positions);
810
811 gslider->position[pos] = CLAMP(gslider->scale_callback((GtkWidget *)gslider, value, GRADIENT_SLIDER_SET), 0.0, 1.0);
812 gslider->selected = gslider->positions == 1 ? 0 : -1;
813 if(!dt_gui_widgets_suppressed()) g_signal_emit_by_name(G_OBJECT(gslider), "value-changed");
814 gtk_widget_queue_draw(GTK_WIDGET(gslider));
815}
816
818{
819 g_return_if_fail(!IS_NULL_PTR(gslider));
820 g_return_if_fail(!IS_NULL_PTR(values));
821 for(int k = 0; k < gslider->positions; k++)
822 gslider->position[k] = CLAMP(gslider->scale_callback((GtkWidget *)gslider, values[k], GRADIENT_SLIDER_SET), 0.0, 1.0);
823 gslider->selected = gslider->positions == 1 ? 0 : -1;
824 if(!dt_gui_widgets_suppressed()) g_signal_emit_by_name(G_OBJECT(gslider), "value-changed");
825 gtk_widget_queue_draw(GTK_WIDGET(gslider));
826}
827
829{
830 g_return_if_fail(!IS_NULL_PTR(gslider));
831 assert(pos <= gslider->positions);
832
833 gslider->marker[pos] = mark;
834 gtk_widget_queue_draw(GTK_WIDGET(gslider));
835}
836
838{
839 g_return_if_fail(!IS_NULL_PTR(gslider));
840 for(int k = 0; k < gslider->positions; k++) gslider->marker[k] = markers[k];
841 gtk_widget_queue_draw(GTK_WIDGET(gslider));
842}
843
845 gint pos)
846{
847 g_return_if_fail(!IS_NULL_PTR(gslider));
848 assert(pos <= gslider->positions);
849
850 gslider->resetvalue[pos] = gslider->scale_callback((GtkWidget *)gslider, value, GRADIENT_SLIDER_SET);
851 gslider->is_resettable = TRUE;
852}
853
855{
856 assert(pos <= gslider->positions);
857
858 return gslider->scale_callback((GtkWidget *)gslider, gslider->resetvalue[pos], GRADIENT_SLIDER_GET);
859}
860
862{
863 g_return_if_fail(!IS_NULL_PTR(gslider));
864 for(int k = 0; k < gslider->positions; k++)
865 gslider->resetvalue[k] = gslider->scale_callback((GtkWidget *)gslider, values[k], GRADIENT_SLIDER_SET);
866 gslider->is_resettable = TRUE;
867}
868
870{
871 g_return_if_fail(!IS_NULL_PTR(gslider));
872 gslider->picker[0] = gslider->picker[1] = gslider->picker[2]
873 = gslider->scale_callback((GtkWidget *)gslider, value, GRADIENT_SLIDER_SET);
874 gtk_widget_queue_draw(GTK_WIDGET(gslider));
875}
876
878 gdouble min, gdouble max)
879{
880 g_return_if_fail(!IS_NULL_PTR(gslider));
881 gslider->picker[0] = gslider->scale_callback((GtkWidget *)gslider, mean, GRADIENT_SLIDER_SET);
882 gslider->picker[1] = gslider->scale_callback((GtkWidget *)gslider, min, GRADIENT_SLIDER_SET);
883 gslider->picker[2] = gslider->scale_callback((GtkWidget *)gslider, max, GRADIENT_SLIDER_SET);
884 gtk_widget_queue_draw(GTK_WIDGET(gslider));
885}
886
888{
889 g_return_val_if_fail(!IS_NULL_PTR(gslider), FALSE);
890 return gslider->is_dragging;
891}
892
894{
895 g_return_if_fail(!IS_NULL_PTR(gslider));
896 gslider->increment = value;
897}
898
900{
901 float (*old_callback)(GtkWidget*, float, int) = gslider->scale_callback;
902 float (*new_callback)(GtkWidget*, float, int) = (IS_NULL_PTR(callback) ? _default_linear_scale_callback : callback);
903 GtkWidget *self = (GtkWidget *)gslider;
904
905 if(old_callback == new_callback) return;
906
907 for(int k = 0; k < gslider->positions; k++)
908 {
909 gslider->position[k] = new_callback(self, old_callback(self, gslider->position[k], GRADIENT_SLIDER_GET), GRADIENT_SLIDER_SET);
910 gslider->resetvalue[k] = new_callback(self, old_callback(self, gslider->resetvalue[k], GRADIENT_SLIDER_GET), GRADIENT_SLIDER_SET);
911 }
912
913 for(int k = 0; k < 3; k++)
914 {
915 gslider->picker[k] = new_callback(self, old_callback(self, gslider->picker[k], GRADIENT_SLIDER_GET), GRADIENT_SLIDER_SET);
916 }
917
918 for(GList *current = gslider->colors; current; current = g_list_next(current))
919 {
920 _gradient_slider_stop_t *stop = (_gradient_slider_stop_t *)current->data;
921 stop->position = new_callback(self, old_callback(self, stop->position, GRADIENT_SLIDER_GET), GRADIENT_SLIDER_SET);
922 }
923
924 gslider->scale_callback = new_callback;
925 gtk_widget_queue_draw(GTK_WIDGET(gslider));
926}
927
928// Public functions for single value type
930{
932 dt_gui_add_class(gslider, "dt_gslider");
933 return gslider;
934}
935
937{
938 GtkWidget *widget = GTK_WIDGET(dtgtk_gradient_slider_new());
939 if (name) gtk_widget_set_name(widget, name);
940
941 return widget;
942}
943
945{
947 dt_gui_add_class(gslider, "dt_gslider");
948 return gslider;
949}
950
952{
953 GtkWidget *widget = GTK_WIDGET(dtgtk_gradient_slider_new_with_color(start, end));
954 if (name) gtk_widget_set_name(widget, name);
955
956 return widget;
957}
958
963
965{
966 return _gradient_slider_get_type();
967}
968
973
978
983
988
993
995{
996 g_return_if_fail(!IS_NULL_PTR(gslider));
997 gslider->picker[0] = gslider->picker[1] = gslider->picker[2]
998 = gslider->scale_callback((GtkWidget *)gslider, value, GRADIENT_SLIDER_SET);
999 gtk_widget_queue_draw(GTK_WIDGET(gslider));
1000}
1001
1003 gdouble min, gdouble max)
1004{
1005 g_return_if_fail(!IS_NULL_PTR(gslider));
1006 gslider->picker[0] = gslider->scale_callback((GtkWidget *)gslider, mean, GRADIENT_SLIDER_SET);
1007 gslider->picker[1] = gslider->scale_callback((GtkWidget *)gslider, min, GRADIENT_SLIDER_SET);
1008 gslider->picker[2] = gslider->scale_callback((GtkWidget *)gslider, max, GRADIENT_SLIDER_SET);
1009 gtk_widget_queue_draw(GTK_WIDGET(gslider));
1010}
1011
1013{
1014 g_return_val_if_fail(!IS_NULL_PTR(gslider), FALSE);
1015 return gslider->is_dragging;
1016}
1017
1019{
1020 g_return_if_fail(!IS_NULL_PTR(gslider));
1021 gslider->increment = value;
1022}
1023
1024// clang-format off
1025// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
1026// vim: shiftwidth=2 expandtab tabstop=2 cindent
1027// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
1028// clang-format on
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
int position()
static const float x
struct _GtkWidget GtkWidget
GtkWidget, opaque, spelled exactly as GTK spells it.
Definition colorspaces.h:98
static const float const float const float min
const float max
const float delta
GHashTable * selected
set of checked row labels, mirrored to conf on every change
static guint dt_keys_mainpad_alternatives(const guint key_val)
Remap keypad keys to usual mainpad ones.
Definition gdkkeys.h:118
GdkRGBA color[]
Definition geotagging.c:541
void dtgtk_gradient_slider_set_marker(GtkDarktableGradientSlider *gslider, gint mark)
void dtgtk_gradient_slider_multivalue_set_resetvalue(GtkDarktableGradientSlider *gslider, gdouble value, gint pos)
GType dtgtk_gradient_slider_multivalue_get_type()
static gdouble _screen_to_scale(GtkWidget *widget, gint screen)
void dtgtk_gradient_slider_multivalue_set_value(GtkDarktableGradientSlider *gslider, gdouble value, gint pos)
static gint _get_active_marker(GtkDarktableGradientSlider *gslider)
GtkWidget * dtgtk_gradient_slider_new_with_name(gchar *name)
static void _gradient_slider_get_preferred_width(GtkWidget *widget, gint *min_width, gint *nat_width)
void dtgtk_gradient_slider_set_resetvalue(GtkDarktableGradientSlider *gslider, gdouble value)
void dtgtk_gradient_slider_multivalue_set_values(GtkDarktableGradientSlider *gslider, gdouble *values)
static void _gradient_slider_set_defaults(GtkDarktableGradientSlider *gslider)
GtkWidget * dtgtk_gradient_slider_multivalue_new_with_color(GdkRGBA start, GdkRGBA end, gint positions)
static gboolean _gradient_slider_leave_notify_event(GtkWidget *widget, GdkEventCrossing *event)
static gboolean _gradient_slider_motion_notify(GtkWidget *widget, GdkEventMotion *event)
static void _gradient_slider_init(GtkDarktableGradientSlider *gslider)
GtkWidget * dtgtk_gradient_slider_new_with_color_and_name(GdkRGBA start, GdkRGBA end, gchar *name)
GtkWidget * dtgtk_gradient_slider_new_with_color(GdkRGBA start, GdkRGBA end)
static void _clamp_marker(GtkDarktableGradientSlider *gslider, const gint selected)
void dtgtk_gradient_slider_set_value(GtkDarktableGradientSlider *gslider, gdouble value)
void dtgtk_gradient_slider_multivalue_set_increment(GtkDarktableGradientSlider *gslider, gdouble value)
static gdouble _slider_move(GtkWidget *widget, gint k, gdouble value, gint direction)
gint _list_find_by_position(gconstpointer a, gconstpointer b)
static gboolean _gradient_slider_postponed_value_change(gpointer data)
static gint _get_active_marker_from_screen(GtkWidget *widget, const gdouble x, const gdouble y)
void dtgtk_gradient_slider_multivalue_set_picker(GtkDarktableGradientSlider *gslider, gdouble value)
GtkWidget * dtgtk_gradient_slider_new()
gdouble dtgtk_gradient_slider_multivalue_get_value(GtkDarktableGradientSlider *gslider, gint pos)
G_DEFINE_TYPE(GtkDarktableGradientSlider, _gradient_slider, GTK_TYPE_DRAWING_AREA)
void dtgtk_gradient_slider_multivalue_set_markers(GtkDarktableGradientSlider *gslider, gint *markers)
static gint _scale_to_screen(GtkWidget *widget, gdouble scale)
static void _gradient_slider_class_init(GtkDarktableGradientSliderClass *klass)
void dtgtk_gradient_slider_set_picker(GtkDarktableGradientSlider *gslider, gdouble value)
@ LAST_SIGNAL
@ VALUE_RESET
@ VALUE_CHANGED
void dtgtk_gradient_slider_multivalue_set_picker_meanminmax(GtkDarktableGradientSlider *gslider, gdouble mean, gdouble min, gdouble max)
void dtgtk_gradient_slider_multivalue_get_values(GtkDarktableGradientSlider *gslider, gdouble *values)
static gboolean _gradient_slider_add_delta_internal(GtkWidget *widget, gdouble delta, guint state, const gint selected)
void dtgtk_gradient_slider_multivalue_clear_stops(GtkDarktableGradientSlider *gslider)
static guint _signals[LAST_SIGNAL]
static gboolean _gradient_slider_button_press(GtkWidget *widget, GdkEventButton *event)
gboolean dtgtk_gradient_slider_multivalue_is_dragging(GtkDarktableGradientSlider *gslider)
static gboolean _gradient_slider_draw(GtkWidget *widget, cairo_t *cr)
#define parent_class
static gboolean _gradient_slider_scroll_event(GtkWidget *widget, GdkEventScroll *event)
static gboolean _gradient_slider_button_release(GtkWidget *widget, GdkEventButton *event)
GtkWidget * dtgtk_gradient_slider_multivalue_new(gint positions)
gboolean dtgtk_gradient_slider_is_dragging(GtkDarktableGradientSlider *gslider)
gdouble dtgtk_gradient_slider_get_value(GtkDarktableGradientSlider *gslider)
gdouble dtgtk_gradient_slider_get_resetvalue(GtkDarktableGradientSlider *gslider)
void dtgtk_gradient_slider_set_picker_meanminmax(GtkDarktableGradientSlider *gslider, gdouble mean, gdouble min, gdouble max)
static gboolean _gradient_slider_enter_notify_event(GtkWidget *widget, GdkEventCrossing *event)
void dtgtk_gradient_slider_set_stop(GtkDarktableGradientSlider *gslider, gfloat position, GdkRGBA color)
static gboolean _gradient_slider_key_press_event(GtkWidget *widget, GdkEventKey *event)
GtkWidget * dtgtk_gradient_slider_multivalue_new_with_color_and_name(GdkRGBA start, GdkRGBA end, gint positions, gchar *name)
#define DTGTK_GRADIENT_SLIDER_DEFAULT_INCREMENT
void dtgtk_gradient_slider_multivalue_set_scale_callback(GtkDarktableGradientSlider *gslider, float(*callback)(GtkWidget *self, float value, int dir))
GType dtgtk_gradient_slider_get_type()
void dtgtk_gradient_slider_set_increment(GtkDarktableGradientSlider *gslider, gdouble value)
void dtgtk_gradient_slider_multivalue_set_stop(GtkDarktableGradientSlider *gslider, gfloat position, GdkRGBA color)
static void _gradient_slider_destroy(GtkWidget *widget)
gdouble dtgtk_gradient_slider_multivalue_get_resetvalue(GtkDarktableGradientSlider *gslider, gint pos)
static gboolean _test_if_marker_is_upper_or_down(const gint marker, const gboolean up)
void dtgtk_gradient_slider_multivalue_set_marker(GtkDarktableGradientSlider *gslider, gint mark, gint pos)
void dtgtk_gradient_slider_multivalue_set_resetvalues(GtkDarktableGradientSlider *gslider, gdouble *values)
static gint _get_active_marker_internal(GtkWidget *widget, const gdouble x, const gboolean up)
static gdouble _get_position_from_screen(GtkWidget *widget, const gdouble x)
static void _gradient_slider_get_preferred_height(GtkWidget *widget, gint *min_height, gint *nat_height)
static float _default_linear_scale_callback(GtkWidget *self, float value, int dir)
GtkWidget * dtgtk_gradient_slider_multivalue_new_with_name(gint positions, gchar *name)
#define DTGTK_GRADIENT_SLIDER(obj)
#define GRADIENT_SLIDER_MAX_POSITIONS
@ GRADIENT_SLIDER_MARGINS_DEFAULT
@ GRADIENT_SLIDER_MARKER_UPPER_OPEN
@ GRADIENT_SLIDER_MARKER_UPPER_FILLED
@ GRADIENT_SLIDER_MARKER_LOWER_OPEN
@ GRADIENT_SLIDER_MARKER_UPPER_FILLED_BIG
@ GRADIENT_SLIDER_MARKER_LOWER_OPEN_BIG
@ GRADIENT_SLIDER_MARKER_LOWER_FILLED
@ GRADIENT_SLIDER_MARKER_UPPER_OPEN_BIG
@ GRADIENT_SLIDER_MARKER_LOWER_FILLED_BIG
@ GRADIENT_SLIDER_GET
@ GRADIENT_SLIDER_SET
@ MOVE_RIGHT
@ MOVE_LEFT
@ FREE_MARKERS
@ PROPORTIONAL_MARKERS
#define DTGTK_IS_GRADIENT_SLIDER(obj)
float *const restrict const size_t k
#define IS_NULL_PTR(p)
C is way too permissive with !=, == and if(var) checks, which can mean too many things depending on w...
Definition macros.h:96
static void dt_free_gpointer(gpointer ptr)
g_free() one pointer, with the signature GDestroyNotify wants.
Definition mem_alloc.h:184
char * key
const char * name
Definition pdf.h:90
static const dt_aligned_pixel_simd_t value
Definition simd.h:144
const float uint32_t state[4]
float(* scale_callback)(GtkWidget *, float, int)
gboolean dt_gui_get_scroll_unit_delta(const GdkEventScroll *event, int *delta)
GtkWidget * dt_widget_scroll_focus(void)
GdkEventMask dt_widget_scroll_mask(void)
void dt_widget_set_scroll_focus(GtkWidget *widget)
gboolean dt_gui_widgets_suppressed(void)
void dt_gui_add_class(GtkWidget *widget, const gchar *class_name)
void dtgtk_cairo_paint_triangle(cairo_t *cr, gint x, int y, gint w, gint h, gint flags, void *data)
void dtgtk_cairo_paint_solid_triangle(cairo_t *cr, gint x, int y, gint w, gint h, gint flags, void *data)
@ CPF_DIRECTION_UP
@ CPF_DIRECTION_DOWN