#include #include #include #include #include #include "draw.h" #include "button.h" #include "log.h" static void layout_destroy(Layout *layout) { if (layout->pl != NULL) g_object_unref(layout->pl); g_list_free_full(layout->children, (GDestroyNotify)layout_destroy); g_free(layout); } Drawer *draw_create(const char *font, int height, int left_pad, int right_pad, int top_pad) { Drawer *draw = g_malloc(sizeof(Drawer)); g_assert_nonnull(draw); log_debug("Pango loading font description '%s'", font); draw->desc = pango_font_description_from_string(font); log_debug("Pango found matching font '%s'", pango_font_description_get_family(draw->desc)); draw->height = height; draw->left_pad = left_pad; draw->right_pad = right_pad; draw->top_pad = top_pad; draw->layouts = NULL; log_debug("Draw context created [height=%d, left_pad=%d, right_pad=%d, top_pad=%d]", height, left_pad, right_pad, top_pad); return draw; } // TODO: Remove this static void compute_width(Drawer *draw, Window *win, int *width, int *height) { int screen_width = win->con->screen_size->width; int screen_height = win->con->screen_size->height; *width = round(screen_width - draw->right_pad - draw->left_pad); *height = round(draw->height); } static void paint_button(cairo_t *cr, const Layout *layout) { double degree = M_PI / 180.0; int radius = (layout->height - 2 * layout->y_pad) / 2; cairo_set_line_width(cr, layout->line_w); Color color = layout->btn->color; cairo_set_source_rgba(cr, color.r, color.g, color.b, color.a); cairo_new_sub_path(cr); cairo_arc(cr, layout->x + layout->x_pad + radius, layout->y + layout->y_pad + radius, radius, 90 * degree, 270 * degree); cairo_arc(cr, layout->x + layout->width - layout->x_pad - radius, layout->y + layout->y_pad + radius, radius, 270 * degree, 450 * degree); cairo_close_path(cr); cairo_fill(cr); color = layout->btn->line_color; cairo_set_source_rgba(cr, color.r, color.g, color.b, color.a); cairo_new_sub_path(cr); cairo_arc(cr, layout->x + layout->x_pad + radius, layout->y + layout->y_pad + radius, radius - layout->line_w, 90 * degree, 270 * degree); cairo_arc(cr, layout->x + layout->width - layout->x_pad - radius, layout->y + layout->y_pad + radius, radius - layout->line_w, 270 * degree, 450 * degree); cairo_close_path(cr); cairo_stroke(cr); } static void paint_text(cairo_t *cr, const Layout *layout) { int text_x = layout->x + layout->width / 2 - layout->text_w / 2; int text_y = layout->y + layout->height / 2 - layout->text_h / 2; Color color = CAST(layout->btn, ButtonSimple)->text_color; cairo_set_source_rgba(cr, color.r, color.g, color.b, color.a); cairo_move_to(cr, text_x, text_y); // NOTE: This works only if the text didn't change in size after the layouting pango_layout_set_text(layout->pl, CAST(layout->btn, ButtonSimple)->text, -1); pango_cairo_update_layout(cr, layout->pl); pango_cairo_show_layout(cr, layout->pl); } void draw_paint(Drawer *draw, Window *win) { int width, height; compute_width(draw, win, &width, &height); cairo_surface_t *surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height); cairo_t *cr = cairo_create(surface); cairo_set_fill_rule(cr, CAIRO_FILL_RULE_EVEN_ODD); cairo_set_operator(cr, CAIRO_OPERATOR_OVER); // Fill the background cairo_set_source_rgba(cr, draw->background.r, draw->background.g, draw->background.b, draw->background.a); cairo_paint(cr); for (GList *it = draw->layouts; it; it = it->next) { Layout *layout = it->data; Button *btn = layout->btn; cairo_push_group(cr); paint_button(cr, layout); if (btn->simple) { paint_text(cr, layout); } else { // TODO } if (btn->anim != NULL && btn->anim->paint_func != NULL) { if (btn->anim->start == 0) { btn->anim->start = g_get_monotonic_time(); log_debug("Starting animation [type=%d, start=%ld, duration=%ld, button=%p]", btn->anim->type, btn->anim->start, btn->anim->duration, btn); } if (!btn->anim->paint_func(btn->anim, cr, layout)) btn->anim->paint_func = NULL; } cairo_pop_group_to_source(cr); cairo_paint(cr); } cairo_destroy(cr); // TODO: Move these somewhere else window_move(win, draw->left_pad, draw->top_pad); window_resize(win, width, draw->height); window_paint_surface(win, surface, width, height); cairo_surface_destroy(surface); } // Works only for simple buttons static void layout_text(Layout *layout, PangoFontDescription *desc, int height, int radius) { const char *text = CAST(layout->btn, ButtonSimple)->text; pango_layout_set_font_description(layout->pl, desc); pango_layout_set_text(layout->pl, text, -1); pango_layout_set_alignment(layout->pl, PANGO_ALIGN_CENTER); int text_w, text_h; pango_layout_get_pixel_size(layout->pl, &text_w, &text_h); layout->text_w = ceil(text_w); layout->text_h = ceil(text_h); // If there is only one glyph the button should be round size_t text_l = g_utf8_strlen(text, -1); layout->width = text_l == 1 ? height : layout->text_w + 2 * radius; layout->height = height; } void draw_compute_layout(Drawer *draw, Window *win, GList *btns) { g_list_free_full(draw->layouts, (GDestroyNotify)layout_destroy); draw->layouts = NULL; int width, height; compute_width(draw, win, &width, &height); Button *prev = NULL; int x = 0; int radius = height / 2; int sep = 10; GList *layout_start[3] = { NULL }; int layout_end[3] = { 0 }; for (GList *it = btns; it; it = it->next) { Button *btn = it->data; Layout *layout = g_malloc(sizeof(Layout)); layout->btn = btn; layout->children = NULL; if (prev != NULL && prev->align == btn->align) x += sep; layout->line_w = btn->line_width; layout->x_pad = btn->x_pad; layout->y_pad = btn->y_pad; layout->x = x; layout->y = 0; draw->layouts = g_list_prepend(draw->layouts, layout); if (prev == NULL || prev->align != btn->align) { layout_start[btn->align] = draw->layouts; } if (!btn->simple) { ButtonGroup *group = CAST(btn, ButtonGroup); g_assert_nonnull(group->children); // If there is only one child treat a group like a single button if (group->children->next == NULL) { Button *child = group->children->data; g_assert_true(child->simple); layout->btn = child; } else { for (GList *it = group->children; it; it = it->next) { Button *btn = it->data; // Nested groups are not allowed g_assert_true(btn->simple); Layout *child = g_malloc(sizeof(Layout)); child->btn = btn; child->children = NULL; child->line_w = btn->line_width; child->x_pad = btn->x_pad; child->y_pad = btn->y_pad; child->x = x; child->y = 0; child->pl = pango_cairo_create_layout(window_get_context(win)); layout_text(child, draw->desc, height, radius); draw->layouts = g_list_prepend(draw->layouts, child); x += child->width + child->line_w * 2; if (it->next != NULL) x += sep; } layout->pl = NULL; layout->width = x - layout->x; layout->height = height; // FIXME: Temporary solution to make the antialiasing (or the circles not overlapping correctly) problem less noticeable bool fix_left = CAST(g_list_first(group->children)->data, Button)->x_pad == 0; bool fix_right = CAST(g_list_last(group->children)->data, Button)->x_pad == 0; if (fix_left) { layout->x += 1; layout->width -= 1; } if (fix_right) layout->width -= 1; } } if (layout->btn->simple) { layout->pl = pango_cairo_create_layout(window_get_context(win)); layout_text(layout, draw->desc, height, radius); // TODO: make it work for groups if (btn->anim != NULL && btn->anim->layout_func != NULL) { if (btn->anim->start == 0) { btn->anim->start = g_get_monotonic_time(); log_debug("Starting animation [type=%d, start=%ld, duration=%ld, button=%p]", btn->anim->type, btn->anim->start, btn->anim->duration, btn); } if (!btn->anim->layout_func(btn->anim, layout)) btn->anim->layout_func = NULL; } x += layout->width + layout->line_w * 2; } prev = btn; layout_end[btn->align] = x; } draw->layouts = g_list_reverse(draw->layouts); int layout_width[3] = { layout_end[PANGO_ALIGN_LEFT], layout_end[PANGO_ALIGN_CENTER] - layout_end[PANGO_ALIGN_LEFT], layout_end[PANGO_ALIGN_RIGHT] - MAX(layout_end[PANGO_ALIGN_CENTER], layout_end[PANGO_ALIGN_LEFT]), }; if (layout_end[PANGO_ALIGN_RIGHT] > width) { log_error("Layout is bigger than the window (%d vs %d)", layout_end[PANGO_ALIGN_RIGHT], width); } int center = round(width / 2); int center_off = center - round(layout_width[PANGO_ALIGN_CENTER] / 2) - layout_end[PANGO_ALIGN_LEFT]; log_debug("Aligning center layout [center=%d, off=%d]", center, center_off); for (GList *it = layout_start[PANGO_ALIGN_CENTER]; it != NULL && it != layout_start[PANGO_ALIGN_RIGHT]; it = it->next) { Layout *layout = it->data; layout->x += center_off; } // The width is off by 2 line width int first_line_w = CAST(layout_start[PANGO_ALIGN_RIGHT]->data, Layout)->line_w; int last_line_w = CAST(g_list_last(layout_start[PANGO_ALIGN_RIGHT])->data, Layout)->line_w; int right = width - layout_width[PANGO_ALIGN_RIGHT]; int right_off = right - MAX(layout_end[PANGO_ALIGN_LEFT], layout_end[PANGO_ALIGN_CENTER]) + first_line_w + last_line_w; log_debug("Aligning right layout [x=%d, off=%d]", right, right_off); for (GList *it = layout_start[PANGO_ALIGN_RIGHT]; it != NULL; it = it->next) { Layout *layout = it->data; layout->x += right_off; } log_debug("Updated layouts"); } void draw_set_background(Drawer *draw, Color background) { draw->background = background; } void draw_destroy(Drawer *draw) { g_list_free_full(draw->layouts, (GDestroyNotify)layout_destroy); pango_font_description_free(draw->desc); g_free(draw); } // vim: ts=4 sw=4 et