/* * Copyright (C) 2016 Alberts Muktupāvels * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "config.h" #include #include #include #include "meta-frame-layout-private.h" #include "meta-frame-style-private.h" #include "meta-theme-gtk-private.h" #include "meta-theme.h" struct _MetaThemeGtk { MetaThemeImpl parent; }; G_DEFINE_TYPE (MetaThemeGtk, meta_theme_gtk, META_TYPE_THEME_IMPL) static gboolean meta_theme_gtk_load (MetaThemeImpl *impl, const gchar *name, GError **error) { g_return_val_if_fail (error == NULL || *error == NULL, FALSE); return TRUE; } static void get_margin (GtkStyleContext *style, GtkBorder *border) { GtkStateFlags state; state = gtk_style_context_get_state (style); gtk_style_context_get_margin (style, state, border); } static void get_padding_and_border (GtkStyleContext *style, GtkBorder *border) { GtkBorder tmp; GtkStateFlags state = gtk_style_context_get_state (style); gtk_style_context_get_border (style, state, border); gtk_style_context_get_padding (style, state, &tmp); border->left += tmp.left; border->top += tmp.top; border->right += tmp.right; border->bottom += tmp.bottom; } static void get_min_size (GtkStyleContext *style, GtkRequisition *requisition) { gtk_style_context_get (style, gtk_style_context_get_state (style), "min-width", &requisition->width, "min-height", &requisition->height, NULL); } static void get_shadow_extents (GtkStyleContext *style, GtkBorder *border) { GdkRectangle clip; gtk_render_background_get_clip (style, 0, 0, 0, 0, &clip); border->left = abs (clip.x); border->top = abs (clip.y); border->right = clip.width - border->left; border->bottom = clip.height - border->top; } static void frame_layout_sync_with_style (MetaFrameLayout *layout, MetaStyleInfo *style_info, gboolean composited, MetaFrameFlags flags) { GtkStyleContext *style; GtkBorder border; GtkRequisition requisition; int border_radius, max_radius; meta_style_info_set_flags (style_info, flags); style = meta_style_info_get_style (style_info, META_STYLE_ELEMENT_DECORATION); get_padding_and_border (style, &layout->gtk.frame_border); scale_border (&layout->gtk.frame_border, layout->title_scale); if (composited) { /* With compositing manager: margin is resize area */ get_margin (style, &layout->invisible_resize_border); get_shadow_extents (style, &layout->gtk.shadow_border); gtk_style_context_get (style, gtk_style_context_get_state (style), "border-radius", &border_radius, NULL); /* GTK+ currently does not allow us to look up radii of individual * corners; however we don't clip the client area, so with the * current trend of using small/no visible frame borders, most * themes should work fine with this. */ layout->top_left_corner_rounded_radius = border_radius; layout->top_right_corner_rounded_radius = border_radius; max_radius = MIN (layout->gtk.frame_border.bottom, layout->gtk.frame_border.left); layout->bottom_left_corner_rounded_radius = MAX (border_radius, max_radius); max_radius = MIN (layout->gtk.frame_border.bottom, layout->gtk.frame_border.left); layout->bottom_right_corner_rounded_radius = MAX (border_radius, max_radius); } else { /* Without compositing manager we can not have invisible border */ layout->invisible_resize_border.top = 0; layout->invisible_resize_border.bottom = 0; layout->invisible_resize_border.left = 0; layout->invisible_resize_border.right = 0; layout->gtk.shadow_border.top = 0; layout->gtk.shadow_border.bottom = 0; layout->gtk.shadow_border.left = 0; layout->gtk.shadow_border.right = 0; layout->top_left_corner_rounded_radius = 0; layout->top_right_corner_rounded_radius = 0; layout->bottom_left_corner_rounded_radius = 0; layout->bottom_right_corner_rounded_radius = 0; /* Without compositing manager: margin is part of border */ get_margin (style, &border); layout->gtk.frame_border.left += border.left; layout->gtk.frame_border.right += border.right; layout->gtk.frame_border.top += border.top; layout->gtk.frame_border.bottom += border.bottom; } if (layout->hide_buttons) layout->gtk.icon_size = 0; if (!layout->has_title && layout->hide_buttons) return; /* border-only - be done */ style = meta_style_info_get_style (style_info, META_STYLE_ELEMENT_TITLEBAR); get_min_size (style, &layout->gtk.titlebar_min_size); get_padding_and_border (style, &layout->gtk.titlebar_border); scale_border (&layout->gtk.titlebar_border, layout->title_scale); style = meta_style_info_get_style (style_info, META_STYLE_ELEMENT_TITLE); get_margin (style, &layout->gtk.title_margin); scale_border (&layout->gtk.title_margin, layout->title_scale); style = meta_style_info_get_style (style_info, META_STYLE_ELEMENT_BUTTON); get_min_size (style, &layout->gtk.button_min_size); get_padding_and_border (style, &layout->button_border); scale_border (&layout->button_border, layout->title_scale); get_margin (style, &layout->gtk.button_margin); scale_border (&layout->gtk.button_margin, layout->title_scale); style = meta_style_info_get_style (style_info, META_STYLE_ELEMENT_IMAGE); get_min_size (style, &requisition); get_padding_and_border (style, &border); scale_border (&border, layout->title_scale); layout->button_border.left += border.left; layout->button_border.right += border.right; layout->button_border.top += border.top; layout->button_border.bottom += border.bottom; get_margin (style, &border); layout->button_border.left += border.left; layout->button_border.right += border.right; layout->button_border.top += border.top; layout->button_border.bottom += border.bottom; layout->gtk.button_min_size.width = MAX (layout->gtk.button_min_size.width, requisition.width); layout->gtk.button_min_size.height = MAX (layout->gtk.button_min_size.height, requisition.height); } static void meta_theme_gtk_get_frame_borders (MetaThemeImpl *impl, MetaFrameLayout *layout, MetaStyleInfo *style_info, gint text_height, MetaFrameFlags flags, MetaFrameType type, MetaFrameBorders *borders) { gboolean composited; gint scale; gint title_height; gint buttons_height; gint content_height; composited = meta_theme_impl_get_composited (impl); frame_layout_sync_with_style (layout, style_info, composited, flags); meta_frame_borders_clear (borders); /* For a full-screen window, we don't have any borders, visible or not. */ if (flags & META_FRAME_FULLSCREEN) return; g_return_if_fail (layout != NULL); if (!layout->has_title) text_height = 0; /* Scale geometry for HiDPI, see comment in meta_theme_gtk_draw_frame () */ scale = get_window_scaling_factor (); title_height = layout->gtk.title_margin.top + text_height / scale + layout->gtk.title_margin.bottom; buttons_height = MAX ((gint) layout->gtk.icon_size, layout->gtk.button_min_size.height) + layout->gtk.button_margin.top + layout->button_border.top + layout->gtk.button_margin.bottom + layout->button_border.bottom; content_height = MAX (title_height, buttons_height); content_height = MAX (content_height, layout->gtk.titlebar_min_size.height) + layout->gtk.titlebar_border.top + layout->gtk.titlebar_border.bottom; borders->visible.top = layout->gtk.frame_border.top + content_height; borders->visible.left = layout->gtk.frame_border.left; borders->visible.right = layout->gtk.frame_border.right; borders->visible.bottom = layout->gtk.frame_border.bottom; borders->shadow = layout->gtk.shadow_border; if (flags & META_FRAME_ALLOWS_HORIZONTAL_RESIZE) { borders->resize.left = layout->invisible_resize_border.left; borders->resize.right = layout->invisible_resize_border.right; } if (flags & META_FRAME_ALLOWS_VERTICAL_RESIZE) { borders->resize.bottom = layout->invisible_resize_border.bottom; if (type != META_FRAME_TYPE_ATTACHED) borders->resize.top = layout->invisible_resize_border.top; } borders->invisible.left = MAX (borders->shadow.left, borders->resize.left); borders->invisible.right = MAX (borders->shadow.right, borders->resize.right); borders->invisible.bottom = MAX (borders->shadow.bottom, borders->resize.bottom); borders->invisible.top = MAX (borders->shadow.top, borders->resize.top); borders->total.left = borders->invisible.left + borders->visible.left; borders->total.right = borders->invisible.right + borders->visible.right; borders->total.bottom = borders->invisible.bottom + borders->visible.bottom; borders->total.top = borders->invisible.top + borders->visible.top; scale_border (&borders->visible, scale); scale_border (&borders->shadow, scale); scale_border (&borders->resize, scale); scale_border (&borders->invisible, scale); scale_border (&borders->total, scale); } static MetaButtonSpace * rect_for_function (MetaFrameGeometry *fgeom, MetaFrameFlags flags, MetaButtonFunction function) { switch (function) { case META_BUTTON_FUNCTION_MENU: if (flags & META_FRAME_ALLOWS_MENU) return &fgeom->menu_rect; else return NULL; case META_BUTTON_FUNCTION_APPMENU: if (flags & META_FRAME_ALLOWS_APPMENU) return &fgeom->appmenu_rect; else return NULL; case META_BUTTON_FUNCTION_MINIMIZE: if (flags & META_FRAME_ALLOWS_MINIMIZE) return &fgeom->min_rect; else return NULL; case META_BUTTON_FUNCTION_MAXIMIZE: if (flags & META_FRAME_ALLOWS_MAXIMIZE) return &fgeom->max_rect; else return NULL; case META_BUTTON_FUNCTION_CLOSE: if (flags & META_FRAME_ALLOWS_DELETE) return &fgeom->close_rect; else return NULL; case META_BUTTON_FUNCTION_SHADE: if ((flags & META_FRAME_ALLOWS_SHADE) && !(flags & META_FRAME_SHADED)) return &fgeom->shade_rect; else return NULL; case META_BUTTON_FUNCTION_ABOVE: if (!(flags & META_FRAME_ABOVE)) return &fgeom->above_rect; else return NULL; case META_BUTTON_FUNCTION_STICK: if (!(flags & META_FRAME_STUCK)) return &fgeom->stick_rect; else return NULL; case META_BUTTON_FUNCTION_UNSHADE: if ((flags & META_FRAME_ALLOWS_SHADE) && (flags & META_FRAME_SHADED)) return &fgeom->unshade_rect; else return NULL; case META_BUTTON_FUNCTION_UNABOVE: if (flags & META_FRAME_ABOVE) return &fgeom->unabove_rect; else return NULL; case META_BUTTON_FUNCTION_UNSTICK: if (flags & META_FRAME_STUCK) return &fgeom->unstick_rect; else return NULL; case META_BUTTON_FUNCTION_LAST: default: break; } return NULL; } static gboolean strip_button (MetaButtonSpace *func_rects[META_BUTTON_FUNCTION_LAST], int *n_rects, MetaButtonSpace *to_strip) { int i; i = 0; while (i < *n_rects) { if (func_rects[i] == to_strip) { *n_rects -= 1; /* shift the other rects back in the array */ while (i < *n_rects) { func_rects[i] = func_rects[i+1]; ++i; } func_rects[i] = NULL; return TRUE; } ++i; } return FALSE; /* did not strip anything */ } static void meta_theme_gtk_calc_geometry (MetaThemeImpl *impl, MetaFrameLayout *layout, MetaStyleInfo *style_info, gint text_height, MetaFrameFlags flags, gint client_width, gint client_height, const MetaButtonLayout *button_layout, MetaFrameType type, MetaFrameGeometry *fgeom) { MetaFrameBorders borders; int i, n_left, n_right, n_left_spacers, n_right_spacers; int x; int button_y; int title_right_edge; int width, height; int scale; int content_width, content_height; int button_width, button_height; int min_size_for_rounding; /* the left/right rects in order; the max # of rects * is the number of button functions */ MetaButtonSpace *left_func_rects[META_BUTTON_FUNCTION_LAST]; MetaButtonSpace *right_func_rects[META_BUTTON_FUNCTION_LAST]; gboolean left_buttons_has_spacer[META_BUTTON_FUNCTION_LAST]; gboolean right_buttons_has_spacer[META_BUTTON_FUNCTION_LAST]; META_THEME_IMPL_GET_CLASS (impl)->get_frame_borders (impl, layout, style_info, text_height, flags, type, &borders); fgeom->borders = borders; width = client_width + borders.total.left + borders.total.right; height = borders.total.top + borders.total.bottom; if (!(flags & META_FRAME_SHADED)) height += client_height; fgeom->width = width; fgeom->height = height; /* Scale geometry for HiDPI, see comment in meta_theme_gtk_draw_frame () */ scale = get_window_scaling_factor (); content_width = width - borders.invisible.left - layout->gtk.frame_border.left * scale - borders.invisible.right - layout->gtk.frame_border.right * scale; content_height = borders.visible.top - layout->gtk.frame_border.top * scale; button_width = MAX ((gint) layout->gtk.icon_size, layout->gtk.button_min_size.width) + layout->button_border.left + layout->button_border.right; button_height = MAX ((gint) layout->gtk.icon_size, layout->gtk.button_min_size.height) + layout->button_border.top + layout->button_border.bottom; button_width *= scale; button_height *= scale; /* FIXME all this code sort of pretends that duplicate buttons * with the same function are allowed, but that breaks the * code in frames.c, so isn't really allowed right now. * Would need left_close_rect, right_close_rect, etc. */ /* Init all button rects to 0, lame hack */ memset (ADDRESS_OF_BUTTON_RECTS (fgeom), '\0', LENGTH_OF_BUTTON_RECTS); n_left = 0; n_right = 0; n_left_spacers = 0; n_right_spacers = 0; if (!layout->hide_buttons) { /* Try to fill in rects */ for (i = 0; i < META_BUTTON_FUNCTION_LAST && button_layout->left_buttons[i] != META_BUTTON_FUNCTION_LAST; i++) { left_func_rects[n_left] = rect_for_function (fgeom, flags, button_layout->left_buttons[i]); if (left_func_rects[n_left] != NULL) { left_buttons_has_spacer[n_left] = button_layout->left_buttons_has_spacer[i]; if (button_layout->left_buttons_has_spacer[i]) ++n_left_spacers; ++n_left; } } for (i = 0; i < META_BUTTON_FUNCTION_LAST && button_layout->right_buttons[i] != META_BUTTON_FUNCTION_LAST; i++) { right_func_rects[n_right] = rect_for_function (fgeom, flags, button_layout->right_buttons[i]); if (right_func_rects[n_right] != NULL) { right_buttons_has_spacer[n_right] = button_layout->right_buttons_has_spacer[i]; if (button_layout->right_buttons_has_spacer[i]) ++n_right_spacers; ++n_right; } } } /* Be sure buttons fit */ while (n_left > 0 || n_right > 0) { int space_used_by_buttons; space_used_by_buttons = 0; space_used_by_buttons += layout->gtk.button_margin.left * scale * n_left; space_used_by_buttons += button_width * n_left; space_used_by_buttons += layout->gtk.button_margin.right * scale * n_left; space_used_by_buttons += (button_width * 0.75) * n_left_spacers; space_used_by_buttons += layout->gtk.titlebar_spacing * scale * MAX (n_left - 1, 0); space_used_by_buttons += layout->gtk.button_margin.left * scale * n_right; space_used_by_buttons += button_width * n_right; space_used_by_buttons += layout->gtk.button_margin.right * scale * n_right; space_used_by_buttons += (button_width * 0.75) * n_right_spacers; space_used_by_buttons += layout->gtk.titlebar_spacing * scale * MAX (n_right - 1, 0); if (space_used_by_buttons <= content_width) break; /* Everything fits, bail out */ /* First try to remove separators */ if (n_left_spacers > 0) { left_buttons_has_spacer[--n_left_spacers] = FALSE; continue; } else if (n_right_spacers > 0) { right_buttons_has_spacer[--n_right_spacers] = FALSE; continue; } /* Otherwise we need to shave out a button. Shave * above, stick, shade, min, max, close, then menu (menu is most useful); * prefer the default button locations. */ if (strip_button (left_func_rects, &n_left, &fgeom->above_rect)) continue; else if (strip_button (right_func_rects, &n_right, &fgeom->above_rect)) continue; else if (strip_button (left_func_rects, &n_left, &fgeom->stick_rect)) continue; else if (strip_button (right_func_rects, &n_right, &fgeom->stick_rect)) continue; else if (strip_button (left_func_rects, &n_left, &fgeom->shade_rect)) continue; else if (strip_button (right_func_rects, &n_right, &fgeom->shade_rect)) continue; else if (strip_button (left_func_rects, &n_left, &fgeom->min_rect)) continue; else if (strip_button (right_func_rects, &n_right, &fgeom->min_rect)) continue; else if (strip_button (left_func_rects, &n_left, &fgeom->max_rect)) continue; else if (strip_button (right_func_rects, &n_right, &fgeom->max_rect)) continue; else if (strip_button (left_func_rects, &n_left, &fgeom->close_rect)) continue; else if (strip_button (right_func_rects, &n_right, &fgeom->close_rect)) continue; else if (strip_button (right_func_rects, &n_right, &fgeom->menu_rect)) continue; else if (strip_button (left_func_rects, &n_left, &fgeom->menu_rect)) continue; else if (strip_button (right_func_rects, &n_right, &fgeom->appmenu_rect)) continue; else if (strip_button (left_func_rects, &n_left, &fgeom->appmenu_rect)) continue; else { g_error ("Could not find a button to strip. n_left = %d n_right = %d", n_left, n_right); } } /* Save the button layout */ fgeom->button_layout = *button_layout; fgeom->n_left_buttons = n_left; fgeom->n_right_buttons = n_right; /* center buttons vertically */ button_y = borders.invisible.top + layout->gtk.frame_border.top * scale + (content_height - button_height) / 2; /* right edge of farthest-right button */ x = width - borders.invisible.right - layout->gtk.frame_border.right * scale - layout->gtk.titlebar_border.right * scale; i = n_right - 1; while (i >= 0) { MetaButtonSpace *rect; if (x < 0) /* if we go negative, leave the buttons we don't get to as 0-width */ break; x -= layout->gtk.button_margin.right * scale; rect = right_func_rects[i]; rect->visible.x = x - button_width; if (right_buttons_has_spacer[i]) rect->visible.x -= (button_width * 0.75); rect->visible.y = button_y; rect->visible.width = button_width; rect->visible.height = button_height; if (flags & META_FRAME_MAXIMIZED || flags & META_FRAME_TILED_LEFT || flags & META_FRAME_TILED_RIGHT) { rect->clickable.x = rect->visible.x; rect->clickable.y = rect->visible.y; rect->clickable.width = button_width; rect->clickable.height = button_height; if (i == n_right - 1) rect->clickable.width += layout->gtk.frame_border.right * scale + layout->gtk.titlebar_border.right * scale; } else g_memmove (&(rect->clickable), &(rect->visible), sizeof(rect->clickable)); x = rect->visible.x - layout->gtk.button_margin.left * scale; if (i > 0) x -= layout->gtk.titlebar_spacing * scale; --i; } /* save right edge of titlebar for later use */ title_right_edge = x; /* Now x changes to be position from the left and we go through * the left-side buttons */ x = borders.invisible.left + layout->gtk.frame_border.left * scale + layout->gtk.titlebar_border.left * scale; for (i = 0; i < n_left; i++) { MetaButtonSpace *rect; x += layout->gtk.button_margin.left * scale; rect = left_func_rects[i]; rect->visible.x = x; rect->visible.y = button_y; rect->visible.width = button_width; rect->visible.height = button_height; if (flags & META_FRAME_MAXIMIZED) { rect->clickable.x = rect->visible.x; rect->clickable.y = rect->visible.y; rect->clickable.width = button_width; rect->clickable.height = button_height; } else g_memmove (&(rect->clickable), &(rect->visible), sizeof(rect->clickable)); x = rect->visible.x + rect->visible.width + layout->gtk.button_margin.right * scale; if (i < n_left - 1) x += layout->gtk.titlebar_spacing * scale; if (left_buttons_has_spacer[i]) x += (button_width * 0.75); } /* Center vertically in the available content area */ fgeom->title_rect.x = x; fgeom->title_rect.y = borders.invisible.top + layout->gtk.frame_border.top * scale + (content_height - text_height) / 2; fgeom->title_rect.width = title_right_edge - fgeom->title_rect.x; fgeom->title_rect.height = text_height; /* Nuke title if it won't fit */ if (fgeom->title_rect.width < 0 || fgeom->title_rect.height < 0) { fgeom->title_rect.width = 0; fgeom->title_rect.height = 0; } if (flags & META_FRAME_SHADED) min_size_for_rounding = 0; else min_size_for_rounding = 5 * scale; fgeom->top_left_corner_rounded_radius = 0; fgeom->top_right_corner_rounded_radius = 0; fgeom->bottom_left_corner_rounded_radius = 0; fgeom->bottom_right_corner_rounded_radius = 0; if (borders.visible.top + borders.visible.left >= min_size_for_rounding) fgeom->top_left_corner_rounded_radius = layout->top_left_corner_rounded_radius * scale; if (borders.visible.top + borders.visible.right >= min_size_for_rounding) fgeom->top_right_corner_rounded_radius = layout->top_right_corner_rounded_radius * scale; if (borders.visible.bottom + borders.visible.left >= min_size_for_rounding) fgeom->bottom_left_corner_rounded_radius = layout->bottom_left_corner_rounded_radius * scale; if (borders.visible.bottom + borders.visible.right >= min_size_for_rounding) fgeom->bottom_right_corner_rounded_radius = layout->bottom_right_corner_rounded_radius * scale; } static const char * get_class_from_button_type (MetaButtonType type) { if (type == META_BUTTON_TYPE_CLOSE) return "close"; else if (type == META_BUTTON_TYPE_MAXIMIZE) return "maximize"; else if (type == META_BUTTON_TYPE_MINIMIZE) return "minimize"; return NULL; } static void meta_theme_gtk_draw_frame (MetaThemeImpl *impl, MetaFrameStyle *style, MetaStyleInfo *style_info, cairo_t *cr, const MetaFrameGeometry *fgeom, PangoLayout *title_layout, MetaFrameFlags flags, MetaButtonState button_states[META_BUTTON_TYPE_LAST], GdkPixbuf *mini_icon, GdkPixbuf *icon) { int scale; GtkStyleContext *context; GtkStateFlags state; MetaButtonType button_type; GdkRectangle visible_rect; GdkRectangle titlebar_rect; GdkRectangle button_rect; const MetaFrameBorders *borders; /* We opt out of GTK+ HiDPI handling, so we have to do the scaling * ourselves; the nitty-gritty is a bit confusing, so here is an overview: * - the values in MetaFrameLayout are always as they appear in the theme, * i.e. unscaled * - calculated values (borders, MetaFrameGeometry) include the scale - as * the geometry is comprised of scaled decorations and the client size * which we must not scale, we don't have another option * - for drawing, we scale the canvas to have GTK+ render elements (borders, * radii, ...) at the correct scale - as a result, we have to "unscale" * the geometry again to not apply the scaling twice */ scale = get_window_scaling_factor (); cairo_scale (cr, scale, scale); borders = &fgeom->borders; visible_rect.x = borders->invisible.left / scale; visible_rect.y = borders->invisible.top / scale; visible_rect.width = (fgeom->width - borders->invisible.left - borders->invisible.right) / scale; visible_rect.height = (fgeom->height - borders->invisible.top - borders->invisible.bottom) / scale; meta_style_info_set_flags (style_info, flags); context = meta_style_info_get_style (style_info, META_STYLE_ELEMENT_DECORATION); gtk_render_background (context, cr, visible_rect.x, visible_rect.y, visible_rect.width, visible_rect.height); gtk_render_frame (context, cr, visible_rect.x, visible_rect.y, visible_rect.width, visible_rect.height); if (flags & META_FRAME_FULLSCREEN) return; titlebar_rect.x = visible_rect.x + borders->visible.left / scale; titlebar_rect.y = visible_rect.y + style->layout->gtk.frame_border.top; titlebar_rect.width = visible_rect.width - (borders->visible.left + borders->visible.right) / scale; titlebar_rect.height = (borders->visible.top / scale) - style->layout->gtk.frame_border.top; context = meta_style_info_get_style (style_info, META_STYLE_ELEMENT_TITLEBAR); gtk_render_background (context, cr, titlebar_rect.x, titlebar_rect.y, titlebar_rect.width, titlebar_rect.height); gtk_render_frame (context, cr, titlebar_rect.x, titlebar_rect.y, titlebar_rect.width, titlebar_rect.height); if (style->layout->has_title && title_layout) { PangoRectangle logical; int text_width, x, y; pango_layout_set_width (title_layout, -1); pango_layout_get_pixel_extents (title_layout, NULL, &logical); text_width = MIN(fgeom->title_rect.width / scale, logical.width); if (text_width < logical.width) pango_layout_set_width (title_layout, PANGO_SCALE * text_width); /* Center within the frame if possible */ x = titlebar_rect.x + (titlebar_rect.width - text_width) / 2; y = titlebar_rect.y + (titlebar_rect.height - logical.height) / 2; if (x < fgeom->title_rect.x / scale) x = fgeom->title_rect.x / scale; else if (x + text_width > (fgeom->title_rect.x + fgeom->title_rect.width) / scale) x = (fgeom->title_rect.x + fgeom->title_rect.width) / scale - text_width; context = meta_style_info_get_style (style_info, META_STYLE_ELEMENT_TITLE); gtk_render_layout (context, cr, x, y, title_layout); } context = meta_style_info_get_style (style_info, META_STYLE_ELEMENT_BUTTON); state = gtk_style_context_get_state (context); for (button_type = META_BUTTON_TYPE_CLOSE; button_type < META_BUTTON_TYPE_LAST; button_type++) { MetaButtonState button_state; const char *button_class; button_class = get_class_from_button_type (button_type); if (button_class) gtk_style_context_add_class (context, button_class); get_button_rect (button_type, fgeom, 0, &button_rect); button_rect.x /= scale; button_rect.y /= scale; button_rect.width /= scale; button_rect.height /= scale; button_state = map_button_state (button_type, fgeom, 0, button_states); if (button_state == META_BUTTON_STATE_PRELIGHT) gtk_style_context_set_state (context, state | GTK_STATE_PRELIGHT); else if (button_state == META_BUTTON_STATE_PRESSED) gtk_style_context_set_state (context, state | GTK_STATE_ACTIVE); else gtk_style_context_set_state (context, state); cairo_save (cr); if (button_rect.width > 0 && button_rect.height > 0) { GdkPixbuf *pixbuf = NULL; const char *icon_name = NULL; gtk_render_background (context, cr, button_rect.x, button_rect.y, button_rect.width, button_rect.height); gtk_render_frame (context, cr, button_rect.x, button_rect.y, button_rect.width, button_rect.height); switch (button_type) { case META_BUTTON_TYPE_CLOSE: icon_name = "window-close-symbolic"; break; case META_BUTTON_TYPE_MAXIMIZE: if (flags & META_FRAME_MAXIMIZED) icon_name = "window-restore-symbolic"; else icon_name = "window-maximize-symbolic"; break; case META_BUTTON_TYPE_MINIMIZE: icon_name = "window-minimize-symbolic"; break; case META_BUTTON_TYPE_MENU: icon_name = "open-menu-symbolic"; break; case META_BUTTON_TYPE_APPMENU: pixbuf = g_object_ref (mini_icon); break; case META_BUTTON_TYPE_LEFT_LEFT_BACKGROUND: case META_BUTTON_TYPE_LEFT_MIDDLE_BACKGROUND: case META_BUTTON_TYPE_LEFT_RIGHT_BACKGROUND: case META_BUTTON_TYPE_LEFT_SINGLE_BACKGROUND: case META_BUTTON_TYPE_RIGHT_LEFT_BACKGROUND: case META_BUTTON_TYPE_RIGHT_MIDDLE_BACKGROUND: case META_BUTTON_TYPE_RIGHT_RIGHT_BACKGROUND: case META_BUTTON_TYPE_RIGHT_SINGLE_BACKGROUND: case META_BUTTON_TYPE_SHADE: case META_BUTTON_TYPE_ABOVE: case META_BUTTON_TYPE_STICK: case META_BUTTON_TYPE_UNSHADE: case META_BUTTON_TYPE_UNABOVE: case META_BUTTON_TYPE_UNSTICK: case META_BUTTON_TYPE_LAST: default: icon_name = NULL; break; } if (icon_name) { GtkIconTheme *theme = gtk_icon_theme_get_default (); GtkIconInfo *info; info = gtk_icon_theme_lookup_icon_for_scale (theme, icon_name, style->layout->gtk.icon_size, scale, 0); pixbuf = gtk_icon_info_load_symbolic_for_context (info, context, NULL, NULL); } if (pixbuf) { float width, height; int x, y; width = gdk_pixbuf_get_width (pixbuf) / scale; height = gdk_pixbuf_get_height (pixbuf) / scale; x = button_rect.x + (button_rect.width - width) / 2; y = button_rect.y + (button_rect.height - height) / 2; cairo_translate (cr, x, y); cairo_scale (cr, width / style->layout->gtk.icon_size / scale, height / style->layout->gtk.icon_size / scale); gdk_cairo_set_source_pixbuf (cr, pixbuf, 0, 0); cairo_paint (cr); g_object_unref (pixbuf); } } cairo_restore (cr); if (button_class) gtk_style_context_remove_class (context, button_class); } } static void meta_theme_gtk_class_init (MetaThemeGtkClass *gtk_class) { MetaThemeImplClass *impl_class; impl_class = META_THEME_IMPL_CLASS (gtk_class); impl_class->load = meta_theme_gtk_load; impl_class->get_frame_borders = meta_theme_gtk_get_frame_borders; impl_class->calc_geometry = meta_theme_gtk_calc_geometry; impl_class->draw_frame = meta_theme_gtk_draw_frame; } static void meta_theme_gtk_init (MetaThemeGtk *gtk) { MetaThemeImpl *impl; MetaFrameType type; impl = META_THEME_IMPL (gtk); for (type = 0; type < META_FRAME_TYPE_LAST; type++) { MetaFrameStyleSet *style_set; MetaFrameStyle *style; gint i; gint j; style_set = meta_frame_style_set_new (NULL); style = meta_frame_style_new (NULL); style->layout = meta_frame_layout_new (); switch (type) { case META_FRAME_TYPE_NORMAL: case META_FRAME_TYPE_DIALOG: case META_FRAME_TYPE_MODAL_DIALOG: case META_FRAME_TYPE_ATTACHED: break; case META_FRAME_TYPE_MENU: case META_FRAME_TYPE_UTILITY: style->layout->title_scale = PANGO_SCALE_SMALL; break; case META_FRAME_TYPE_BORDER: style->layout->has_title = FALSE; style->layout->hide_buttons = TRUE; break; case META_FRAME_TYPE_LAST: default: g_assert_not_reached (); } for (i = 0; i < META_FRAME_FOCUS_LAST; i++) { for (j = 0; j < META_FRAME_RESIZE_LAST; j++) { meta_frame_style_ref (style); style_set->normal_styles[j][i] = style; meta_frame_style_ref (style); style_set->shaded_styles[j][i] = style; } meta_frame_style_ref (style); style_set->maximized_styles[i] = style; meta_frame_style_ref (style); style_set->tiled_left_styles[i] = style; meta_frame_style_ref (style); style_set->tiled_right_styles[i] = style; meta_frame_style_ref (style); style_set->maximized_and_shaded_styles[i] = style; meta_frame_style_ref (style); style_set->tiled_left_and_shaded_styles[i] = style; meta_frame_style_ref (style); style_set->tiled_right_and_shaded_styles[i] = style; } meta_frame_style_unref (style); meta_theme_impl_add_style_set (impl, type, style_set); } }