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 <stdlib.h>
41#include <string.h>
42
43#include "common/darktable.h"
44#include "gui/gdkkeys.h"
45#include "common/math.h"
46#include "develop/develop.h"
47#include "gradientslider.h"
48#include "gui/gtk.h"
49
50
51#define DTGTK_GRADIENT_SLIDER_VALUE_CHANGED_DELAY_MAX 50
52#define DTGTK_GRADIENT_SLIDER_VALUE_CHANGED_DELAY_MIN 10
53#define DTGTK_GRADIENT_SLIDER_DEFAULT_INCREMENT 0.01
54
55// define GTypes
56G_DEFINE_TYPE(GtkDarktableGradientSlider, _gradient_slider, GTK_TYPE_DRAWING_AREA);
57#define parent_class _gradient_slider_parent_class
58
59// Class overrides
60static void _gradient_slider_get_preferred_height(GtkWidget *widget, gint *min_height, gint *nat_height);
61static void _gradient_slider_get_preferred_width(GtkWidget *widget, gint *min_width, gint *nat_width);
62static gboolean _gradient_slider_draw(GtkWidget *widget, cairo_t *cr);
63static void _gradient_slider_destroy(GtkWidget *widget);
64
65// Events
66static gboolean _gradient_slider_enter_notify_event(GtkWidget *widget, GdkEventCrossing *event);
67static gboolean _gradient_slider_button_press(GtkWidget *widget, GdkEventButton *event);
68static gboolean _gradient_slider_button_release(GtkWidget *widget, GdkEventButton *event);
69static gboolean _gradient_slider_motion_notify(GtkWidget *widget, GdkEventMotion *event);
70static gboolean _gradient_slider_scroll_event(GtkWidget *widget, GdkEventScroll *event);
71static gboolean _gradient_slider_key_press_event(GtkWidget *widget, GdkEventKey *event);
72
73enum
74{
78};
79
80static guint _signals[LAST_SIGNAL] = { 0 };
81
82static gboolean _gradient_slider_postponed_value_change(gpointer data)
83{
84 if(!GTK_IS_WIDGET(data)) return 0;
85
86 if(DTGTK_GRADIENT_SLIDER(data)->is_changed)
87 {
88 g_signal_emit_by_name(G_OBJECT(data), "value-changed");
89 DTGTK_GRADIENT_SLIDER(data)->is_changed = FALSE;
90 }
91
92 DTGTK_GRADIENT_SLIDER(data)->timeout_handle = 0;
93 return G_SOURCE_REMOVE; // This is called by the gtk mainloop and is threadsafe
94}
95
96static inline gboolean _test_if_marker_is_upper_or_down(const gint marker, const gboolean up)
97{
98 if(up && (marker == GRADIENT_SLIDER_MARKER_LOWER_OPEN ||
102 return FALSE;
103 else if(!up && (marker == GRADIENT_SLIDER_MARKER_UPPER_OPEN ||
107 return FALSE;
108 else
109 return TRUE; // must be a DOUBLE
110}
111
112static inline gdouble _screen_to_scale(GtkWidget *widget, gint screen)
113{
115
116 GtkAllocation allocation;
117 gtk_widget_get_allocation(widget, &allocation);
118 return ((gdouble)screen - gslider->margin_left) / ((gdouble)allocation.width - gslider->margin_left - gslider->margin_right);
119}
120
121static inline gint _scale_to_screen(GtkWidget *widget, gdouble scale)
122{
124
125 GtkAllocation allocation;
126 gtk_widget_get_allocation(widget, &allocation);
127 return (gint)(scale * (allocation.width - gslider->margin_left - gslider->margin_right) + gslider->margin_left);
128}
129
130static inline gdouble _get_position_from_screen(GtkWidget *widget, const gdouble x)
131{
133 gdouble position = roundf(_screen_to_scale(widget, x) / gslider->increment) * gslider->increment;
134 return CLAMP(position, 0., 1.);
135}
136
138{
139 return (gslider->selected >= 0) ? gslider->selected : gslider->active;
140}
141
142static inline void _clamp_marker(GtkDarktableGradientSlider *gslider, const gint selected)
143{
144 g_return_if_fail(!IS_NULL_PTR(gslider));
145
146 const gdouble min = (selected == 0) ? 0.0f : gslider->position[selected - 1];
147 const gdouble max = (selected == gslider->positions - 1) ? 1.0f : gslider->position[selected + 1];
148 gslider->position[selected] = CLAMP(gslider->position[selected], min, max);
149}
150
151static gint _get_active_marker_internal(GtkWidget *widget, const gdouble x, const gboolean up)
152{
154 gint lselected = -1;
155 const gdouble newposition = _get_position_from_screen(widget, x);
156
157 assert(gslider->positions > 0);
158
159 for(int k = 0; k < gslider->positions; k++)
160 if(_test_if_marker_is_upper_or_down(gslider->marker[k], up))
161 {
162 if(lselected < 0) lselected = k;
163 if(fabs(newposition - gslider->position[k]) < fabs(newposition - gslider->position[lselected]))
164 lselected = k;
165 }
166
167 return lselected;
168}
169
170static gint _get_active_marker_from_screen(GtkWidget *widget, const gdouble x, const gdouble y)
171{
172 GtkAllocation allocation;
173 gtk_widget_get_allocation(widget, &allocation);
174
175 gboolean up = (y <= allocation.height / 2.f);
176 gint lselected = _get_active_marker_internal(widget, x, up);
177 if(lselected < 0) lselected = _get_active_marker_internal(widget, x, !up);
178
179 assert(lselected >= 0);
180
181 return lselected;
182}
183
184static gdouble _slider_move(GtkWidget *widget, gint k, gdouble value, gint direction)
185{
186 g_return_val_if_fail(DTGTK_IS_GRADIENT_SLIDER(widget), value);
187
189
190 gdouble newvalue = value;
191 gdouble leftnext, rightnext, ms;
192
193 switch(gslider->markers_type)
194 {
195 case FREE_MARKERS:
196 {
197 leftnext = (k == 0) ? 0.0f : gslider->position[k - 1];
198 rightnext = (k == gslider->positions - 1) ? 1.0f : gslider->position[k + 1];
199 ms = gslider->min_spacing;
200 switch(direction)
201 {
202 case MOVE_LEFT:
203 if(value < leftnext + ms)
204 newvalue = (k == 0) ? fmax(value, 0.0f) : _slider_move(widget, k - 1, value - ms, direction) + ms;
205 break;
206 case MOVE_RIGHT:
207 if(value > rightnext - ms)
208 newvalue = (k == gslider->positions - 1) ? fmin(value, 1.0f) : _slider_move(widget, k + 1, value + ms, direction) - ms;
209 break;
210 }
211 break;
212 }
214 {
215 ms = fmax(gslider->min_spacing, 1.0e-6);
216 const double vmin = ((k == 0) ? 0.0f : gslider->position[0]);
217 const double vmax = ((k == gslider->positions - 1) ? 1.0f : gslider->position[gslider->positions - 1]);
218
219 newvalue = CLAMP(value, vmin + ms * k, vmax - ms * (gslider->positions - 1 - k));
220 const double rl = (newvalue - gslider->position[0]) / (gslider->position[k] - gslider->position[0]);
221 const double rh = (gslider->position[gslider->positions - 1] - newvalue) /
222 (gslider->position[gslider->positions - 1] - gslider->position[k]);
223
224 for(int i = 1; i < k; i++)
225 gslider->position[i] = rl * (gslider->position[i] - gslider->position[0]) + gslider->position[0];
226
227 for(int i = k + 1; i < gslider->positions; i++)
228 gslider->position[i] = gslider->position[gslider->positions - 1] -
229 rh * (gslider->position[gslider->positions - 1] - gslider->position[i]);
230 break;
231 }
232 }
233 gslider->position[k] = newvalue;
234 return newvalue;
235}
236
237static gboolean _gradient_slider_add_delta_internal(GtkWidget *widget, gdouble delta, guint state, const gint selected)
238{
239 g_return_val_if_fail(DTGTK_IS_GRADIENT_SLIDER(widget), TRUE);
240
242
243 if(selected == -1) return TRUE;
244
245 gslider->position[selected] = gslider->position[selected] + delta;
246 _clamp_marker(gslider, selected);
247 gslider->is_changed = TRUE;
248
249 gtk_widget_queue_draw(widget);
250
251 if(gslider->timeout_handle)
252 {
253 g_source_remove(gslider->timeout_handle);
254 gslider->timeout_handle = 0;
255 }
256 gslider->timeout_handle = g_timeout_add(350, _gradient_slider_postponed_value_change, widget);
257
258 return TRUE;
259}
260
261static float _default_linear_scale_callback(GtkWidget *self, float value, int dir)
262{
263 // regardless of dir: input <-> output
264 return value;
265}
266
267static gboolean _gradient_slider_enter_notify_event(GtkWidget *widget, GdkEventCrossing *event)
268{
269 g_return_val_if_fail(DTGTK_IS_GRADIENT_SLIDER(widget), FALSE);
270
272 gtk_widget_set_state_flags(widget, GTK_STATE_FLAG_PRELIGHT, TRUE);
273 gslider->is_entered = TRUE;
274 gtk_widget_queue_draw(widget);
275 return FALSE;
276}
277
278static gboolean _gradient_slider_leave_notify_event(GtkWidget *widget, GdkEventCrossing *event)
279{
280 g_return_val_if_fail(DTGTK_IS_GRADIENT_SLIDER(widget), FALSE);
281
283 if(!(gslider->is_dragging))
284 {
285 gtk_widget_set_state_flags(widget, GTK_STATE_FLAG_NORMAL, TRUE);
286 gslider->is_entered = FALSE;
287 gslider->active = -1;
288 gtk_widget_queue_draw(widget);
289 }
290 return FALSE;
291}
292
293static gboolean _gradient_slider_button_press(GtkWidget *widget, GdkEventButton *event)
294{
295 g_return_val_if_fail(DTGTK_IS_GRADIENT_SLIDER(widget), FALSE);
296
299
300 // reset slider
301 if(event->button == 1 && event->type == GDK_2BUTTON_PRESS && gslider->is_resettable)
302 {
303 gslider->is_dragging = FALSE;
304 gslider->do_reset = TRUE;
305 gslider->selected = -1;
306 for(int k = 0; k < gslider->positions; k++) gslider->position[k] = gslider->resetvalue[k];
307 gtk_widget_queue_draw(widget);
308 g_signal_emit_by_name(G_OBJECT(widget), "value-changed");
309 g_signal_emit_by_name(G_OBJECT(widget), "value-reset");
310 }
311 else if((event->button == 1 || event->button == 3) && event->type == GDK_BUTTON_PRESS)
312 {
313 const gint lselected = _get_active_marker_from_screen(widget, event->x, event->y);
314
315 assert(lselected >= 0);
316 assert(lselected <= gslider->positions - 1);
317
318 if(event->button == 1) // left mouse button : select and start dragging
319 {
320 gslider->selected = lselected;
321 gslider->do_reset = FALSE;
322
323 const gdouble newposition = _get_position_from_screen(widget, event->x);
324 const gint direction = gslider->position[gslider->selected] <= newposition ? MOVE_RIGHT : MOVE_LEFT;
325
326 _slider_move(widget, gslider->selected, newposition, direction);
327
328 gslider->is_changed = TRUE;
329 gslider->is_dragging = TRUE;
330 }
331 else if(gslider->positions > 1) // right mouse button: switch on/off selection (only if we have more than one marker)
332 {
333 gslider->is_dragging = FALSE;
334 gslider->do_reset = FALSE;
335
336 if(gslider->selected != lselected)
337 gslider->selected = lselected;
338 else
339 gslider->selected = -1;
340
341 gtk_widget_queue_draw(widget);
342 }
343 }
344
345 return TRUE;
346}
347
348static gboolean _gradient_slider_motion_notify(GtkWidget *widget, GdkEventMotion *event)
349{
350 g_return_val_if_fail(DTGTK_IS_GRADIENT_SLIDER(widget), FALSE);
351
353
354 if(gslider->is_dragging == TRUE && gslider->selected != -1 && gslider->do_reset == FALSE)
355 {
356 const gdouble newposition = _get_position_from_screen(widget, event->x);
357 const gint direction = gslider->position[gslider->selected] <= newposition ? MOVE_RIGHT : MOVE_LEFT;
358
359 _slider_move(widget, gslider->selected, newposition, direction);
360
361 gslider->is_changed = TRUE;
362
363 gtk_widget_queue_draw(widget);
364
365 // timeout_handle should always be zero here, but check just in case
366 if(gslider->timeout_handle)
367 {
368 g_source_remove(gslider->timeout_handle);
369 gslider->timeout_handle = 0;
370 }
371 gslider->timeout_handle = g_timeout_add(350, _gradient_slider_postponed_value_change, widget);
372 }
373 else
374 {
375 gslider->active = _get_active_marker_from_screen(widget, event->x, event->y);
376 }
377
378 if(gslider->selected != -1) gtk_widget_grab_focus(widget);
379
380 return TRUE;
381}
382
383static gboolean _gradient_slider_button_release(GtkWidget *widget, GdkEventButton *event)
384{
385 g_return_val_if_fail(DTGTK_IS_GRADIENT_SLIDER(widget), FALSE);
386
388 const gint selected = _get_active_marker(gslider);
389
390 if(event->button == 1 && selected != -1 && gslider->do_reset == FALSE)
391 {
392 // First get some dimension info
393 gslider->is_changed = TRUE;
394 const gdouble newposition = _get_position_from_screen(widget, event->x);
395 const gint direction = gslider->position[selected] <= newposition ? MOVE_RIGHT : MOVE_LEFT;
396
397 _slider_move(widget, selected, newposition, direction);
398
399 gtk_widget_queue_draw(widget);
401
402 gslider->is_dragging = FALSE;
403 g_signal_emit_by_name(G_OBJECT(widget), "value-changed");
404 }
405 return TRUE;
406}
407
408static gboolean _gradient_slider_scroll_event(GtkWidget *widget, GdkEventScroll *event)
409{
410 g_return_val_if_fail(DTGTK_IS_GRADIENT_SLIDER(widget), TRUE);
411
412 if(darktable.gui->has_scroll_focus != widget) return FALSE;
413
415 const gint selected = _get_active_marker(gslider);
416 if(selected == -1) return TRUE;
417
418 gtk_widget_grab_focus(widget);
419
420 int delta_y;
421 if(dt_gui_get_scroll_unit_delta(event, &delta_y))
422 {
423 gdouble delta = delta_y * -gslider->increment;
424 return _gradient_slider_add_delta_internal(widget, delta, event->state, selected);
425 }
426
427 return TRUE;
428}
429
430static gboolean _gradient_slider_key_press_event(GtkWidget *widget, GdkEventKey *event)
431{
432 g_return_val_if_fail(DTGTK_IS_GRADIENT_SLIDER(widget), TRUE);
433
435
436 int handled = FALSE;
437 float delta = -gslider->increment;
438 guint key = dt_keys_mainpad_alternatives(event->keyval);
439
440 switch(key)
441 {
442 case GDK_KEY_Up:
443 case GDK_KEY_Right:
444 delta = gslider->increment;
445 case GDK_KEY_Down:
446 case GDK_KEY_Left:
447 handled = TRUE;
448 }
449
450 if(!handled) return FALSE;
451
452 const gint selected = _get_active_marker(gslider);
453 if(selected == -1) return TRUE;
454
455 return _gradient_slider_add_delta_internal(widget, delta, event->state, selected);
456}
457
459{
460 GtkWidgetClass *widget_class = (GtkWidgetClass *)klass;
461
462 widget_class->get_preferred_height = _gradient_slider_get_preferred_height;
463 widget_class->get_preferred_width = _gradient_slider_get_preferred_width;
464 widget_class->draw = _gradient_slider_draw;
465 widget_class->destroy = _gradient_slider_destroy;
466
467 widget_class->enter_notify_event = _gradient_slider_enter_notify_event;
468 widget_class->leave_notify_event = _gradient_slider_leave_notify_event;
469 widget_class->button_press_event = _gradient_slider_button_press;
470 widget_class->button_release_event = _gradient_slider_button_release;
471 widget_class->motion_notify_event = _gradient_slider_motion_notify;
472 widget_class->scroll_event = _gradient_slider_scroll_event;
473 widget_class->key_press_event = _gradient_slider_key_press_event;
474
475 _signals[VALUE_CHANGED] = g_signal_new("value-changed", G_TYPE_FROM_CLASS(klass), G_SIGNAL_RUN_LAST, 0,
476 NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
477 _signals[VALUE_RESET] = g_signal_new("value-reset", G_TYPE_FROM_CLASS(klass), G_SIGNAL_RUN_LAST, 0,
478 NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
479}
480
482{
483 g_return_if_fail(!IS_NULL_PTR(gslider));
484
485 GtkWidget *widget = GTK_WIDGET(gslider);
486 gtk_widget_add_events(widget,
487 GDK_EXPOSURE_MASK |
488 GDK_BUTTON_PRESS_MASK |
489 GDK_BUTTON_RELEASE_MASK |
490 GDK_ENTER_NOTIFY_MASK |
491 GDK_LEAVE_NOTIFY_MASK |
492 GDK_KEY_PRESS_MASK |
493 GDK_KEY_RELEASE_MASK |
494 GDK_POINTER_MOTION_MASK |
496
497 gtk_widget_set_has_window(widget, TRUE);
498 gtk_widget_set_can_focus(widget, TRUE);
499}
500
501static void _gradient_slider_get_preferred_height(GtkWidget *widget, gint *min_height, gint *nat_height)
502{
503 g_return_if_fail(!IS_NULL_PTR(widget));
504
505 GtkStyleContext *context = gtk_widget_get_style_context(widget);
506 GtkStateFlags state = gtk_widget_get_state_flags(widget);
507
508 GtkBorder margin, border, padding;
509 int css_min_height;
510 gtk_style_context_get (context, state, "min-height", &css_min_height, NULL);
511 gtk_style_context_get_margin(context, state, &margin);
512 gtk_style_context_get_border(context, state, &border);
513 gtk_style_context_get_padding(context, state, &padding);
514 *min_height = *nat_height = css_min_height + padding.top + padding.bottom + border.top + border.bottom + margin.top + margin.bottom;
515}
516
517static void _gradient_slider_get_preferred_width(GtkWidget *widget, gint *min_width, gint *nat_width)
518{
519 g_return_if_fail(DTGTK_IS_GRADIENT_SLIDER(widget));
520
521 GtkStyleContext *context = gtk_widget_get_style_context(widget);
522 GtkStateFlags state = gtk_widget_get_state_flags(widget);
523
524 GtkBorder margin, border, padding;
525 int css_min_width;
526 gtk_style_context_get (context, state, "min-width", &css_min_width, NULL);
527 gtk_style_context_get_margin(context, state, &margin);
528 gtk_style_context_get_border(context, state, &border);
529 gtk_style_context_get_padding(context, state, &padding);
530 *min_width = *nat_width = css_min_width + padding.left + padding.right + border.left + border.right + margin.left + margin.right;
531
532 DTGTK_GRADIENT_SLIDER(widget)->margin_left = padding.left + border.left + margin.left;
533 DTGTK_GRADIENT_SLIDER(widget)->margin_right = padding.right + border.right + margin.right;
534}
535
537{
538 g_return_if_fail(DTGTK_IS_GRADIENT_SLIDER(widget));
539
541
542 if(gslider->timeout_handle)
543 {
544 g_source_remove(gslider->timeout_handle);
545 gslider->timeout_handle = 0;
546 }
547
548 if(gslider->colors)
549 g_list_free_full(gslider->colors, dt_free_gpointer);
550
551 gslider->colors = NULL;
552
553 GTK_WIDGET_CLASS(parent_class)->destroy(widget);
554}
555
556static gboolean _gradient_slider_draw(GtkWidget *widget, cairo_t *cr)
557{
558 g_return_val_if_fail(DTGTK_IS_GRADIENT_SLIDER(widget), FALSE);
560
561 assert(gslider->position > 0);
562
563 GtkStyleContext *context = gtk_widget_get_style_context(widget);
564 GtkStateFlags state = gtk_widget_get_state_flags(widget);
565
566 GdkRGBA color;
567 gtk_style_context_get_color(context, state, &color);
568
569 GtkAllocation allocation;
570 GtkBorder margin, border, padding;
571 gtk_widget_get_allocation(widget, &allocation);
572 gtk_style_context_get_margin(context, state, &margin);
573 gtk_style_context_get_border(context, state, &border);
574 gtk_style_context_get_padding(context, state, &padding);
575
576 // Begin cairo drawing
577 // for frame and background, we remove css margin from allocation
578 int startx = margin.left;
579 int starty = margin.top;
580 int cwidth = allocation.width - margin.left - margin.right;
581 int cheight = allocation.height - margin.top - margin.bottom;
582 gtk_render_background(context, cr, startx, starty, cwidth, cheight);
583 gtk_render_frame(context, cr, startx, starty, cwidth, cheight);
584
585 // then we draw the content
586 startx += padding.left + border.left;
587 starty += padding.top + border.top;
588 cwidth -= padding.left + padding.right + border.left + border.right;
589 cheight -= padding.top + padding.bottom + border.top + border.bottom;
590 const int y1 = round(0.3f * cheight);
591 const int gheight = cheight - 2 * y1;
592
593 // First build the cairo gradient and then fill the gradient
594 if(gslider->colors)
595 {
596 cairo_pattern_t *gradient = cairo_pattern_create_linear(0, 0, cwidth, 0);
597 for(GList *current = gslider->colors; current; current = g_list_next(current))
598 {
599 _gradient_slider_stop_t *stop = (_gradient_slider_stop_t *)current->data;
600 cairo_pattern_add_color_stop_rgba(gradient, stop->position, stop->color.red, stop->color.green,
601 stop->color.blue, stop->color.alpha);
602 }
603 if(!IS_NULL_PTR(gradient)) // Do we got a gradient, lets draw it
604 {
605 cairo_set_line_width(cr, 0.1);
606 cairo_set_line_cap(cr, CAIRO_LINE_CAP_ROUND);
607 cairo_translate(cr, 0, starty);
608 cairo_set_source(cr, gradient);
609 cairo_rectangle(cr, startx, y1, cwidth, gheight);
610 cairo_fill(cr);
611 cairo_stroke(cr);
612 cairo_pattern_destroy(gradient);
613 }
614 }
615
616 // Lets draw position arrows
617 cairo_set_source_rgba(cr, color.red, color.green, color.blue, 1.0);
618
619 // do we have a picker value to draw?
620 if(!isnan(gslider->picker[0]))
621 {
622 int vx_min = _scale_to_screen(widget, CLAMP(gslider->picker[1], 0.0, 1.0));
623 int vx_max = _scale_to_screen(widget, CLAMP(gslider->picker[2], 0.0, 1.0));
624 int vx_avg = _scale_to_screen(widget, CLAMP(gslider->picker[0], 0.0, 1.0));
625
626 cairo_set_source_rgba(cr, color.red, color.green, color.blue, 0.33);
627
628 cairo_rectangle(cr, vx_min, y1, fmax((float)vx_max - vx_min, 0.0f), gheight);
629 cairo_fill(cr);
630
631 cairo_set_source_rgba(cr, color.red, color.green, color.blue, 1.0);
632
633 cairo_move_to(cr, vx_avg, y1);
634 cairo_rel_line_to(cr, 0, gheight);
635 cairo_set_antialias(cr, CAIRO_ANTIALIAS_NONE);
636 cairo_set_line_width(cr, 1.0);
637 cairo_stroke(cr);
638 }
639
640 for(int k = 0; k < gslider->positions; k++)
641 {
642 const int vx = _scale_to_screen(widget, gslider->position[k]);
643 const int mk = gslider->marker[k];
644 const int sz = round((mk & (1 << 3)) ? 1.9f * y1 : 1.4f * y1); // big or small marker?
645
646 if(k == gslider->selected && gslider->is_entered) // highlight the active marker
647 cairo_set_source_rgba(cr, color.red, color.green, color.blue, 1.0);
648 else
649 cairo_set_source_rgba(cr, color.red * 0.8, color.green * 0.8, color.blue * 0.8, 1.0);
650
651 cairo_set_antialias(cr, CAIRO_ANTIALIAS_DEFAULT);
652
653 if(mk & 0x04) /* upper arrow */
654 {
655 if(mk & 0x01) /* filled */
656 dtgtk_cairo_paint_solid_triangle(cr, round(vx - 0.5f * sz), round((float)y1 - 0.55f * sz), sz, sz, CPF_DIRECTION_DOWN, NULL);
657 else
658 dtgtk_cairo_paint_triangle(cr, round(vx - 0.5f * sz), round((float)y1 - 0.55f * sz), sz, sz, CPF_DIRECTION_DOWN, NULL);
659 }
660
661 if(mk & 0x02) /* lower arrow */
662 {
663 if(mk & 0x01) /* filled */
664 dtgtk_cairo_paint_solid_triangle(cr, round(vx - 0.5f * sz), round((float)cheight - y1 - 0.45f * sz), sz, sz, CPF_DIRECTION_UP, NULL);
665 else
666 dtgtk_cairo_paint_triangle(cr, round(vx - 0.5f * sz), round((float)cheight - y1 - 0.45f * sz), sz, sz, CPF_DIRECTION_UP, NULL);
667 }
668 }
669
670 return FALSE;
671}
672
673gint _list_find_by_position(gconstpointer a, gconstpointer b)
674{
676 gfloat position = *((gfloat *)b);
677 return (gint)((stop->position * 100.0) - (position * 100.0));
678}
679
681{
682 g_return_if_fail(!IS_NULL_PTR(gslider));
683
684 gslider->is_dragging = gslider->is_changed = gslider->do_reset = gslider->is_entered = 0;
685 gslider->timeout_handle = 0;
686 gslider->selected = gslider->positions == 1 ? 0 : -1;
687 gslider->active = -1;
689 gslider->is_resettable = FALSE;
690 gslider->is_entered = FALSE;
691 gslider->picker[0] = gslider->picker[1] = gslider->picker[2] = NAN;
694 gslider->markers_type = FREE_MARKERS;
695 gslider->colors = NULL;
696 gslider->min_spacing = 0;
697 for(int k = 0; k < gslider->positions; k++)
698 {
699 gslider->position[k] = 0.0;
700 gslider->resetvalue[k] = 0.0;
702 }
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(!darktable.gui->reset) 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(!darktable.gui->reset) 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
929// Public functions for single value type
931{
933 dt_gui_add_class(gslider, "dt_gslider");
934 return gslider;
935}
936
938{
939 GtkWidget *widget = GTK_WIDGET(dtgtk_gradient_slider_new());
940 if (name) gtk_widget_set_name(widget, name);
941
942 return widget;
943}
944
946{
948 dt_gui_add_class(gslider, "dt_gslider");
949 return gslider;
950}
951
953{
954 GtkWidget *widget = GTK_WIDGET(dtgtk_gradient_slider_new_with_color(start, end));
955 if (name) gtk_widget_set_name(widget, name);
956
957 return widget;
958}
959
964
966{
967 return _gradient_slider_get_type();
968}
969
974
979
984
989
994
996{
997 g_return_if_fail(!IS_NULL_PTR(gslider));
998 gslider->picker[0] = gslider->picker[1] = gslider->picker[2]
999 = gslider->scale_callback((GtkWidget *)gslider, value, GRADIENT_SLIDER_SET);
1000 gtk_widget_queue_draw(GTK_WIDGET(gslider));
1001}
1002
1004 gdouble min, gdouble max)
1005{
1006 g_return_if_fail(!IS_NULL_PTR(gslider));
1007 gslider->picker[0] = gslider->scale_callback((GtkWidget *)gslider, mean, GRADIENT_SLIDER_SET);
1008 gslider->picker[1] = gslider->scale_callback((GtkWidget *)gslider, min, GRADIENT_SLIDER_SET);
1009 gslider->picker[2] = gslider->scale_callback((GtkWidget *)gslider, max, GRADIENT_SLIDER_SET);
1010 gtk_widget_queue_draw(GTK_WIDGET(gslider));
1011}
1012
1014{
1015 g_return_val_if_fail(!IS_NULL_PTR(gslider), FALSE);
1016 return gslider->is_dragging;
1017}
1018
1020{
1021 g_return_if_fail(!IS_NULL_PTR(gslider));
1022 gslider->increment = value;
1023}
1024
1025
1026// clang-format off
1027// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
1028// vim: shiftwidth=2 expandtab tabstop=2 cindent
1029// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
1030// clang-format on
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
int position()
static const float const float const float min
const float max
const float delta
char * key
char * name
darktable_t darktable
Definition darktable.c:181
static void dt_free_gpointer(gpointer ptr)
Definition darktable.h:463
static const dt_aligned_pixel_simd_t value
Definition darktable.h:577
#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 darktable.h:281
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
Definition dtgtk/paint.h:61
@ CPF_DIRECTION_DOWN
Definition dtgtk/paint.h:62
static guint dt_keys_mainpad_alternatives(const guint key_val)
Remap keypad keys to usual mainpad ones.
Definition gdkkeys.h:113
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)
@ LAST_SIGNAL
@ VALUE_RESET
@ VALUE_CHANGED
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)
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)
@ GRADIENT_SLIDER_GET
@ GRADIENT_SLIDER_SET
#define DTGTK_GRADIENT_SLIDER(obj)
@ 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
@ FREE_MARKERS
@ PROPORTIONAL_MARKERS
#define GRADIENT_SLIDER_MAX_POSITIONS
@ GRADIENT_SLIDER_MARGINS_DEFAULT
@ MOVE_RIGHT
@ MOVE_LEFT
#define DTGTK_IS_GRADIENT_SLIDER(obj)
gboolean dt_gui_get_scroll_unit_delta(const GdkEventScroll *event, int *delta)
Definition gtk.c:313
void dt_gui_add_class(GtkWidget *widget, const gchar *class_name)
Definition gtk.c:133
static const float x
float *const restrict const size_t k
struct _GtkWidget GtkWidget
Definition splash.h:29
const float uint32_t state[4]
float(* scale_callback)(GtkWidget *, float, int)
struct dt_gui_gtk_t * gui
Definition darktable.h:775
gint scroll_mask
Definition gtk.h:224
int32_t reset
Definition gtk.h:172
GtkWidget * has_scroll_focus
Definition gtk.h:228