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