diff --git a/CMakeLists.txt b/CMakeLists.txt index e53620b..8a36dc0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -121,6 +121,8 @@ set( SOURCES src/config.c src/util/strnatcmp.c src/util/timer.c src/util/cache.c + src/util/color.c + src/util/gradient.c src/util/window.c ) if( ENABLE_BATTERY ) diff --git a/src/config.c b/src/config.c index 6b06e1d..98ca619 100644 --- a/src/config.c +++ b/src/config.c @@ -231,10 +231,6 @@ void add_entry(char *key, char *value) // 'rounded' is the first parameter => alloc a new background if (backgrounds->len > 0) { Background *bg = &g_array_index(backgrounds, Background, backgrounds->len - 1); - if (!read_bg_color2) - memcpy(&bg->fill_color2, &bg->fill_color, sizeof(Color)); - if (!read_bg_gradient) - bg->gradient = 0; if (!read_bg_color_hover) memcpy(&bg->fill_color_hover, &bg->fill_color, sizeof(Color)); if (!read_border_color_hover) @@ -277,19 +273,6 @@ void add_entry(char *key, char *value) bg->fill_color.alpha = (atoi(value2) / 100.0); else bg->fill_color.alpha = 0.5; - } else if (strcmp(key, "background_color2") == 0) { - Background* bg = &g_array_index(backgrounds, Background, backgrounds->len-1); - extract_values(value, &value1, &value2, &value3); - get_color (value1, bg->fill_color2.rgb); - if (value2) - bg->fill_color2.alpha = (atoi (value2) / 100.0); - else - bg->fill_color2.alpha = 0.5; - read_bg_color2 = 1; - } else if (strcmp(key, "gradient") == 0) { - Background *bg = &g_array_index(backgrounds, Background, backgrounds->len-1); - bg->gradient = atoi(value); - read_bg_gradient = 1; } else if (strcmp(key, "border_color") == 0) { Background *bg = &g_array_index(backgrounds, Background, backgrounds->len - 1); extract_values(value, &value1, &value2, &value3); @@ -336,6 +319,131 @@ void add_entry(char *key, char *value) read_border_color_press = 1; } + /* Gradients */ + else if (strcmp(key, "gradient") == 0) { + // Create a new gradient + GradientClass g; + init_gradient(&g, gradient_type_from_string(value)); + g_array_append_val(gradients, g); + } else if (strcmp(key, "start_color") == 0) { + GradientClass *g = &g_array_index(gradients, GradientClass, gradients->len - 1); + extract_values(value, &value1, &value2, &value3); + get_color(value1, g->start_color.rgb); + if (value2) + g->start_color.alpha = (atoi(value2) / 100.0); + else + g->start_color.alpha = 0.5; + } else if (strcmp(key, "end_color") == 0) { + GradientClass *g = &g_array_index(gradients, GradientClass, gradients->len - 1); + extract_values(value, &value1, &value2, &value3); + get_color(value1, g->end_color.rgb); + if (value2) + g->end_color.alpha = (atoi(value2) / 100.0); + else + g->end_color.alpha = 0.5; + } else if (strcmp(key, "color_stop") == 0) { + GradientClass *g = &g_array_index(gradients, GradientClass, gradients->len - 1); + extract_values(value, &value1, &value2, &value3); + ColorStop *color_stop = (ColorStop *) calloc(1, sizeof(ColorStop)); + color_stop->offset = atof(value1); + get_color(value2, color_stop->color.rgb); + if (value3) + color_stop->color.alpha = (atoi(value2) / 100.0); + else + color_stop->color.alpha = 0.5; + g->extra_color_stops = g_list_append(g->extra_color_stops, color_stop); + } else if (strcmp(key, "from_origin") == 0) { + GradientClass *g = &g_array_index(gradients, GradientClass, gradients->len - 1); + if (g->type == GRADIENT_HORIZONTAL || g->type == GRADIENT_VERTICAL || g->type == GRADIENT_CENTERED) { + fprintf(stderr, RED "Control points can only be specified for linear and radial gradients: line %s = %s" RESET "\n", key, value); + } else { + g->from.origin = origin_from_string(value); + } + } else if (strcmp(key, "to_origin") == 0) { + GradientClass *g = &g_array_index(gradients, GradientClass, gradients->len - 1); + if (g->type == GRADIENT_HORIZONTAL || g->type == GRADIENT_VERTICAL || g->type == GRADIENT_CENTERED) { + fprintf(stderr, RED "Control points can only be specified for linear and radial gradients: line %s = %s" RESET "\n", key, value); + } else { + g->to.origin = origin_from_string(value); + } + } else if (strcmp(key, "from_offset_x") == 0) { + GradientClass *g = &g_array_index(gradients, GradientClass, gradients->len - 1); + if (g->type == GRADIENT_HORIZONTAL || g->type == GRADIENT_VERTICAL || g->type == GRADIENT_CENTERED) { + fprintf(stderr, RED "Control points can only be specified for linear and radial gradients: line %s = %s" RESET "\n", key, value); + } else { + Offset *offset = offset_from_string(value); + if (!offset) { + fprintf(stderr, RED "Invalid value: line %s = %s" RESET "\n", key, value); + } else { + g->from.offsets_x = g_list_append(g->from.offsets_x, offset); + } + } + } else if (strcmp(key, "from_offset_y") == 0) { + GradientClass *g = &g_array_index(gradients, GradientClass, gradients->len - 1); + if (g->type == GRADIENT_HORIZONTAL || g->type == GRADIENT_VERTICAL || g->type == GRADIENT_CENTERED) { + fprintf(stderr, RED "Control points can only be specified for linear and radial gradients: line %s = %s" RESET "\n", key, value); + } else { + Offset *offset = offset_from_string(value); + if (!offset) { + fprintf(stderr, RED "Invalid value: line %s = %s" RESET "\n", key, value); + } else { + g->from.offsets_y = g_list_append(g->from.offsets_y, offset); + } + } + } else if (strcmp(key, "from_offset_r") == 0) { + GradientClass *g = &g_array_index(gradients, GradientClass, gradients->len - 1); + if (g->type == GRADIENT_HORIZONTAL || g->type == GRADIENT_VERTICAL || g->type == GRADIENT_CENTERED) { + fprintf(stderr, RED "Control points can only be specified for linear and radial gradients: line %s = %s" RESET "\n", key, value); + } else if (g->type == GRADIENT_LINEAR) { + fprintf(stderr, RED "Invalid parameter for linear gradient: line %s = %s" RESET "\n", key, value); + } else { + Offset *offset = offset_from_string(value); + if (!offset) { + fprintf(stderr, RED "Invalid value: line %s = %s" RESET "\n", key, value); + } else { + g->from.offsets_r = g_list_append(g->from.offsets_r, offset); + } + } + } else if (strcmp(key, "to_offset_x") == 0) { + GradientClass *g = &g_array_index(gradients, GradientClass, gradients->len - 1); + if (g->type == GRADIENT_HORIZONTAL || g->type == GRADIENT_VERTICAL || g->type == GRADIENT_CENTERED) { + fprintf(stderr, RED "Control points can only be specified for linear and radial gradients: line %s = %s" RESET "\n", key, value); + } else { + Offset *offset = offset_from_string(value); + if (!offset) { + fprintf(stderr, RED "Invalid value: line %s = %s" RESET "\n", key, value); + } else { + g->to.offsets_x = g_list_append(g->to.offsets_x, offset); + } + } + } else if (strcmp(key, "to_offset_y") == 0) { + GradientClass *g = &g_array_index(gradients, GradientClass, gradients->len - 1); + if (g->type == GRADIENT_HORIZONTAL || g->type == GRADIENT_VERTICAL || g->type == GRADIENT_CENTERED) { + fprintf(stderr, RED "Control points can only be specified for linear and radial gradients: line %s = %s" RESET "\n", key, value); + } else { + Offset *offset = offset_from_string(value); + if (!offset) { + fprintf(stderr, RED "Invalid value: line %s = %s" RESET "\n", key, value); + } else { + g->to.offsets_y = g_list_append(g->to.offsets_y, offset); + } + } + } else if (strcmp(key, "to_offset_r") == 0) { + GradientClass *g = &g_array_index(gradients, GradientClass, gradients->len - 1); + if (g->type == GRADIENT_HORIZONTAL || g->type == GRADIENT_VERTICAL || g->type == GRADIENT_CENTERED) { + fprintf(stderr, RED "Control points can only be specified for linear and radial gradients: line %s = %s" RESET "\n", key, value); + } else if (g->type == GRADIENT_LINEAR) { + fprintf(stderr, RED "Invalid parameter for linear gradient: line %s = %s" RESET "\n", key, value); + } else { + Offset *offset = offset_from_string(value); + if (!offset) { + fprintf(stderr, RED "Invalid value: line %s = %s" RESET "\n", key, value); + } else { + g->to.offsets_r = g_list_append(g->to.offsets_r, offset); + } + } + } + /* Panel */ else if (strcmp(key, "panel_monitor") == 0) { panel_config.monitor = config_get_monitor(value); @@ -442,6 +550,11 @@ void add_entry(char *key, char *value) int id = atoi(value); id = (id < backgrounds->len && id >= 0) ? id : 0; panel_config.area.bg = &g_array_index(backgrounds, Background, id); + } else if (strcmp(key, "panel_gradient_id") == 0) { + int id = atoi(value); + id = (id < gradients->len && id >= 0) ? id : -1; + if (id >= 0) + panel_config.area.gradients = g_list_append(panel_config.area.gradients, &g_array_index(backgrounds, Background, id)); } else if (strcmp(key, "wm_menu") == 0) wm_menu = atoi(value); else if (strcmp(key, "panel_dock") == 0) @@ -544,6 +657,13 @@ void add_entry(char *key, char *value) int id = atoi(value); id = (id < backgrounds->len && id >= 0) ? id : 0; panel_config.battery.area.bg = &g_array_index(backgrounds, Background, id); +#endif + } else if (strcmp(key, "battery_gradient_id") == 0) { +#ifdef ENABLE_BATTERY + int id = atoi(value); + id = (id < gradients->len && id >= 0) ? id : -1; + if (id >= 0) + panel_config.battery.area.gradients = g_list_append(panel_config.battery.area.gradients, &g_array_index(backgrounds, Background, id)); #endif } else if (strcmp(key, "battery_hide") == 0) { #ifdef ENABLE_BATTERY @@ -565,6 +685,12 @@ void add_entry(char *key, char *value) int id = atoi(value); id = (id < backgrounds->len && id >= 0) ? id : 0; separator->area.bg = &g_array_index(backgrounds, Background, id); + } else if (strcmp(key, "separator_gradient_id") == 0) { + Separator *separator = get_or_create_last_separator(); + int id = atoi(value); + id = (id < gradients->len && id >= 0) ? id : -1; + if (id >= 0) + separator->area.gradients = g_list_append(separator->area.gradients, &g_array_index(backgrounds, Background, id)); } else if (strcmp(key, "separator_color") == 0) { Separator *separator = get_or_create_last_separator(); extract_values(value, &value1, &value2, &value3); @@ -657,6 +783,12 @@ void add_entry(char *key, char *value) int id = atoi(value); id = (id < backgrounds->len && id >= 0) ? id : 0; execp->backend->bg = &g_array_index(backgrounds, Background, id); + } else if (strcmp(key, "execp_gradient_id") == 0) { + Execp *execp = get_or_create_last_execp(); + int id = atoi(value); + id = (id < gradients->len && id >= 0) ? id : -1; + if (id >= 0) + execp->area.gradients = g_list_append(execp->area.gradients, &g_array_index(backgrounds, Background, id)); } else if (strcmp(key, "execp_centered") == 0) { Execp *execp = get_or_create_last_execp(); execp->backend->centered = atoi(value); @@ -753,6 +885,11 @@ void add_entry(char *key, char *value) int id = atoi(value); id = (id < backgrounds->len && id >= 0) ? id : 0; panel_config.clock.area.bg = &g_array_index(backgrounds, Background, id); + } else if (strcmp(key, "clock_gradient_id") == 0) { + int id = atoi(value); + id = (id < gradients->len && id >= 0) ? id : -1; + if (id >= 0) + panel_config.clock.area.gradients = g_list_append(panel_config.clock.area.gradients, &g_array_index(backgrounds, Background, id)); } else if (strcmp(key, "clock_tooltip") == 0) { if (strlen(value) > 0) time_tooltip_format = strdup(value); @@ -794,13 +931,23 @@ void add_entry(char *key, char *value) } else if (strcmp(key, "taskbar_background_id") == 0) { int id = atoi(value); id = (id < backgrounds->len && id >= 0) ? id : 0; - panel_config.g_taskbar.background[TASKBAR_NORMAL] = &g_array_index(backgrounds, Background, id); + panel_config.g_taskbar.area.bg = &g_array_index(backgrounds, Background, id); if (panel_config.g_taskbar.background[TASKBAR_ACTIVE] == 0) panel_config.g_taskbar.background[TASKBAR_ACTIVE] = panel_config.g_taskbar.background[TASKBAR_NORMAL]; + } else if (strcmp(key, "taskbar_gradient_id") == 0) { + int id = atoi(value); + id = (id < gradients->len && id >= 0) ? id : -1; + if (id >= 0) + panel_config.g_taskbar.gradient[TASKBAR_NORMAL] = g_list_append(panel_config.g_taskbar.gradient[TASKBAR_NORMAL], &g_array_index(gradients, GradientClass, id)); } else if (strcmp(key, "taskbar_active_background_id") == 0) { int id = atoi(value); id = (id < backgrounds->len && id >= 0) ? id : 0; panel_config.g_taskbar.background[TASKBAR_ACTIVE] = &g_array_index(backgrounds, Background, id); + } else if (strcmp(key, "taskbar_active_gradient_id") == 0) { + int id = atoi(value); + id = (id < gradients->len && id >= 0) ? id : -1; + if (id >= 0) + panel_config.g_taskbar.gradient[TASKBAR_ACTIVE] = g_list_append(panel_config.g_taskbar.gradient[TASKBAR_ACTIVE], &g_array_index(gradients, GradientClass, id)); } else if (strcmp(key, "taskbar_name") == 0) { taskbarname_enabled = atoi(value); } else if (strcmp(key, "taskbar_name_padding") == 0) { @@ -815,10 +962,20 @@ void add_entry(char *key, char *value) if (panel_config.g_taskbar.background_name[TASKBAR_ACTIVE] == 0) panel_config.g_taskbar.background_name[TASKBAR_ACTIVE] = panel_config.g_taskbar.background_name[TASKBAR_NORMAL]; + } else if (strcmp(key, "taskbar_name_gradient_id") == 0) { + int id = atoi(value); + id = (id < gradients->len && id >= 0) ? id : -1; + if (id >= 0) + panel_config.g_taskbar.gradient_name[TASKBAR_NORMAL] = g_list_append(panel_config.g_taskbar.gradient_name[TASKBAR_NORMAL], &g_array_index(gradients, GradientClass, id)); } else if (strcmp(key, "taskbar_name_active_background_id") == 0) { int id = atoi(value); id = (id < backgrounds->len && id >= 0) ? id : 0; panel_config.g_taskbar.background_name[TASKBAR_ACTIVE] = &g_array_index(backgrounds, Background, id); + } else if (strcmp(key, "taskbar_name_active_gradient_id") == 0) { + int id = atoi(value); + id = (id < gradients->len && id >= 0) ? id : -1; + if (id >= 0) + panel_config.g_taskbar.gradient_name[TASKBAR_ACTIVE] = g_list_append(panel_config.g_taskbar.gradient_name[TASKBAR_ACTIVE], &g_array_index(gradients, GradientClass, id)); } else if (strcmp(key, "taskbar_name_font") == 0) { panel_config.taskbarname_font_desc = pango_font_description_from_string(value); panel_config.taskbarname_has_font = TRUE; @@ -930,6 +1087,17 @@ void add_entry(char *key, char *value) if (status == TASK_NORMAL) panel_config.g_task.area.bg = panel_config.g_task.background[TASK_NORMAL]; } + } else if (g_regex_match_simple("task.*_gradient_id", key, 0, 0)) { + gchar **split = g_regex_split_simple("_", key, 0, 0); + int status = g_strv_length(split) == 3 ? TASK_NORMAL : get_task_status(split[1]); + g_strfreev(split); + if (status >= 0) { + int id = atoi(value); + id = (id < gradients->len && id >= 0) ? id : -1; + if (id >= 0) { + panel_config.g_task.gradient[status] = g_list_append(panel_config.g_task.gradient[status], &g_array_index(gradients, GradientClass, id)); + } + } } // "tooltip" is deprecated but here for backwards compatibility else if (strcmp(key, "task_tooltip") == 0 || strcmp(key, "tooltip") == 0) @@ -957,6 +1125,11 @@ void add_entry(char *key, char *value) int id = atoi(value); id = (id < backgrounds->len && id >= 0) ? id : 0; systray.area.bg = &g_array_index(backgrounds, Background, id); + } else if (strcmp(key, "systray_gradient_id") == 0) { + int id = atoi(value); + id = (id < gradients->len && id >= 0) ? id : -1; + if (id >= 0) + systray.area.gradients = g_list_append(systray.area.gradients, &g_array_index(gradients, GradientClass, id)); } else if (strcmp(key, "systray_sort") == 0) { if (strcmp(value, "descending") == 0) systray.sort = SYSTRAY_SORT_DESCENDING; @@ -989,10 +1162,20 @@ void add_entry(char *key, char *value) int id = atoi(value); id = (id < backgrounds->len && id >= 0) ? id : 0; panel_config.launcher.area.bg = &g_array_index(backgrounds, Background, id); + } else if (strcmp(key, "launcher_gradient_id") == 0) { + int id = atoi(value); + id = (id < gradients->len && id >= 0) ? id : -1; + if (id >= 0) + panel_config.launcher.area.gradients = g_list_append(panel_config.launcher.area.gradients, &g_array_index(gradients, GradientClass, id)); } else if (strcmp(key, "launcher_icon_background_id") == 0) { int id = atoi(value); id = (id < backgrounds->len && id >= 0) ? id : 0; launcher_icon_bg = &g_array_index(backgrounds, Background, id); + } else if (strcmp(key, "launcher_icon_gradient_id") == 0) { + int id = atoi(value); + id = (id < gradients->len && id >= 0) ? id : -1; + if (id >= 0) + launcher_icon_gradient = &g_array_index(gradients, GradientClass, id); } else if (strcmp(key, "launcher_icon_size") == 0) { launcher_max_icon_size = atoi(value); } else if (strcmp(key, "launcher_item_app") == 0) { @@ -1175,10 +1358,6 @@ gboolean config_read_file(const char *path) if (backgrounds->len > 0) { Background *bg = &g_array_index(backgrounds, Background, backgrounds->len - 1); - if (!read_bg_color2) - memcpy(&bg->fill_color2, &bg->fill_color, sizeof(Color)); - if (!read_bg_gradient) - bg->gradient = 0; if (!read_bg_color_hover) memcpy(&bg->fill_color_hover, &bg->fill_color, sizeof(Color)); if (!read_border_color_hover) diff --git a/src/launcher/launcher.c b/src/launcher/launcher.c index 28113a9..79a44bb 100644 --- a/src/launcher/launcher.c +++ b/src/launcher/launcher.c @@ -53,6 +53,7 @@ char *icon_theme_name_xsettings; int launcher_icon_theme_override; int startup_notifications; Background *launcher_icon_bg; +GradientClass *launcher_icon_gradient; Imlib_Image scale_icon(Imlib_Image original, int icon_size); void free_icon(Imlib_Image icon); diff --git a/src/launcher/launcher.h b/src/launcher/launcher.h index f60ead6..0185eff 100644 --- a/src/launcher/launcher.h +++ b/src/launcher/launcher.h @@ -46,6 +46,7 @@ extern char *icon_theme_name_config; extern int launcher_icon_theme_override; extern int startup_notifications; extern Background *launcher_icon_bg; +extern GradientClass *launcher_icon_gradient; // default global data void default_launcher(); diff --git a/src/panel.c b/src/panel.c index 320f046..8652d88 100644 --- a/src/panel.c +++ b/src/panel.c @@ -73,6 +73,7 @@ Panel *panels; int num_panels; GArray *backgrounds; +GArray *gradients; Imlib_Image default_icon; char *default_font = NULL; @@ -98,6 +99,7 @@ void default_panel() max_tick_urgent = 14; mouse_left = TOGGLE_ICONIFY; backgrounds = g_array_new(0, 0, sizeof(Background)); + gradients = g_array_new(0, 0, sizeof(GradientClass)); memset(&panel_config, 0, sizeof(Panel)); snprintf(panel_config.area.name, sizeof(panel_config.area.name), "Panel"); @@ -145,8 +147,14 @@ void cleanup_panel() free(panels); panels = NULL; if (backgrounds) - g_array_free(backgrounds, 1); + g_array_free(backgrounds, TRUE); backgrounds = NULL; + if (gradients) { + for (guint i = 0; i < gradients->len; i++) + cleanup_gradient(&g_array_index(gradients, GradientClass, i)); + g_array_free(gradients, TRUE); + } + gradients = NULL; pango_font_description_free(panel_config.g_task.font_desc); panel_config.g_task.font_desc = NULL; pango_font_description_free(panel_config.taskbarname_font_desc); @@ -961,6 +969,7 @@ void render_panel(Panel *panel) relayout(&panel->area); if (debug_geometry) area_dump_geometry(&panel->area, 0); + update_dependent_gradients(&panel->area); draw_tree(&panel->area); } diff --git a/src/panel.h b/src/panel.h index a22f440..84d98f5 100644 --- a/src/panel.h +++ b/src/panel.h @@ -83,6 +83,7 @@ extern Strut panel_strut_policy; extern char *panel_items_order; extern int max_tick_urgent; extern GArray *backgrounds; +extern GArray *gradients; extern Imlib_Image default_icon; #define DEFAULT_FONT "sans 10" extern char *default_font; diff --git a/src/taskbar/task.h b/src/taskbar/task.h index 706332d..0c78721 100644 --- a/src/taskbar/task.h +++ b/src/taskbar/task.h @@ -36,6 +36,7 @@ typedef struct GlobalTask { int brightness[TASK_STATE_COUNT]; int config_asb_mask; Background *background[TASK_STATE_COUNT]; + GList *gradient[TASK_STATE_COUNT]; int config_background_mask; // starting position for text ~ task_padding + task_border + icon_size double text_posx, text_height; diff --git a/src/taskbar/taskbar.h b/src/taskbar/taskbar.h index 7138916..e7d7d82 100644 --- a/src/taskbar/taskbar.h +++ b/src/taskbar/taskbar.h @@ -43,6 +43,8 @@ typedef struct GlobalTaskbar { Area area_name; Background *background[TASKBAR_STATE_COUNT]; Background *background_name[TASKBAR_STATE_COUNT]; + GList *gradient[TASKBAR_STATE_COUNT]; + GList *gradient_name[TASKBAR_STATE_COUNT]; } GlobalTaskbar; extern gboolean taskbar_enabled; diff --git a/src/util/area.c b/src/util/area.c index dd81d77..2e2e960 100644 --- a/src/util/area.c +++ b/src/util/area.c @@ -381,6 +381,20 @@ void show(Area *a) a->resize_needed = TRUE; } +void update_dependent_gradients(Area *a) +{ + if (!a->on_screen) + return; + if (a->_changed) { + for (GList *l = a->dependent_gradients; l; l = l->next) { + GradientInstance *gi = (GradientInstance *)l->data; + update_gradient(gi); + } + } + for (GList *l = a->children; l; l = l->next) + update_dependent_gradients((Area *)l->data); +} + void draw(Area *a) { if (a->_changed) { @@ -439,17 +453,8 @@ void draw(Area *a) void draw_background(Area *a, cairo_t *c) { if ((a->bg->fill_color.alpha > 0.0) || - (panel_config.mouse_effects && (a->has_mouse_over_effect || a->has_mouse_press_effect)) || - (area_has_gradient_fill(a))) { - - cairo_pattern_t *cairo_gradient_pattern; - - if (area_has_gradient_fill(a)) { - cairo_gradient_pattern = cairo_pattern_create_linear(0.0, 0.0, 0.0, a->height - top_bottom_border_width(a)); - cairo_pattern_add_color_stop_rgba(cairo_gradient_pattern, 0.1, a->bg->fill_color.rgb[0], a->bg->fill_color.rgb[1], a->bg->fill_color.rgb[2], a->bg->fill_color.alpha); - cairo_pattern_add_color_stop_rgba(cairo_gradient_pattern, 0.9, a->bg->fill_color2.rgb[0], a->bg->fill_color2.rgb[1], a->bg->fill_color2.rgb[2], a->bg->fill_color2.alpha); - cairo_set_source(c, cairo_gradient_pattern); - } else if (a->mouse_state == MOUSE_OVER) + (panel_config.mouse_effects && (a->has_mouse_over_effect || a->has_mouse_press_effect))) { + if (a->mouse_state == MOUSE_OVER) cairo_set_source_rgba(c, a->bg->fill_color_hover.rgb[0], a->bg->fill_color_hover.rgb[1], @@ -476,8 +481,21 @@ void draw_background(Area *a, cairo_t *c) a->bg->border.radius - a->bg->border.width / 1.571); cairo_fill(c); + + /* + cairo_pattern_t *cairo_gradient_pattern; + + if (area_has_gradient_fill(a)) { + cairo_gradient_pattern = cairo_pattern_create_linear(0.0, 0.0, 0.0, a->height - top_bottom_border_width(a)); + cairo_pattern_add_color_stop_rgba(cairo_gradient_pattern, 0.1, a->bg->fill_color.rgb[0], + a->bg->fill_color.rgb[1], a->bg->fill_color.rgb[2], a->bg->fill_color.alpha); + cairo_pattern_add_color_stop_rgba(cairo_gradient_pattern, 0.9, a->bg->fill_color2.rgb[0], + a->bg->fill_color2.rgb[1], a->bg->fill_color2.rgb[2], a->bg->fill_color2.alpha); + cairo_set_source(c, cairo_gradient_pattern); + } else if (area_has_gradient_fill(a)) - cairo_pattern_destroy(cairo_gradient_pattern); + cairo_pattern_destroy(cairo_gradient_pattern); + */ } if (a->bg->border.width > 0) { @@ -542,6 +560,7 @@ void add_area(Area *a, Area *parent) schedule_redraw(parent); panel_refresh = TRUE; } + init_area_gradients(a); } void free_area(Area *a) @@ -570,6 +589,7 @@ void free_area(Area *a) if (mouse_over_area == a) { mouse_over_area = NULL; } + free_area_gradients(a); } void mouse_over(Area *area, int pressed) @@ -802,20 +822,20 @@ void area_dump_geometry(Area *area, int indent) area->width, area->height); fprintf(stderr, - "%*sBorder: left = %d, right = %d, top = %d, bottom = %d\n", - indent, - "", - left_border_width(area), - right_border_width(area), - top_border_width(area), - bottom_border_width(area)); + "%*sBorder: left = %d, right = %d, top = %d, bottom = %d\n", + indent, + "", + left_border_width(area), + right_border_width(area), + top_border_width(area), + bottom_border_width(area)); fprintf(stderr, - "%*sPadding: left = right = %d, top = bottom = %d, spacing = %d\n", - indent, - "", - area->paddingxlr, - area->paddingy, - area->paddingx); + "%*sPadding: left = right = %d, top = bottom = %d, spacing = %d\n", + indent, + "", + area->paddingxlr, + area->paddingy, + area->paddingx); if (area->_dump_geometry) area->_dump_geometry(area, indent); if (area->children) { @@ -826,17 +846,104 @@ void area_dump_geometry(Area *area, int indent) } } -gboolean area_has_gradient_fill(Area *area) +void instantiate_gradient_offsets(Area *area, GradientInstance *gi, GList *offsets, GList **offset_instances) { - if (!area->bg->gradient) - return FALSE; - else if ((area->bg->fill_color.alpha <= 0.0 ) && (area->bg->fill_color2.alpha <= 0.0)) - return FALSE; - else if ((area->bg->fill_color.rgb[0] == area->bg->fill_color2.rgb[0]) && - (area->bg->fill_color.rgb[1] == area->bg->fill_color2.rgb[1]) && - (area->bg->fill_color.rgb[2] == area->bg->fill_color2.rgb[2]) && - (area->bg->fill_color.alpha == area->bg->fill_color2.alpha)) - return FALSE; - else return TRUE; - + for (GList *l = offsets; l; l = l->next) { + Offset *offset = (Offset *)l->data; + OffsetInstance *offset_instance = (OffsetInstance *)calloc(1, sizeof(OffsetInstance)); + offset_instance->constant = offset->constant; + if (offset_instance->constant) { + offset_instance->constant_value = offset->constant_value; + } else { + offset_instance->variable = offset->variable; + offset_instance->multiplier = offset->multiplier; + if (offset->variable_element == ORIGIN_ELEMENT) + offset_instance->variable_element = area; + else if (offset->variable_element == ORIGIN_PARENT) + offset_instance->variable_element = area->parent ? (Area *)area->parent : area; + else if (offset->variable_element == ORIGIN_PANEL) + offset_instance->variable_element = (Area *)area->panel; + else if (offset->variable_element == ORIGIN_SCREEN) + // TODO + offset_instance->variable_element = (Area *)area->panel; + else if (offset->variable_element == ORIGIN_DESKTOP) + // TODO + offset_instance->variable_element = (Area *)area->panel; + else + g_assert_not_reached(); + *offset_instances = g_list_append(*offset_instances, offset_instance); + offset_instance->variable_element->dependent_gradients = + g_list_append(offset_instance->variable_element->dependent_gradients, gi); + gi->gradient_dependencies = g_list_append(gi->gradient_dependencies, offset_instance->variable_element); + } + } +} + +void free_gradient_offsets(GradientInstance *gi, GList **offset_instances) +{ + for (GList *l = *offset_instances; l; l = l->next) { + OffsetInstance *offset_instance = (OffsetInstance *)l->data; + if (!offset_instance->constant) { + offset_instance->variable_element->dependent_gradients = + g_list_remove_all(offset_instance->variable_element->dependent_gradients, gi); + gi->gradient_dependencies = g_list_remove_all(gi->gradient_dependencies, offset_instance->variable_element); + } + } + g_list_free_full(*offset_instances, free); +} + +void instantiate_gradient_point(Area *area, + GradientInstance *gi, + ControlPoint *control, + ControlPointInstance *control_instance) +{ + instantiate_gradient_offsets(area, gi, control->offsets_x, &control_instance->offsets_x); + instantiate_gradient_offsets(area, gi, control->offsets_y, &control_instance->offsets_y); + instantiate_gradient_offsets(area, gi, control->offsets_r, &control_instance->offsets_r); +} + +void free_gradient_point(GradientInstance *gi, ControlPointInstance *control_instance) +{ + free_gradient_offsets(gi, &control_instance->offsets_x); + free_gradient_offsets(gi, &control_instance->offsets_y); + free_gradient_offsets(gi, &control_instance->offsets_r); +} + +void instantiate_gradient(Area *area, GradientClass *g, GradientInstance *gi) +{ + gi->gradient_class = g; + gi->area = area; + gi->from.origin = area; + instantiate_gradient_point(area, gi, &g->from, &gi->from); + instantiate_gradient_point(area, gi, &g->to, &gi->to); +} + +void free_gradient(GradientInstance *gi) +{ + free_gradient_point(gi, &gi->from); + free_gradient_point(gi, &gi->to); +} + +void init_area_gradients(Area *area) +{ + for (GList *l = area->gradients; l; l = l->next) { + GradientClass *g = (GradientClass *)l->data; + GradientInstance *gi = (GradientInstance *)calloc(1, sizeof(GradientInstance)); + instantiate_gradient(area, g, gi); + area->gradient_instances = g_list_append(area->gradient_instances, gi); + } +} + +void free_area_gradients(Area *area) +{ + for (GList *l = area->gradient_instances; l; l = l->next) { + GradientInstance *gi = (GradientInstance *)l->data; + free_gradient(gi); + } + g_list_free_full(area->gradient_instances, free); +} + +void update_gradient(GradientInstance *gi) +{ + // TODO } diff --git a/src/util/area.h b/src/util/area.h index 76e66b7..bfd64ce 100644 --- a/src/util/area.h +++ b/src/util/area.h @@ -11,6 +11,9 @@ #include #include +#include "color.h" +#include "gradient.h" + // DATA ORGANISATION // // Areas in tint2 are similar to widgets in a GUI. @@ -117,13 +120,6 @@ // The caller takes ownership of the pointer. // The Area's _get_tooltip_text member must point to this function. -typedef struct Color { - // Values are in [0, 1], with 0 meaning no intensity. - double rgb[3]; - // Values are in [0, 1], with 0 meaning fully transparent, 1 meaning fully opaque. - double alpha; -} Color; - typedef enum BorderMask { BORDER_TOP = 1 << 0, BORDER_BOTTOM = 1 << 1, @@ -147,8 +143,6 @@ typedef struct Border { typedef struct Background { // Normal state Color fill_color; - Color fill_color2; - gboolean gradient; Border border; // On mouse hover Color fill_color_hover; @@ -179,6 +173,12 @@ typedef struct Area { // Size, including borders int width, height; Background *bg; + // Each element is a pointer to a GradientClass (list can be empty), no ownership + GList *gradients; + // Each element is a GradientInstance attached to this Area (list can be empty) + GList *gradient_instances; + // Each element is a GradientInstance that depends on this Area's geometry (position or size) + GList *dependent_gradients; // List of children, each one a pointer to Area GList *children; // Pointer to the parent Area or NULL @@ -311,11 +311,15 @@ gboolean area_is_under_mouse(void *obj, int x, int y); // they are outside the drawing area of the button. gboolean full_width_area_is_under_mouse(void *obj, int x, int y); +void init_area_gradients(Area *area); +void free_area_gradients(Area *area); + void area_dump_geometry(Area *area, int indent); void mouse_over(Area *area, int pressed); void mouse_out(); -gboolean area_has_gradient_fill(Area *area); +void update_gradient(GradientInstance *gi); +void update_dependent_gradients(Area *a); #endif diff --git a/src/util/color.c b/src/util/color.c new file mode 100644 index 0000000..73452be --- /dev/null +++ b/src/util/color.c @@ -0,0 +1,2 @@ +#include "color.h" + diff --git a/src/util/color.h b/src/util/color.h new file mode 100644 index 0000000..5e97bf9 --- /dev/null +++ b/src/util/color.h @@ -0,0 +1,11 @@ +#ifndef COLOR_H +#define COLOR_H + +typedef struct Color { + // Values are in [0, 1], with 0 meaning no intensity. + double rgb[3]; + // Values are in [0, 1], with 0 meaning fully transparent, 1 meaning fully opaque. + double alpha; +} Color; + +#endif // COLOR_H diff --git a/src/util/common.c b/src/util/common.c index c45b996..fb000c2 100644 --- a/src/util/common.c +++ b/src/util/common.c @@ -201,6 +201,7 @@ void get_color(char *hex, double *rgb) void extract_values(const char *value, char **value1, char **value2, char **value3) { + char *value0 = strdup(value); char *b = 0, *c = 0; if (*value1) @@ -210,14 +211,14 @@ void extract_values(const char *value, char **value1, char **value2, char **valu if (*value3) free(*value3); - if ((b = strchr(value, ' '))) { + if ((b = strchr(value0, ' '))) { b[0] = '\0'; b++; } else { *value2 = 0; *value3 = 0; } - *value1 = strdup(value); + *value1 = strdup(value0); g_strstrip(*value1); if (b) { @@ -236,6 +237,63 @@ void extract_values(const char *value, char **value1, char **value2, char **valu *value3 = strdup(c); g_strstrip(*value3); } + + free(value0); +} + +void extract_values_4(const char *value, char **value1, char **value2, char **value3, char **value4) +{ + char *value0 = strdup(value); + char *b = 0, *c = 0, *d; + + if (*value1) + free(*value1); + if (*value2) + free(*value2); + if (*value3) + free(*value3); + if (*value4) + free(*value4); + + if ((b = strchr(value0, ' '))) { + b[0] = '\0'; + b++; + } else { + *value2 = 0; + *value3 = 0; + *value4 = 0; + } + *value1 = strdup(value0); + g_strstrip(*value1); + + if (b) { + if ((c = strchr(b, ' '))) { + c[0] = '\0'; + c++; + } else { + c = 0; + *value3 = 0; + *value4 = 0; + } + *value2 = strdup(b); + g_strstrip(*value2); + } + + if (c) { + if ((d = strchr(c, ' '))) { + d[0] = '\0'; + d++; + } else { + d = 0; + *value4 = 0; + } + *value3 = strdup(c); + g_strstrip(*value3); + + *value4 = strdup(d); + g_strstrip(*value4); + } + free(value0); } void adjust_asb(DATA32 *data, int w, int h, float alpha_adjust, float satur_adjust, float bright_adjust) diff --git a/src/util/common.h b/src/util/common.h index 44a8590..b38880e 100644 --- a/src/util/common.h +++ b/src/util/common.h @@ -51,6 +51,7 @@ void copy_file(const char *path_src, const char *path_dest); gboolean parse_line(const char *line, char **key, char **value); void extract_values(const char *value, char **value1, char **value2, char **value3); +void extract_values_4(const char *value, char **value1, char **value2, char **value3, char **value4); // Executes a command in a shell. void tint_exec(const char *command); diff --git a/src/util/gradient.c b/src/util/gradient.c new file mode 100644 index 0000000..6266179 --- /dev/null +++ b/src/util/gradient.c @@ -0,0 +1,278 @@ +#include "gradient.h" + +#include +#include +#include + +#include "common.h" + +gboolean read_double(const char *str, double *value) +{ + if (!str[0]) + return FALSE; + char *end; + *value = strtod(str, &end); + if (end[0]) + return FALSE; + return TRUE; +} + +gboolean read_double_with_percent(const char *str, double *value) +{ + if (!str[0]) + return FALSE; + char *end; + *value = strtod(str, &end); + if (end[0] == '%' && !end[1]) { + *value *= 0.01; + return TRUE; + } + if (end[0]) + return FALSE; + return TRUE; +} + +GradientType gradient_type_from_string(const char *str) +{ + if (g_str_equal(str, "horizontal")) + return GRADIENT_HORIZONTAL; + if (g_str_equal(str, "vertical")) + return GRADIENT_VERTICAL; + if (g_str_equal(str, "centered")) + return GRADIENT_CENTERED; + if (g_str_equal(str, "linear")) + return GRADIENT_LINEAR; + if (g_str_equal(str, "radial")) + return GRADIENT_RADIAL; + fprintf(stderr, RED "Invalid gradient type: %s" RESET "\n", str); + return GRADIENT_VERTICAL; +} + +gboolean read_origin_from_string(const char *str, Origin *element) +{ + if (g_str_equal(str, "element")) { + *element = ORIGIN_ELEMENT; + return TRUE; + } + if (g_str_equal(str, "parent")) { + *element = ORIGIN_PARENT; + return TRUE; + } + if (g_str_equal(str, "panel")) { + *element = ORIGIN_PANEL; + return TRUE; + } + if (g_str_equal(str, "screen")) { + *element = ORIGIN_SCREEN; + return TRUE; + } + if (g_str_equal(str, "desktop")) { + *element = ORIGIN_DESKTOP; + return TRUE; + } + return FALSE; +} + +Origin origin_from_string(const char *str) +{ + Origin result; + if (read_origin_from_string(str, &result)) + return result; + fprintf(stderr, RED "Invalid origin type: %s" RESET "\n", str); + return ORIGIN_ELEMENT; +} + +gboolean read_size_from_string(const char *str, SizeVariable *variable) +{ + if (g_str_equal(str, "width")) { + *variable = SIZE_WIDTH; + return TRUE; + } + if (g_str_equal(str, "height")) { + *variable = SIZE_HEIGHT; + return TRUE; + } + if (g_str_equal(str, "left")) { + *variable = SIZE_LEFT; + return TRUE; + } + if (g_str_equal(str, "right")) { + *variable = SIZE_RIGHT; + return TRUE; + } + if (g_str_equal(str, "top")) { + *variable = SIZE_TOP; + return TRUE; + } + if (g_str_equal(str, "bottom")) { + *variable = SIZE_BOTTOM; + return TRUE; + } + if (g_str_equal(str, "center")) { + *variable = SIZE_CENTER; + return TRUE; + } + if (g_str_equal(str, "radius")) { + *variable = SIZE_RADIUS; + return TRUE; + } + return FALSE; +} + +gboolean read_size_variable_from_string(const char *str, + Origin *variable_element, + SizeVariable *variable, + double *multiplier) +{ + if (read_size_from_string(str, variable)) { + *variable_element = ORIGIN_ELEMENT; + *multiplier = 1; + return TRUE; + } + + char *value1 = 0, *value2 = 0, *value3 = 0, *value4 = 0; + extract_values_4(str, &value1, &value2, &value3, &value4); + + if (value1 && value2 && !value3) { + if (read_origin_from_string(value1, variable_element) && read_size_from_string(value2, variable)) { + *multiplier = 1; + if (value1) + free(value1); + if (value2) + free(value2); + if (value3) + free(value3); + if (value4) + free(value4); + return TRUE; + } + } + + if (value1 && value2 && value3 && value4) { + if (read_origin_from_string(value1, variable_element) && read_size_from_string(value2, variable) && + g_str_equal(value3, "*") && read_double_with_percent(value4, multiplier)) { + if (value1) + free(value1); + if (value2) + free(value2); + if (value3) + free(value3); + if (value4) + free(value4); + return TRUE; + } + } + + if (value1) + free(value1); + if (value2) + free(value2); + if (value3) + free(value3); + if (value4) + free(value4); + + return FALSE; +} + +Offset *offset_from_string(const char *str) +{ + Offset *offset = (Offset *)calloc(1, sizeof(Offset)); + // number ? + if (read_double(str, &offset->constant_value)) { + offset->constant = TRUE; + return offset; + } + // SIZE ? + offset->constant = FALSE; + + if (read_size_variable_from_string(str, &offset->variable_element, &offset->variable, &offset->multiplier)) { + return offset; + } + + free(offset); + return NULL; +} + +void init_gradient(GradientClass *g, GradientType type) +{ + memset(g, 0, sizeof(*g)); + g->type = type; + if (g->type == GRADIENT_VERTICAL) { + g->from.origin = ORIGIN_ELEMENT; + Offset *offset_top = (Offset *)calloc(1, sizeof(Offset)); + offset_top->constant = TRUE; + offset_top->constant_value = 0; + g->from.offsets_y = g_list_append(g->from.offsets_y, offset_top); + Offset *offset_bottom = (Offset *)calloc(1, sizeof(Offset)); + offset_bottom->constant = FALSE; + offset_bottom->variable_element = ORIGIN_ELEMENT; + offset_bottom->variable = SIZE_HEIGHT; + offset_bottom->multiplier = 1.0; + g->from.offsets_y = g_list_append(g->from.offsets_y, offset_bottom); + } else if (g->type == GRADIENT_HORIZONTAL) { + g->from.origin = ORIGIN_ELEMENT; + Offset *offset_left = (Offset *)calloc(1, sizeof(Offset)); + offset_left->constant = TRUE; + offset_left->constant_value = 0; + g->from.offsets_x = g_list_append(g->from.offsets_x, offset_left); + Offset *offset_right = (Offset *)calloc(1, sizeof(Offset)); + offset_right->constant = FALSE; + offset_right->variable_element = ORIGIN_ELEMENT; + offset_right->variable = SIZE_WIDTH; + offset_right->multiplier = 1.0; + g->from.offsets_x = g_list_append(g->from.offsets_x, offset_right); + } else if (g->type == GRADIENT_CENTERED) { + g->from.origin = ORIGIN_ELEMENT; + Offset *offset_center_x = (Offset *)calloc(1, sizeof(Offset)); + offset_center_x->constant = FALSE; + offset_center_x->variable_element = ORIGIN_ELEMENT; + offset_center_x->variable = SIZE_CENTER; + offset_center_x->multiplier = 1.0; + g->from.offsets_x = g_list_append(g->from.offsets_x, offset_center_x); + Offset *offset_center_y = (Offset *)calloc(1, sizeof(Offset)); + offset_center_y->constant = FALSE; + offset_center_y->variable_element = ORIGIN_ELEMENT; + offset_center_y->variable = SIZE_CENTER; + offset_center_y->multiplier = 1.0; + g->from.offsets_y = g_list_append(g->from.offsets_y, offset_center_y); + Offset *offset_center_r = (Offset *)calloc(1, sizeof(Offset)); + offset_center_x->constant = TRUE; + offset_center_x->constant_value = 0; + g->from.offsets_r = g_list_append(g->from.offsets_r, offset_center_r); + g->to.origin = ORIGIN_ELEMENT; + offset_center_x = (Offset *)calloc(1, sizeof(Offset)); + offset_center_x->constant = FALSE; + offset_center_x->variable_element = ORIGIN_ELEMENT; + offset_center_x->variable = SIZE_CENTER; + offset_center_x->multiplier = 1.0; + g->to.offsets_x = g_list_append(g->to.offsets_x, offset_center_x); + offset_center_y = (Offset *)calloc(1, sizeof(Offset)); + offset_center_y->constant = FALSE; + offset_center_y->variable_element = ORIGIN_ELEMENT; + offset_center_y->variable = SIZE_CENTER; + offset_center_y->multiplier = 1.0; + g->to.offsets_y = g_list_append(g->to.offsets_y, offset_center_y); + offset_center_r = (Offset *)calloc(1, sizeof(Offset)); + offset_center_r->constant = FALSE; + offset_center_r->variable_element = ORIGIN_ELEMENT; + offset_center_r->variable = SIZE_RADIUS; + offset_center_r->multiplier = 1.0; + g->to.offsets_r = g_list_append(g->to.offsets_r, offset_center_r); + } else if (g->type == GRADIENT_LINEAR) { + // Nothing to do, the user has to add control points + } else if (g->type == GRADIENT_RADIAL) { + // Nothing to do, the user has to add control points + } +} + +void cleanup_gradient(GradientClass *g) +{ + g_list_free_full(g->extra_color_stops, free); + g_list_free_full(g->from.offsets_x, free); + g_list_free_full(g->from.offsets_y, free); + g_list_free_full(g->from.offsets_r, free); + g_list_free_full(g->to.offsets_x, free); + g_list_free_full(g->to.offsets_y, free); + g_list_free_full(g->to.offsets_r, free); +} diff --git a/src/util/gradient.h b/src/util/gradient.h new file mode 100644 index 0000000..fff9f6e --- /dev/null +++ b/src/util/gradient.h @@ -0,0 +1,115 @@ +#ifndef GRADIENT_H +#define GRADIENT_H + +#include +#include + +#include "color.h" + +////////////////////////////////////////////////////////////////////// +// Gradient types read from config options, not associated to any area + +typedef enum GradientType { + GRADIENT_VERTICAL = 0, + GRADIENT_HORIZONTAL, + GRADIENT_CENTERED, + GRADIENT_LINEAR, + GRADIENT_RADIAL +} GradientType; + +typedef struct ColorStop { + Color color; + // offset in 0-1 + double offset; +} ColorStop; + +typedef enum Origin { + ORIGIN_ELEMENT = 0, + ORIGIN_PARENT, + ORIGIN_PANEL, + ORIGIN_SCREEN, + ORIGIN_DESKTOP +} Origin; + +typedef enum SizeVariable { + SIZE_WIDTH = 0, + SIZE_HEIGHT, + SIZE_LEFT, + SIZE_RIGHT, + SIZE_TOP, + SIZE_BOTTOM, + SIZE_CENTER, + SIZE_RADIUS +} SizeVariable; + +typedef struct Offset { + gboolean constant; + // if constant == true + double constant_value; + // else + Origin variable_element; + SizeVariable variable; + double multiplier; +} Offset; + +typedef struct ControlPoint { + Origin origin; + // Each element is an Offset + GList *offsets_x; + GList *offsets_y; + // Defined only for radial gradients + GList *offsets_r; +} ControlPoint; + +typedef struct GradientClass { + GradientType type; + Color start_color; + Color end_color; + // Each element is a ColorStop + GList *extra_color_stops; + ControlPoint from; + ControlPoint to; +} GradientClass; + +GradientType gradient_type_from_string(const char *str); +Origin origin_from_string(const char *str); +Offset *offset_from_string(const char *str); +void init_gradient(GradientClass *g, GradientType type); +void cleanup_gradient(GradientClass *g); + +///////////////////////////////////////// +// Gradient instances associated to Areas + +struct Area; +typedef struct Area Area; + +typedef struct OffsetInstance { + gboolean constant; + // if constant == true + double constant_value; + // else + Area *variable_element; + SizeVariable variable; + double multiplier; +} OffsetInstance; + +typedef struct ControlPointInstance { + Area *origin; + // Each element is an OffsetInstance + GList *offsets_x; + GList *offsets_y; + GList *offsets_r; +} ControlPointInstance; + +typedef struct GradientInstance { + GradientClass *gradient_class; + Area *area; + ControlPointInstance from; + ControlPointInstance to; + cairo_pattern_t *pattern; + // Each element is an Area whose geometry is used to compute this gradient + // TODO why do we need it? + GList *gradient_dependencies; +} GradientInstance; + +#endif // GRADIENT_H diff --git a/tint2.files b/tint2.files index 91cdf95..2e17683 100644 --- a/tint2.files +++ b/tint2.files @@ -210,3 +210,7 @@ themes/vertical-neutral-icons.tint2rc doc/tint2.md src/separator/separator.c src/separator/separator.h +src/util/gradient.h +src/util/gradient.c +src/util/color.h +src/util/color.c