| /* |
| * Copyright © 2008 Kristian Høgsberg |
| * Copyright © 2012 Intel Corporation |
| * |
| * Permission is hereby granted, free of charge, to any person obtaining |
| * a copy of this software and associated documentation files (the |
| * "Software"), to deal in the Software without restriction, including |
| * without limitation the rights to use, copy, modify, merge, publish, |
| * distribute, sublicense, and/or sell copies of the Software, and to |
| * permit persons to whom the Software is furnished to do so, subject to |
| * the following conditions: |
| * |
| * The above copyright notice and this permission notice (including the |
| * next paragraph) shall be included in all copies or substantial |
| * portions of the Software. |
| * |
| * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
| * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
| * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
| * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS |
| * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN |
| * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN |
| * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
| * SOFTWARE. |
| */ |
| |
| #include "config.h" |
| |
| #include <stdint.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <stdio.h> |
| #include <math.h> |
| #include <wayland-util.h> |
| #include <cairo.h> |
| #include "cairo-util.h" |
| |
| #include "shared/helpers.h" |
| #include "image-loader.h" |
| #include "config-parser.h" |
| |
| #ifdef HAVE_PANGO |
| #include <pango/pangocairo.h> |
| #endif |
| |
| void |
| surface_flush_device(cairo_surface_t *surface) |
| { |
| cairo_device_t *device; |
| |
| device = cairo_surface_get_device(surface); |
| if (device) |
| cairo_device_flush(device); |
| } |
| |
| static int |
| blur_surface(cairo_surface_t *surface, int margin) |
| { |
| int32_t width, height, stride, x, y, z, w; |
| uint8_t *src, *dst; |
| uint32_t *s, *d, a, p; |
| int i, j, k, size, half; |
| uint32_t kernel[71]; |
| double f; |
| |
| size = ARRAY_LENGTH(kernel); |
| width = cairo_image_surface_get_width(surface); |
| height = cairo_image_surface_get_height(surface); |
| stride = cairo_image_surface_get_stride(surface); |
| src = cairo_image_surface_get_data(surface); |
| |
| dst = malloc(height * stride); |
| if (dst == NULL) |
| return -1; |
| |
| half = size / 2; |
| a = 0; |
| for (i = 0; i < size; i++) { |
| f = (i - half); |
| kernel[i] = exp(- f * f / ARRAY_LENGTH(kernel)) * 10000; |
| a += kernel[i]; |
| } |
| |
| for (i = 0; i < height; i++) { |
| s = (uint32_t *) (src + i * stride); |
| d = (uint32_t *) (dst + i * stride); |
| for (j = 0; j < width; j++) { |
| if (margin < j && j < width - margin) { |
| d[j] = s[j]; |
| continue; |
| } |
| |
| x = 0; |
| y = 0; |
| z = 0; |
| w = 0; |
| for (k = 0; k < size; k++) { |
| if (j - half + k < 0 || j - half + k >= width) |
| continue; |
| p = s[j - half + k]; |
| |
| x += (p >> 24) * kernel[k]; |
| y += ((p >> 16) & 0xff) * kernel[k]; |
| z += ((p >> 8) & 0xff) * kernel[k]; |
| w += (p & 0xff) * kernel[k]; |
| } |
| d[j] = (x / a << 24) | (y / a << 16) | (z / a << 8) | w / a; |
| } |
| } |
| |
| for (i = 0; i < height; i++) { |
| s = (uint32_t *) (dst + i * stride); |
| d = (uint32_t *) (src + i * stride); |
| for (j = 0; j < width; j++) { |
| if (margin <= i && i < height - margin) { |
| d[j] = s[j]; |
| continue; |
| } |
| |
| x = 0; |
| y = 0; |
| z = 0; |
| w = 0; |
| for (k = 0; k < size; k++) { |
| if (i - half + k < 0 || i - half + k >= height) |
| continue; |
| s = (uint32_t *) (dst + (i - half + k) * stride); |
| p = s[j]; |
| |
| x += (p >> 24) * kernel[k]; |
| y += ((p >> 16) & 0xff) * kernel[k]; |
| z += ((p >> 8) & 0xff) * kernel[k]; |
| w += (p & 0xff) * kernel[k]; |
| } |
| d[j] = (x / a << 24) | (y / a << 16) | (z / a << 8) | w / a; |
| } |
| } |
| |
| free(dst); |
| cairo_surface_mark_dirty(surface); |
| |
| return 0; |
| } |
| |
| void |
| render_shadow(cairo_t *cr, cairo_surface_t *surface, |
| int x, int y, int width, int height, int margin, int top_margin) |
| { |
| cairo_pattern_t *pattern; |
| cairo_matrix_t matrix; |
| int i, fx, fy, shadow_height, shadow_width; |
| |
| cairo_set_source_rgba(cr, 0, 0, 0, 0.45); |
| cairo_set_operator(cr, CAIRO_OPERATOR_OVER); |
| pattern = cairo_pattern_create_for_surface (surface); |
| cairo_pattern_set_filter(pattern, CAIRO_FILTER_NEAREST); |
| |
| for (i = 0; i < 4; i++) { |
| /* when fy is set, then we are working with lower corners, |
| * when fx is set, then we are working with right corners |
| * |
| * 00 ------- 01 |
| * | | |
| * | | |
| * 10 ------- 11 |
| */ |
| fx = i & 1; |
| fy = i >> 1; |
| |
| cairo_matrix_init_translate(&matrix, |
| -x + fx * (128 - width), |
| -y + fy * (128 - height)); |
| cairo_pattern_set_matrix(pattern, &matrix); |
| |
| shadow_width = margin; |
| shadow_height = fy ? margin : top_margin; |
| |
| /* if the shadows together are greater than the surface, we need |
| * to fix it - set the shadow size to the half of |
| * the size of surface. Also handle the case when the size is |
| * not divisible by 2. In that case we need one part of the |
| * shadow to be one pixel greater. !fy or !fx, respectively, |
| * will do the work. |
| */ |
| if (height < 2 * shadow_height) |
| shadow_height = (height + !fy) / 2; |
| |
| if (width < 2 * shadow_width) |
| shadow_width = (width + !fx) / 2; |
| |
| cairo_reset_clip(cr); |
| cairo_rectangle(cr, |
| x + fx * (width - shadow_width), |
| y + fy * (height - shadow_height), |
| shadow_width, shadow_height); |
| cairo_clip (cr); |
| cairo_mask(cr, pattern); |
| } |
| |
| |
| shadow_width = width - 2 * margin; |
| shadow_height = top_margin; |
| if (height < 2 * shadow_height) |
| shadow_height = height / 2; |
| |
| if (shadow_width > 0 && shadow_height) { |
| /* Top stretch */ |
| cairo_matrix_init_translate(&matrix, 60, 0); |
| cairo_matrix_scale(&matrix, 8.0 / width, 1); |
| cairo_matrix_translate(&matrix, -x - width / 2, -y); |
| cairo_pattern_set_matrix(pattern, &matrix); |
| cairo_rectangle(cr, x + margin, y, shadow_width, shadow_height); |
| |
| cairo_reset_clip(cr); |
| cairo_rectangle(cr, |
| x + margin, y, |
| shadow_width, shadow_height); |
| cairo_clip (cr); |
| cairo_mask(cr, pattern); |
| |
| /* Bottom stretch */ |
| cairo_matrix_translate(&matrix, 0, -height + 128); |
| cairo_pattern_set_matrix(pattern, &matrix); |
| |
| cairo_reset_clip(cr); |
| cairo_rectangle(cr, x + margin, y + height - margin, |
| shadow_width, margin); |
| cairo_clip (cr); |
| cairo_mask(cr, pattern); |
| } |
| |
| shadow_width = margin; |
| if (width < 2 * shadow_width) |
| shadow_width = width / 2; |
| |
| shadow_height = height - margin - top_margin; |
| |
| /* if height is smaller than sum of margins, |
| * then the shadow is already done by the corners */ |
| if (shadow_height > 0 && shadow_width) { |
| /* Left stretch */ |
| cairo_matrix_init_translate(&matrix, 0, 60); |
| cairo_matrix_scale(&matrix, 1, 8.0 / height); |
| cairo_matrix_translate(&matrix, -x, -y - height / 2); |
| cairo_pattern_set_matrix(pattern, &matrix); |
| cairo_reset_clip(cr); |
| cairo_rectangle(cr, x, y + top_margin, |
| shadow_width, shadow_height); |
| cairo_clip (cr); |
| cairo_mask(cr, pattern); |
| |
| /* Right stretch */ |
| cairo_matrix_translate(&matrix, -width + 128, 0); |
| cairo_pattern_set_matrix(pattern, &matrix); |
| cairo_rectangle(cr, x + width - shadow_width, y + top_margin, |
| shadow_width, shadow_height); |
| cairo_reset_clip(cr); |
| cairo_clip (cr); |
| cairo_mask(cr, pattern); |
| } |
| |
| cairo_pattern_destroy(pattern); |
| cairo_reset_clip(cr); |
| } |
| |
| void |
| tile_source(cairo_t *cr, cairo_surface_t *surface, |
| int x, int y, int width, int height, int margin, int top_margin) |
| { |
| cairo_pattern_t *pattern; |
| cairo_matrix_t matrix; |
| int i, fx, fy, vmargin; |
| |
| cairo_set_operator(cr, CAIRO_OPERATOR_OVER); |
| pattern = cairo_pattern_create_for_surface (surface); |
| cairo_pattern_set_filter(pattern, CAIRO_FILTER_NEAREST); |
| cairo_set_source(cr, pattern); |
| cairo_pattern_destroy(pattern); |
| |
| for (i = 0; i < 4; i++) { |
| fx = i & 1; |
| fy = i >> 1; |
| |
| cairo_matrix_init_translate(&matrix, |
| -x + fx * (128 - width), |
| -y + fy * (128 - height)); |
| cairo_pattern_set_matrix(pattern, &matrix); |
| |
| if (fy) |
| vmargin = margin; |
| else |
| vmargin = top_margin; |
| |
| cairo_rectangle(cr, |
| x + fx * (width - margin), |
| y + fy * (height - vmargin), |
| margin, vmargin); |
| cairo_fill(cr); |
| } |
| |
| /* Top stretch */ |
| cairo_matrix_init_translate(&matrix, 60, 0); |
| cairo_matrix_scale(&matrix, 8.0 / (width - 2 * margin), 1); |
| cairo_matrix_translate(&matrix, -x - width / 2, -y); |
| cairo_pattern_set_matrix(pattern, &matrix); |
| cairo_rectangle(cr, x + margin, y, width - 2 * margin, top_margin); |
| cairo_fill(cr); |
| |
| /* Bottom stretch */ |
| cairo_matrix_translate(&matrix, 0, -height + 128); |
| cairo_pattern_set_matrix(pattern, &matrix); |
| cairo_rectangle(cr, x + margin, y + height - margin, |
| width - 2 * margin, margin); |
| cairo_fill(cr); |
| |
| /* Left stretch */ |
| cairo_matrix_init_translate(&matrix, 0, 60); |
| cairo_matrix_scale(&matrix, 1, 8.0 / (height - margin - top_margin)); |
| cairo_matrix_translate(&matrix, -x, -y - height / 2); |
| cairo_pattern_set_matrix(pattern, &matrix); |
| cairo_rectangle(cr, x, y + top_margin, |
| margin, height - margin - top_margin); |
| cairo_fill(cr); |
| |
| /* Right stretch */ |
| cairo_matrix_translate(&matrix, -width + 128, 0); |
| cairo_pattern_set_matrix(pattern, &matrix); |
| cairo_rectangle(cr, x + width - margin, y + top_margin, |
| margin, height - margin - top_margin); |
| cairo_fill(cr); |
| } |
| |
| void |
| rounded_rect(cairo_t *cr, int x0, int y0, int x1, int y1, int radius) |
| { |
| cairo_move_to(cr, x0, y0 + radius); |
| cairo_arc(cr, x0 + radius, y0 + radius, radius, M_PI, 3 * M_PI / 2); |
| cairo_line_to(cr, x1 - radius, y0); |
| cairo_arc(cr, x1 - radius, y0 + radius, radius, 3 * M_PI / 2, 2 * M_PI); |
| cairo_line_to(cr, x1, y1 - radius); |
| cairo_arc(cr, x1 - radius, y1 - radius, radius, 0, M_PI / 2); |
| cairo_line_to(cr, x0 + radius, y1); |
| cairo_arc(cr, x0 + radius, y1 - radius, radius, M_PI / 2, M_PI); |
| cairo_close_path(cr); |
| } |
| |
| cairo_surface_t * |
| load_cairo_surface(const char *filename) |
| { |
| pixman_image_t *image; |
| int width, height, stride; |
| void *data; |
| |
| image = load_image(filename); |
| if (image == NULL) { |
| return NULL; |
| } |
| |
| data = pixman_image_get_data(image); |
| width = pixman_image_get_width(image); |
| height = pixman_image_get_height(image); |
| stride = pixman_image_get_stride(image); |
| |
| return cairo_image_surface_create_for_data(data, CAIRO_FORMAT_ARGB32, |
| width, height, stride); |
| } |
| |
| void |
| theme_set_background_source(struct theme *t, cairo_t *cr, uint32_t flags) |
| { |
| cairo_pattern_t *pattern; |
| |
| if (flags & THEME_FRAME_ACTIVE) { |
| pattern = cairo_pattern_create_linear(16, 16, 16, 112); |
| cairo_pattern_add_color_stop_rgb(pattern, 0.0, 1.0, 1.0, 1.0); |
| cairo_pattern_add_color_stop_rgb(pattern, 0.2, 0.8, 0.8, 0.8); |
| cairo_set_source(cr, pattern); |
| cairo_pattern_destroy(pattern); |
| } else { |
| cairo_set_source_rgba(cr, 0.75, 0.75, 0.75, 1); |
| } |
| } |
| |
| struct theme * |
| theme_create(void) |
| { |
| struct theme *t; |
| cairo_t *cr; |
| |
| t = malloc(sizeof *t); |
| if (t == NULL) |
| return NULL; |
| |
| t->margin = 32; |
| t->width = 6; |
| t->titlebar_height = 27; |
| t->frame_radius = 3; |
| t->shadow = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, 128, 128); |
| cr = cairo_create(t->shadow); |
| cairo_set_operator(cr, CAIRO_OPERATOR_OVER); |
| cairo_set_source_rgba(cr, 0, 0, 0, 1); |
| rounded_rect(cr, 32, 32, 96, 96, t->frame_radius); |
| cairo_fill(cr); |
| if (cairo_status (cr) != CAIRO_STATUS_SUCCESS) |
| goto err_shadow; |
| cairo_destroy(cr); |
| if (blur_surface(t->shadow, 64) == -1) |
| goto err_shadow; |
| |
| t->active_frame = |
| cairo_image_surface_create (CAIRO_FORMAT_ARGB32, 128, 128); |
| cr = cairo_create(t->active_frame); |
| cairo_set_operator(cr, CAIRO_OPERATOR_OVER); |
| |
| theme_set_background_source(t, cr, THEME_FRAME_ACTIVE); |
| rounded_rect(cr, 0, 0, 128, 128, t->frame_radius); |
| cairo_fill(cr); |
| |
| if (cairo_status(cr) != CAIRO_STATUS_SUCCESS) |
| goto err_active_frame; |
| |
| cairo_destroy(cr); |
| |
| t->inactive_frame = |
| cairo_image_surface_create (CAIRO_FORMAT_ARGB32, 128, 128); |
| cr = cairo_create(t->inactive_frame); |
| cairo_set_operator(cr, CAIRO_OPERATOR_OVER); |
| theme_set_background_source(t, cr, 0); |
| rounded_rect(cr, 0, 0, 128, 128, t->frame_radius); |
| cairo_fill(cr); |
| |
| if (cairo_status (cr) != CAIRO_STATUS_SUCCESS) |
| goto err_inactive_frame; |
| |
| cairo_destroy(cr); |
| |
| return t; |
| |
| err_inactive_frame: |
| cairo_surface_destroy(t->inactive_frame); |
| err_active_frame: |
| cairo_surface_destroy(t->active_frame); |
| err_shadow: |
| cairo_surface_destroy(t->shadow); |
| free(t); |
| return NULL; |
| } |
| |
| void |
| theme_destroy(struct theme *t) |
| { |
| cairo_surface_destroy(t->active_frame); |
| cairo_surface_destroy(t->inactive_frame); |
| cairo_surface_destroy(t->shadow); |
| free(t); |
| } |
| |
| #ifdef HAVE_PANGO |
| static PangoLayout * |
| create_layout(cairo_t *cr, const char *title) |
| { |
| PangoLayout *layout; |
| PangoFontDescription *desc; |
| |
| layout = pango_cairo_create_layout(cr); |
| pango_layout_set_text(layout, title, -1); |
| desc = pango_font_description_from_string("Sans Bold 10"); |
| pango_layout_set_font_description(layout, desc); |
| pango_font_description_free(desc); |
| pango_layout_set_ellipsize(layout, PANGO_ELLIPSIZE_END); |
| pango_layout_set_alignment(layout, PANGO_ALIGN_LEFT); |
| pango_layout_set_auto_dir (layout, FALSE); |
| pango_layout_set_single_paragraph_mode (layout, TRUE); |
| pango_layout_set_width (layout, -1); |
| |
| return layout; |
| } |
| #endif |
| |
| #ifdef HAVE_PANGO |
| #define SHOW_TEXT(cr) \ |
| pango_cairo_show_layout(cr, title_layout) |
| #else |
| #define SHOW_TEXT(cr) \ |
| cairo_show_text(cr, title) |
| #endif |
| |
| void |
| theme_render_frame(struct theme *t, |
| cairo_t *cr, int width, int height, |
| const char *title, cairo_rectangle_int_t *title_rect, |
| struct wl_list *buttons, uint32_t flags) |
| { |
| cairo_surface_t *source; |
| int x, y, margin, top_margin; |
| int text_width, text_height; |
| |
| cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); |
| cairo_set_source_rgba(cr, 0, 0, 0, 0); |
| cairo_paint(cr); |
| |
| if (flags & THEME_FRAME_MAXIMIZED) |
| margin = 0; |
| else { |
| render_shadow(cr, t->shadow, |
| 2, 2, width + 8, height + 8, |
| 64, 64); |
| margin = t->margin; |
| } |
| |
| if (flags & THEME_FRAME_ACTIVE) |
| source = t->active_frame; |
| else |
| source = t->inactive_frame; |
| |
| if (title || !wl_list_empty(buttons)) |
| top_margin = t->titlebar_height; |
| else |
| top_margin = t->width; |
| |
| tile_source(cr, source, |
| margin, margin, |
| width - margin * 2, height - margin * 2, |
| t->width, top_margin); |
| |
| if (title || !wl_list_empty(buttons)) { |
| |
| cairo_rectangle (cr, title_rect->x, title_rect->y, |
| title_rect->width, title_rect->height); |
| cairo_clip(cr); |
| cairo_set_operator(cr, CAIRO_OPERATOR_OVER); |
| |
| #ifdef HAVE_PANGO |
| PangoLayout *title_layout; |
| PangoRectangle logical; |
| |
| title_layout = create_layout(cr, title); |
| |
| pango_layout_get_pixel_extents (title_layout, NULL, &logical); |
| text_width = MIN(title_rect->width, logical.width); |
| text_height = logical.height; |
| if (text_width < logical.width) |
| pango_layout_set_width (title_layout, text_width * PANGO_SCALE); |
| |
| #else |
| cairo_text_extents_t extents; |
| cairo_font_extents_t font_extents; |
| |
| cairo_select_font_face(cr, "sans", |
| CAIRO_FONT_SLANT_NORMAL, |
| CAIRO_FONT_WEIGHT_BOLD); |
| cairo_set_font_size(cr, 14); |
| cairo_text_extents(cr, title, &extents); |
| cairo_font_extents (cr, &font_extents); |
| text_width = extents.width; |
| text_height = font_extents.descent - font_extents.ascent; |
| #endif |
| |
| x = (width - text_width) / 2; |
| y = margin + (t->titlebar_height - text_height) / 2; |
| if (x < title_rect->x) |
| x = title_rect->x; |
| else if (x + text_width > (title_rect->x + title_rect->width)) |
| x = (title_rect->x + title_rect->width) - text_width; |
| |
| if (flags & THEME_FRAME_ACTIVE) { |
| cairo_move_to(cr, x + 1, y + 1); |
| cairo_set_source_rgb(cr, 1, 1, 1); |
| SHOW_TEXT(cr); |
| cairo_move_to(cr, x, y); |
| cairo_set_source_rgb(cr, 0, 0, 0); |
| SHOW_TEXT(cr); |
| } else { |
| cairo_move_to(cr, x, y); |
| cairo_set_source_rgb(cr, 0.4, 0.4, 0.4); |
| SHOW_TEXT(cr); |
| } |
| } |
| } |
| |
| enum theme_location |
| theme_get_location(struct theme *t, int x, int y, |
| int width, int height, int flags) |
| { |
| int vlocation, hlocation, location; |
| int margin, top_margin, grip_size; |
| |
| if (flags & THEME_FRAME_MAXIMIZED) { |
| margin = 0; |
| grip_size = 0; |
| } else { |
| margin = t->margin; |
| grip_size = 8; |
| } |
| |
| if (flags & THEME_FRAME_NO_TITLE) |
| top_margin = t->width; |
| else |
| top_margin = t->titlebar_height; |
| |
| if (x < margin) |
| hlocation = THEME_LOCATION_EXTERIOR; |
| else if (x < margin + grip_size) |
| hlocation = THEME_LOCATION_RESIZING_LEFT; |
| else if (x < width - margin - grip_size) |
| hlocation = THEME_LOCATION_INTERIOR; |
| else if (x < width - margin) |
| hlocation = THEME_LOCATION_RESIZING_RIGHT; |
| else |
| hlocation = THEME_LOCATION_EXTERIOR; |
| |
| if (y < margin) |
| vlocation = THEME_LOCATION_EXTERIOR; |
| else if (y < margin + grip_size) |
| vlocation = THEME_LOCATION_RESIZING_TOP; |
| else if (y < height - margin - grip_size) |
| vlocation = THEME_LOCATION_INTERIOR; |
| else if (y < height - margin) |
| vlocation = THEME_LOCATION_RESIZING_BOTTOM; |
| else |
| vlocation = THEME_LOCATION_EXTERIOR; |
| |
| location = vlocation | hlocation; |
| if (location & THEME_LOCATION_EXTERIOR) |
| location = THEME_LOCATION_EXTERIOR; |
| if (location == THEME_LOCATION_INTERIOR && |
| y < margin + top_margin) |
| location = THEME_LOCATION_TITLEBAR; |
| else if (location == THEME_LOCATION_INTERIOR) |
| location = THEME_LOCATION_CLIENT_AREA; |
| |
| return location; |
| } |