1 /*
2     This file is part of darktable,
3     Copyright (C) 2010-2021 darktable developers.
4 
5     darktable is free software: you can redistribute it and/or modify
6     it under the terms of the GNU General Public License as published by
7     the Free Software Foundation, either version 3 of the License, or
8     (at your option) any later version.
9 
10     darktable is distributed in the hope that it will be useful,
11     but WITHOUT ANY WARRANTY; without even the implied warranty of
12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13     GNU General Public License for more details.
14 
15     You should have received a copy of the GNU General Public License
16     along with darktable.  If not, see <http://www.gnu.org/licenses/>.
17 */
18 #include "togglebutton.h"
19 #include "bauhaus/bauhaus.h"
20 #include "button.h"
21 #include "gui/gtk.h"
22 #include <string.h>
23 
24 static void _togglebutton_class_init(GtkDarktableToggleButtonClass *klass);
25 static void _togglebutton_init(GtkDarktableToggleButton *slider);
26 static gboolean _togglebutton_draw(GtkWidget *widget, cairo_t *cr);
27 
_togglebutton_class_init(GtkDarktableToggleButtonClass * klass)28 static void _togglebutton_class_init(GtkDarktableToggleButtonClass *klass)
29 {
30   GtkWidgetClass *widget_class = (GtkWidgetClass *)klass;
31 
32   widget_class->draw = _togglebutton_draw;
33 }
34 
_togglebutton_init(GtkDarktableToggleButton * slider)35 static void _togglebutton_init(GtkDarktableToggleButton *slider)
36 {
37 }
38 
_togglebutton_draw(GtkWidget * widget,cairo_t * cr)39 static gboolean _togglebutton_draw(GtkWidget *widget, cairo_t *cr)
40 {
41   g_return_val_if_fail(widget != NULL, FALSE);
42   g_return_val_if_fail(DTGTK_IS_TOGGLEBUTTON(widget), FALSE);
43 
44   GtkDarktableToggleButton *button = DTGTK_TOGGLEBUTTON(widget);
45 
46   GtkStateFlags state = gtk_widget_get_state_flags(widget);
47 
48   GdkRGBA fg_color;
49   GtkStyleContext *context = gtk_widget_get_style_context(widget);
50 
51   if(button->icon_flags & CPF_CUSTOM_FG)
52     fg_color = button->fg;
53   else if(button->icon_flags & CPF_IGNORE_FG_STATE)
54     gtk_style_context_get_color(context, state & ~GTK_STATE_FLAG_SELECTED, &fg_color);
55   else
56     gtk_style_context_get_color(context, state, &fg_color);
57 
58   /* fetch flags */
59   int flags = DTGTK_TOGGLEBUTTON(widget)->icon_flags;
60 
61   /* update active state paint flag */
62   const gboolean active = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget));
63   if(active)
64     flags |= CPF_ACTIVE;
65   else
66     flags &= ~CPF_ACTIVE;
67 
68   /* update focus state paint flag */
69   const gboolean hasfocus = ((DTGTK_TOGGLEBUTTON(widget)->icon_data == darktable.develop->gui_module)
70                          && darktable.develop->gui_module);
71   if(hasfocus)
72     flags |= CPF_FOCUS;
73   else
74     flags &= ~CPF_FOCUS;
75 
76   /* prelight */
77   if(state & GTK_STATE_FLAG_PRELIGHT)
78     flags |= CPF_PRELIGHT;
79   else
80     flags &= ~CPF_PRELIGHT;
81 
82   /* begin cairo drawing */
83   /* get button total allocation */
84   GtkAllocation allocation;
85   gtk_widget_get_allocation(widget, &allocation);
86   const int width = allocation.width;
87   const int height = allocation.height;
88 
89   /* get the css geometry properties of the button */
90   GtkBorder margin, border, padding;
91   gtk_style_context_get_margin(context, state, &margin);
92   gtk_style_context_get_border(context, state, &border);
93   gtk_style_context_get_padding(context, state, &padding);
94 
95   /* for button frame and background, we remove css margin from allocation */
96   int startx = margin.left;
97   int starty = margin.top;
98   int cwidth = width - margin.left - margin.right;
99   int cheight = height - margin.top - margin.bottom;
100 
101   /* draw standard button background if not transparent nor flat styled */
102   if((flags & CPF_STYLE_FLAT))
103   {
104     if((flags & CPF_PRELIGHT) || ((flags & CPF_ACTIVE) && !(flags & CPF_BG_TRANSPARENT)))
105     {
106       // When CPF_BG_TRANSPARENT is set, change the background on
107       // PRELIGHT, but not on ACTIVE
108       if(!(flags & CPF_BG_TRANSPARENT) || (flags & CPF_PRELIGHT))
109         gtk_render_background(context, cr, startx, starty, cwidth, cheight);
110     }
111     else if(!(flags & CPF_ACTIVE) || (flags & CPF_IGNORE_FG_STATE))
112       fg_color.alpha = CLAMP(fg_color.alpha / 2.0, 0.3, 1.0);
113   }
114   else if(!(flags & CPF_BG_TRANSPARENT))
115     gtk_render_background(context, cr, startx, starty, cwidth, cheight);
116 
117   gtk_render_frame(context, cr, startx, starty, cwidth, cheight);
118 
119   gdk_cairo_set_source_rgba(cr, &fg_color);
120 
121   /* draw icon */
122   if(DTGTK_TOGGLEBUTTON(widget)->icon)
123   {
124     /* calculate the button content allocation */
125     startx += border.left + padding.left;
126     starty += border.top + padding.top;
127     cwidth -= border.left + border.right + padding.left + padding.right;
128     cheight -= border.top + border.bottom + padding.top + padding.bottom;
129 
130     /* we have to leave some breathing room to the cairo icon paint function to possibly    */
131     /* draw slightly outside the bounding box, for optical alignment and balancing of icons */
132     /* we do this by putting a drawing area widget inside the button and using the CSS      */
133     /* margin property in px of the drawing area as extra room in percent (DPI safe)        */
134     /* we do this because Gtk+ does not support CSS size in percent                         */
135     /* this extra margin can be also (slightly) negative                                    */
136     GtkStyleContext *ccontext = gtk_widget_get_style_context(DTGTK_TOGGLEBUTTON(widget)->canvas);
137     GtkBorder cmargin;
138     gtk_style_context_get_margin(ccontext, state, &cmargin);
139 
140     startx += round(cmargin.left * cwidth / 100.0f);
141     starty += round(cmargin.top * cheight / 100.0f);
142     cwidth = round((float)cwidth * (1.0 - (cmargin.left + cmargin.right) / 100.0f));
143     cheight = round((float)cheight * (1.0 - (cmargin.top + cmargin.bottom) / 100.0f));
144 
145     void *icon_data = DTGTK_TOGGLEBUTTON(widget)->icon_data;
146 
147     if(cwidth > 0 && cheight > 0)
148       DTGTK_TOGGLEBUTTON(widget)->icon(cr, startx, starty, cwidth, cheight, flags, icon_data);
149   }
150 
151   return FALSE;
152 }
153 
154 // Public functions
dtgtk_togglebutton_new(DTGTKCairoPaintIconFunc paint,gint paintflags,void * paintdata)155 GtkWidget *dtgtk_togglebutton_new(DTGTKCairoPaintIconFunc paint, gint paintflags, void *paintdata)
156 {
157   GtkDarktableToggleButton *button;
158   button = g_object_new(dtgtk_togglebutton_get_type(), NULL);
159   button->icon = paint;
160   button->icon_flags = paintflags;
161   button->icon_data = paintdata;
162   button->canvas = gtk_drawing_area_new();
163   gtk_container_add(GTK_CONTAINER(button), button->canvas);
164   gtk_widget_set_name(GTK_WIDGET(button), "dt-toggle-button");
165   gtk_widget_set_name(GTK_WIDGET(button->canvas), "button-canvas");
166   return (GtkWidget *)button;
167 }
168 
dtgtk_togglebutton_get_type()169 GType dtgtk_togglebutton_get_type()
170 {
171   static GType dtgtk_togglebutton_type = 0;
172   if(!dtgtk_togglebutton_type)
173   {
174     static const GTypeInfo dtgtk_togglebutton_info = {
175       sizeof(GtkDarktableToggleButtonClass), (GBaseInitFunc)NULL, (GBaseFinalizeFunc)NULL,
176       (GClassInitFunc)_togglebutton_class_init, NULL, /* class_finalize */
177       NULL,                                           /* class_data */
178       sizeof(GtkDarktableToggleButton), 0,            /* n_preallocs */
179       (GInstanceInitFunc)_togglebutton_init,
180     };
181     dtgtk_togglebutton_type = g_type_register_static(GTK_TYPE_TOGGLE_BUTTON, "GtkDarktableToggleButton",
182                                                      &dtgtk_togglebutton_info, 0);
183   }
184   return dtgtk_togglebutton_type;
185 }
186 
187 
dtgtk_togglebutton_set_paint(GtkDarktableToggleButton * button,DTGTKCairoPaintIconFunc paint,gint paintflags,void * paintdata)188 void dtgtk_togglebutton_set_paint(GtkDarktableToggleButton *button, DTGTKCairoPaintIconFunc paint,
189                                   gint paintflags, void *paintdata)
190 {
191   g_return_if_fail(button != NULL);
192   button->icon = paint;
193   button->icon_flags = paintflags;
194   button->icon_data = paintdata;
195 }
196 
dtgtk_togglebutton_override_color(GtkDarktableToggleButton * button,GdkRGBA * color)197 void dtgtk_togglebutton_override_color(GtkDarktableToggleButton *button, GdkRGBA *color)
198 {
199   g_return_if_fail(button != NULL);
200   if(color)
201   {
202     button->fg = *color;
203     button->icon_flags |= CPF_CUSTOM_FG;
204   }
205   else
206     button->icon_flags &= ~CPF_CUSTOM_FG;
207 }
208 
dtgtk_togglebutton_override_background_color(GtkDarktableToggleButton * button,GdkRGBA * color)209 void dtgtk_togglebutton_override_background_color(GtkDarktableToggleButton *button, GdkRGBA *color)
210 {
211   g_return_if_fail(button != NULL);
212   if(color)
213   {
214     button->bg = *color;
215     button->icon_flags |= CPF_CUSTOM_BG;
216   }
217   else
218     button->icon_flags &= ~CPF_CUSTOM_BG;
219 }
220 
221 // modelines: These editor modelines have been set for all relevant files by tools/update_modelines.sh
222 // vim: shiftwidth=2 expandtab tabstop=2 cindent
223 // kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
224