#include #include #include #include #include #include "../action.h" #include "../block.h" #include "../format.h" #include "../any_log.h" typedef enum { KNOB_NONE, KNOB_CAPSULE, KNOB_TRIANGLE, KNOB_RECTANGLE, } block_slider_knob_t; typedef struct { block_spec_t block; int value; unsigned int height; unsigned int width; unsigned int line_width; gradient_t bar_color; gradient_t line_color; gradient_t bg_color; bool seekable; block_slider_knob_t knob; unsigned int knob_height; unsigned int knob_width; unsigned int knob_line_width; int knob_x_offset; int knob_y_offset; gradient_t knob_color; gradient_t knob_line_color; double knob_rotation; action_t *left_click; action_t *middle_click; action_t *right_click; } block_slider_t; static void block_slider_layout(block_t *block, layout_t *layout, layout_info_t info) { block_slider_t *slider = (block_slider_t *)block; layout->width = info.height + slider->width; } static void block_slider_render(layout_t *layout, cairo_t *cr) { block_slider_t *slider = (block_slider_t *)layout->block; int radius = slider->height / 2; int bar_y = layout->y + (layout->height - slider->height) / 2; int bar_x = layout->x + (layout->width - slider->width) / 2; cairo_matrix_t matrix; cairo_matrix_init_scale(&matrix, 1.0 / slider->width, 1.0); cairo_matrix_translate(&matrix, -bar_x, 0.0); cairo_pattern_t *pattern = slider->bg_color.pattern; if (pattern != NULL) { render_capsule_fast(cr, bar_x, bar_y, slider->width, radius, radius); cairo_pattern_set_matrix(pattern, &matrix); cairo_set_source(cr, pattern); cairo_fill(cr); } pattern = slider->bar_color.pattern; if (pattern != NULL) { int current = (slider->value * slider->width) / 100; render_capsule(cr, bar_x, bar_y, current, radius, radius); cairo_pattern_set_matrix(pattern, &matrix); cairo_set_source(cr, pattern); cairo_fill(cr); } pattern = slider->line_color.pattern; if (pattern != NULL) { int line_radius = radius; render_capsule_fast(cr, bar_x, bar_y, slider->width, radius, line_radius); cairo_pattern_set_matrix(pattern, &matrix); cairo_set_source(cr, pattern); cairo_set_line_width(cr, slider->line_width); cairo_stroke(cr); } if (slider->knob == KNOB_NONE || slider->knob_width == 0 || slider->knob_height == 0) return; int knob_height = slider->knob_height; int knob_width = slider->knob_width; int knob_radius = knob_height / 2; const double degree = M_PI / 180.0; double knob_rotation = slider->knob_rotation * degree; int knob_rw = knob_width * cos(knob_rotation) + knob_height * sin(knob_rotation); //int current = (slider->value * slider->width) / 100 - knob_rw / 2; int current = (slider->value * (slider->width - knob_rw)) / 100; int knob_x = bar_x + slider->line_width + slider->knob_x_offset + current - knob_rw / 2; int knob_y = bar_y + slider->knob_y_offset + radius - knob_radius; int t_x = knob_x + knob_width / 2; int t_y = knob_y + knob_height / 2; pattern = slider->knob_color.pattern; if (pattern != NULL) { cairo_pattern_set_matrix(pattern, &matrix); cairo_set_source(cr, pattern); cairo_save(cr); cairo_translate(cr, t_x, t_y); cairo_rotate(cr, knob_rotation); cairo_translate(cr, -t_x, -t_y); switch (slider->knob) { case KNOB_CAPSULE: render_capsule(cr, knob_x, knob_y, knob_width, knob_radius, knob_radius); break; case KNOB_TRIANGLE: render_triangle(cr, knob_x, knob_y, knob_width, knob_height); break; case KNOB_RECTANGLE: cairo_rectangle(cr, knob_x, knob_y, knob_width, knob_height); break; default: unreachable(); } cairo_restore(cr); cairo_fill(cr); } pattern = slider->knob_line_color.pattern; if (pattern != NULL) { cairo_pattern_set_matrix(pattern, &matrix); cairo_set_source(cr, pattern); cairo_set_line_width(cr, slider->knob_line_width); cairo_save(cr); cairo_translate(cr, t_x, t_y); cairo_rotate(cr, knob_rotation); cairo_translate(cr, -t_x, -t_y); switch (slider->knob) { case KNOB_CAPSULE: { int line_radius = knob_radius; render_capsule(cr, knob_x, knob_y, knob_width, knob_radius, line_radius); break; } case KNOB_TRIANGLE: render_triangle(cr, knob_x, knob_y, knob_width, knob_height); break; case KNOB_RECTANGLE: cairo_rectangle(cr, knob_x, knob_y, knob_width, knob_height); break; default: unreachable(); } cairo_restore(cr); cairo_stroke(cr); } } static void block_slider_event(layout_t *layout, config_t *config, event_t event) { block_slider_t *slider = (block_slider_t *)layout->block; if (layout->block->hidden) return; int value = slider->value; if (event_is_click(event)) { // TODO: Adjust the size to account for line_width // int bar_x = layout->x + (layout->width - slider->width) / 2; int bar_y = layout->y + (layout->height - slider->height) / 2; int bar_width = slider->width; int bar_height = slider->height; // NOTE: Consider disabling these actions for non seekable sliders bool clicked = check_capsule(event.x, event.y, bar_x, bar_y, bar_width, bar_height); action_t *action = layout->block->actions[event.type]; switch (event.type) { case EVENT_LEFT_CLICK: if (slider->left_click != NULL && clicked) action = slider->left_click; break; case EVENT_MIDDLE_CLICK: if (slider->middle_click != NULL && clicked) action = slider->middle_click; break; case EVENT_RIGHT_CLICK: if (slider->right_click != NULL && clicked) action = slider->right_click; break; default: unreachable(); } action_perform(action, layout->block, config); if (slider->seekable && clicked) slider->value = 100 * (event.x - bar_x) / (double)(bar_width - 1); } else { action_perform(layout->block->actions[event.type], layout->block, config); if (slider->seekable && event_is_scroll(event)) { int step = event.type == EVENT_SCROLL_DOWN ? -1 : 1; slider->value += step; if (slider->value > 100) slider->value = 100; if (slider->value < 0) slider->value = 0; } } if (slider->value != value) { log_value_debug("Updated slider value", "s:label", layout->block->label, "i:old_value", value, "i:new_value", slider->value); } } static void block_slider_init(block_t *block) { block->type = BLOCK_SPEC; block_slider_t *slider = (block_slider_t *)block; slider->block.layout_fn = block_slider_layout; slider->block.render_fn = block_slider_render; slider->block.event_fn = block_slider_event; } static void block_slider_clean(block_t *block) { block_slider_t *slider = (block_slider_t *)block; gradient_clean(&slider->bar_color); gradient_clean(&slider->line_color); gradient_clean(&slider->bg_color); gradient_clean(&slider->knob_color); gradient_clean(&slider->knob_line_color); } static int block_slider_validate(block_t *block, config_t *config) { block_slider_t *slider = (block_slider_t *)block; int errors = 0; if (slider->value < 0 || slider->value > 100) { log_error("Block '%s' requires '%s' to be between 0 and 100", block->label, "bar-value"); errors++; } if (slider->knob_width < slider->knob_height) { log_error("Block '%s' requires '%s' greater than '%s'", block->label, "knob-width", "knob-height"); errors++; } if (fabs(slider->knob_rotation) > 360.0) { log_error("Block '%s' requires '%s' to be at most 360", block->label, "knob-rotation"); errors++; } return errors; } static bool block_slider_resolve(block_t *block, config_t *config) { block_slider_t *slider = (block_slider_t *)block; action_t **actions[] = { &slider->left_click, &slider->middle_click, &slider->right_click, }; for (size_t i = 0; i < 3; i++) { if (!block_resolve_action(block, config, actions[i])) return false; } return true; } static config_enum_t knob_shape_enum[] = { { "none", KNOB_NONE }, { "capsule", KNOB_CAPSULE }, { "triangle", KNOB_TRIANGLE }, { "rectangle", KNOB_RECTANGLE }, { 0 }, }; static const config_entry_t block_slider_entries[] = { // TODO: Ugly names { "bar-color", CONFIG_GRADIENT, NULL, NULL, offsetof(block_slider_t, bar_color) }, { "bar-line-color", CONFIG_GRADIENT, NULL, NULL, offsetof(block_slider_t, line_color) }, { "bar-bg-color", CONFIG_GRADIENT, NULL, NULL, offsetof(block_slider_t, bg_color) }, { "bar-height", CONFIG_UINT, NULL, NULL, offsetof(block_slider_t, height) }, { "bar-width", CONFIG_UINT, NULL, NULL, offsetof(block_slider_t, width) }, { "bar-line-width", CONFIG_UINT, NULL, NULL, offsetof(block_slider_t, line_width) }, { "bar-value", CONFIG_INT, NULL, NULL, offsetof(block_slider_t, value) }, { "seekable", CONFIG_BOOL, NULL, NULL, offsetof(block_slider_t, seekable) }, { "knob", CONFIG_ENUM, knob_shape_enum, NULL, offsetof(block_slider_t, knob) }, { "knob-height", CONFIG_UINT, NULL, NULL, offsetof(block_slider_t, knob_height) }, { "knob-width", CONFIG_UINT, NULL, NULL, offsetof(block_slider_t, knob_width) }, { "knob-line-width", CONFIG_UINT, NULL, NULL, offsetof(block_slider_t, knob_line_width) }, { "knob-color", CONFIG_GRADIENT, NULL, NULL, offsetof(block_slider_t, knob_color) }, { "knob-line-color", CONFIG_GRADIENT, NULL, NULL, offsetof(block_slider_t, knob_line_color) }, { "knob-x-offset", CONFIG_INT, NULL, NULL, offsetof(block_slider_t, knob_x_offset) }, { "knob-y-offset", CONFIG_INT, NULL, NULL, offsetof(block_slider_t, knob_y_offset) }, { "knob-rotation", CONFIG_DOUBLE, NULL, NULL, offsetof(block_slider_t, knob_rotation) }, { "left-click-bar", CONFIG_STRING, NULL, NULL, offsetof(block_slider_t, left_click) }, { "middle-click-bar", CONFIG_STRING, NULL, NULL, offsetof(block_slider_t, middle_click) }, { "right-click-bar", CONFIG_STRING, NULL, NULL, offsetof(block_slider_t, right_click) }, { 0 }, }; static config_status_t block_slider_change(block_t *block, config_t *config, const char *key, const char *value) { return config_read_entry(block_slider_entries, block, NULL, key, value); } const block_scheme_t block_slider_scheme = { .name = "slider", .entries = block_slider_entries, .size = sizeof(block_slider_t), .validate_change = true, .init_fn = block_slider_init, .clean_fn = block_slider_clean, .validate_fn = block_slider_validate, .resolve_fn = block_slider_resolve, .change_fn = block_slider_change, };