1 /*
2  * Copyright (C) 2019-2021 Alexandros Theodotou <alex at zrythm dot org>
3  *
4  * This file is part of Zrythm
5  *
6  * Zrythm is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU Affero General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * Zrythm is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU Affero General Public License for more details.
15  *
16  * You should have received a copy of the GNU Affero General Public License
17  * along with Zrythm.  If not, see <https://www.gnu.org/licenses/>.
18  */
19 
20 #include <stdbool.h>
21 #include <stdlib.h>
22 
23 #include "gui/widgets/automation_mode.h"
24 #include "utils/cairo.h"
25 #include "utils/objects.h"
26 #include "utils/ui.h"
27 #include "zrythm.h"
28 #include "zrythm_app.h"
29 
30 void
31 automation_mode_widget_init (
32   AutomationModeWidget * self)
33 {
34   gdk_rgba_parse (
35     &self->def_color, UI_COLOR_BUTTON_NORMAL);
36   gdk_rgba_parse (
37     &self->hovered_color, UI_COLOR_BUTTON_HOVER);
38   self->toggled_colors[0] = UI_COLORS->solo_checked;
39   self->held_colors[0] = UI_COLORS->solo_active;
40   gdk_rgba_parse (
41     &self->toggled_colors[1], UI_COLOR_RECORD_CHECKED);
42   gdk_rgba_parse (
43     &self->held_colors[1], UI_COLOR_RECORD_ACTIVE);
44   gdk_rgba_parse (
45     &self->toggled_colors[2], "#666666");
46   gdk_rgba_parse (
47     &self->held_colors[2], "#888888");
48   self->aspect = 1.0;
49   self->corner_radius = 8.0;
50 
51   /* set dimensions */
52   PangoLayout * layout = self->layout;
53   int total_width = 0;
54   int x_px, y_px;
55   char txt[400];
56 #define DO(caps) \
57   if (AUTOMATION_MODE_##caps == \
58         AUTOMATION_MODE_RECORD) \
59     { \
60       automation_record_mode_get_localized ( \
61         self->owner->record_mode, txt); \
62     } \
63   else \
64     { \
65       automation_mode_get_localized ( \
66         AUTOMATION_MODE_##caps, txt); \
67     } \
68   pango_layout_set_text (layout, txt, -1); \
69   pango_layout_get_pixel_size ( \
70     layout, &x_px, &y_px); \
71   self->text_widths[AUTOMATION_MODE_##caps] = x_px; \
72   self->text_heights[AUTOMATION_MODE_##caps] = y_px; \
73   if (y_px > self->max_text_height) \
74     self->max_text_height = y_px; \
75   total_width += x_px
76 
77   DO (READ);
78   DO (RECORD);
79   DO (OFF);
80 
81   self->width =
82     total_width +
83     AUTOMATION_MODE_HPADDING * 6 +
84     AUTOMATION_MODE_HSEPARATOR_SIZE * 2;
85 }
86 
87 /**
88  * Creates a new track widget from the given track.
89  */
90 AutomationModeWidget *
91 automation_mode_widget_new (
92   int             height,
93   PangoLayout *   layout,
94   AutomationTrack * owner)
95 {
96   AutomationModeWidget * self =
97     object_new (AutomationModeWidget);
98 
99   self->owner = owner;
100   self->height = height;
101   self->layout = pango_layout_copy (layout);
102   PangoFontDescription * desc =
103     pango_font_description_from_string ("7");
104   pango_layout_set_font_description (
105     self->layout, desc);
106   pango_font_description_free (desc);
107   automation_mode_widget_init (self);
108 
109   return self;
110 }
111 
112 static AutomationMode
113 get_hit_mode (
114   AutomationModeWidget * self,
115   double                 x)
116 {
117   for (int i = 0; i < NUM_AUTOMATION_MODES - 1; i++)
118     {
119       int total_widths = 0;
120       for (int j = 0; j <= i; j++)
121         {
122           total_widths += self->text_widths[j];
123         }
124       total_widths +=
125         2 * AUTOMATION_MODE_HPADDING * (i + 1);
126       double next_start = self->x + total_widths;
127       /*g_message ("[%d] x: %f next start: %f",*/
128         /*i, x, next_start);*/
129       if (x < next_start)
130         {
131           return (AutomationMode) i;
132         }
133     }
134   return AUTOMATION_MODE_OFF;
135 }
136 
137 /**
138  * Gets the color for \ref state for \ref mode.
139  *
140  * @param state new state.
141  * @param mode AutomationMode.
142  */
143 static void
144 get_color_for_state (
145   AutomationModeWidget *  self,
146   CustomButtonWidgetState state,
147   AutomationMode          mode,
148   GdkRGBA *               c)
149 {
150   (void) c;
151   switch (state)
152     {
153     case CUSTOM_BUTTON_WIDGET_STATE_NORMAL:
154       *c = self->def_color;
155       break;
156     case CUSTOM_BUTTON_WIDGET_STATE_HOVERED:
157       *c = self->hovered_color;
158       break;
159     case CUSTOM_BUTTON_WIDGET_STATE_ACTIVE:
160       *c = self->held_colors[mode];
161       break;
162     case CUSTOM_BUTTON_WIDGET_STATE_TOGGLED:
163       *c = self->toggled_colors[mode];
164       break;
165     default:
166       g_warn_if_reached ();
167     }
168 }
169 
170 /**
171  * @param mode Mode the state applies to.
172  * @param state This state only applies to the mode.
173  */
174 static void
175 draw_bg (
176   AutomationModeWidget *  self,
177   cairo_t *               cr,
178   double                  x,
179   double                  y,
180   int                     draw_frame)
181 {
182   if (draw_frame)
183     {
184       cairo_set_source_rgba (
185         cr, 1, 1, 1, 0.4);
186       cairo_set_line_width (cr, 0.5);
187       z_cairo_rounded_rectangle (
188         cr, x, y, self->width, self->height,
189         self->aspect, self->corner_radius);
190       cairo_stroke (cr);
191     }
192 
193   /* draw bg with fade from last state */
194   int draw_order[NUM_AUTOMATION_MODES] = {
195     AUTOMATION_MODE_READ, AUTOMATION_MODE_OFF,
196     AUTOMATION_MODE_RECORD };
197   for (int idx = 0; idx < NUM_AUTOMATION_MODES; idx++)
198     {
199       int i = draw_order[idx];
200 
201       CustomButtonWidgetState cur_state =
202         self->current_states[i];
203       GdkRGBA c;
204       get_color_for_state (
205         self, cur_state, (AutomationMode) i, &c);
206       if (self->last_states[i] != cur_state)
207         {
208           self->transition_frames =
209             CUSTOM_BUTTON_WIDGET_MAX_TRANSITION_FRAMES;
210         }
211 
212       /* draw transition if transition frames exist */
213       if (self->transition_frames)
214         {
215           GdkRGBA mid_c;
216           ui_get_mid_color (
217             &mid_c, &c, &self->last_colors[i],
218             1.0 -
219             (double) self->transition_frames /
220               (double) CUSTOM_BUTTON_WIDGET_MAX_TRANSITION_FRAMES);
221           c = mid_c;
222           if (i == NUM_AUTOMATION_MODES - 1)
223             self->transition_frames--;
224         }
225       gdk_cairo_set_source_rgba (cr, &c);
226       self->last_colors[i] = c;
227 
228       double new_x = x;
229       int new_width =
230         self->text_widths[i] +
231         2 * AUTOMATION_MODE_HPADDING;
232       int rounded = 1;
233       switch (i)
234         {
235         case AUTOMATION_MODE_READ:
236           new_width +=
237             self->text_widths[i + 1];
238           break;
239         case AUTOMATION_MODE_RECORD:
240           rounded = 0;
241           new_x +=
242             self->text_widths[0] +
243             2 * AUTOMATION_MODE_HPADDING;
244           break;
245         case AUTOMATION_MODE_OFF:
246           new_x +=
247             self->text_widths[0] +
248             2 * AUTOMATION_MODE_HPADDING +
249             self->text_widths[1];
250           new_width =
251             ((int) x + self->width) - (int) new_x;
252           break;
253         }
254       if (rounded)
255         {
256           z_cairo_rounded_rectangle (
257             cr, new_x, y, new_width, self->height,
258             self->aspect, self->corner_radius);
259         }
260       else
261         {
262           cairo_rectangle (
263             cr, new_x, y, new_width, self->height);
264         }
265       cairo_fill (cr);
266     }
267 }
268 
269 void
270 automation_mode_widget_draw (
271   AutomationModeWidget * self,
272   cairo_t *                 cr,
273   double                    x,
274   double                    y,
275   double                    x_cursor,
276   CustomButtonWidgetState   state)
277 {
278   /* get hit button */
279   self->has_hit_mode = 0;
280   if (state == CUSTOM_BUTTON_WIDGET_STATE_HOVERED ||
281       state == CUSTOM_BUTTON_WIDGET_STATE_ACTIVE)
282     {
283       self->has_hit_mode = 1;
284       self->hit_mode = get_hit_mode (self, x_cursor);
285       /*g_message ("hit mode %d", self->hit_mode);*/
286     }
287 
288   /* get current states */
289   for (unsigned int i = 0;
290        i < NUM_AUTOMATION_MODES; i++)
291     {
292       AutomationMode prev_am =
293           self->owner->automation_mode;
294       if (self->has_hit_mode &&
295           i == self->hit_mode)
296         {
297           if (prev_am != i ||
298               (prev_am == i &&
299                state ==
300                  CUSTOM_BUTTON_WIDGET_STATE_ACTIVE))
301             {
302               self->current_states[i] = state;
303             }
304           else
305             {
306               self->current_states[i] =
307                 CUSTOM_BUTTON_WIDGET_STATE_TOGGLED;
308             }
309         }
310       else
311         self->current_states[i] =
312           self->owner->automation_mode ==
313             i ?
314               CUSTOM_BUTTON_WIDGET_STATE_TOGGLED :
315               CUSTOM_BUTTON_WIDGET_STATE_NORMAL;
316     }
317 
318   draw_bg (self, cr, x, y, false);
319 
320   /*draw_icon_with_shadow (self, cr, x, y, state);*/
321 
322   /* draw text */
323   int total_text_widths = 0;
324   for (int i = 0; i < NUM_AUTOMATION_MODES; i++)
325     {
326       PangoLayout * layout = self->layout;
327       cairo_set_source_rgba (
328         cr, 1, 1, 1, 1);
329       cairo_move_to (
330         cr,
331         x + AUTOMATION_MODE_HPADDING +
332           i * (2 * AUTOMATION_MODE_HPADDING) +
333           total_text_widths,
334         (y + self->height / 2) -
335           self->text_heights[i] / 2);
336       char mode_str[400];
337       if (i == AUTOMATION_MODE_RECORD)
338         {
339           automation_record_mode_get_localized (
340             self->owner->record_mode, mode_str);
341           pango_layout_set_text (
342             layout, mode_str, -1);
343         }
344       else
345         {
346           automation_mode_get_localized (
347             (AutomationMode) i, mode_str);
348           pango_layout_set_text (
349             layout, mode_str, -1);
350         }
351       pango_cairo_show_layout (cr, layout);
352 
353       total_text_widths += self->text_widths[i];
354 
355       self->last_states[i] = self->current_states[i];
356     }
357 }
358 
359 void
360 automation_mode_widget_free (
361   AutomationModeWidget * self)
362 {
363   free (self);
364 }
365 
366