blob: dfefe39eae972acdfaa40e418f73ebc030c86fb8 [file] [log] [blame]
// Copyright (c) 2009 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <algorithm>
#include <cairo.h>
#include <math.h>
#include <gmodule.h>
#include <gtk/gtk.h>
#include <string.h>
#include "theme/scoped_path.h"
#include "theme/scoped_pattern.h"
#include "theme/scoped_surface.h"
#include "theme/theme_draw.h"
using theme::ScopedPath;
using theme::ScopedPattern;
using theme::ScopedSurface;
// Common colors first:
// Border color used for many widgets.
static const double BASE_STROKE_R = static_cast<double>(0x8F) / 255.0;
static const double BASE_STROKE_G = static_cast<double>(0x8F) / 255.0;
static const double BASE_STROKE_B = static_cast<double>(0x8F) / 255.0;
// Disabled border color used for many widgets.
static const double DISABLED_BASE_STROKE_R = static_cast<double>(0xB7) / 255.0;
static const double DISABLED_BASE_STROKE_G = static_cast<double>(0xB7) / 255.0;
static const double DISABLED_BASE_STROKE_B = static_cast<double>(0xB7) / 255.0;
static const double FOCUSED_STROKE_R = static_cast<double>(0x50) / 255.0;
static const double FOCUSED_STROKE_G = static_cast<double>(0x7a) / 255.0;
static const double FOCUSED_STROKE_B = static_cast<double>(0xD5) / 255.0;
// Common gradient stop and colors.
static const double GRADIENT_STOP_0 = 0;
static const double GRADIENT_STOP_1 = .5;
static const double GRADIENT_STOP_2 = 1.0;
static const double GRADIENT_R0 = 1.0;
static const double GRADIENT_G0 = 1.0;
static const double GRADIENT_B0 = 1.0;
static const double GRADIENT_R1 = 1.0;
static const double GRADIENT_G1 = 1.0;
static const double GRADIENT_B1 = 1.0;
static const double GRADIENT_R2 = static_cast<double>(0xD8) / 255.0;
static const double GRADIENT_G2 = static_cast<double>(0xD8) / 255.0;
static const double GRADIENT_B2 = static_cast<double>(0xD8) / 255.0;
static const double PRESSED_GRADIENT_R0 = static_cast<double>(0x95) / 255.0;
static const double PRESSED_GRADIENT_G0 = static_cast<double>(0x95) / 255.0;
static const double PRESSED_GRADIENT_B0 = static_cast<double>(0x95) / 255.0;
static const double PRESSED_GRADIENT_R1 = static_cast<double>(0xE3) / 255.0;
static const double PRESSED_GRADIENT_G1 = static_cast<double>(0xE3) / 255.0;
static const double PRESSED_GRADIENT_B1 = static_cast<double>(0xE3) / 255.0;
// Color used for selected text and a couple of other things.
static const double SELECTED_TEXT_BG_R = static_cast<double>(0xDC) / 255.0;
static const double SELECTED_TEXT_BG_G = static_cast<double>(0xE4) / 255.0;
static const double SELECTED_TEXT_BG_B = static_cast<double>(0xFA) / 255.0;
// Radius of the rounded rects drawn.
static const int BORDER_CORNER_RADIUS = 3;
// Stroke width when focused.
static const int FOCUSED_STROKE_WIDTH = 2;
// Stroke width when not focused.
static const int STROKE_WIDTH = 1;
// Then per widget colors/settings.
static const int COMBOBOX_IDEAL_ARROW_SIZE = 7;
static const double H_SEPARATOR_R0 = static_cast<double>(0xDA) / 255.0;
static const double H_SEPARATOR_G0 = static_cast<double>(0xDA) / 255.0;
static const double H_SEPARATOR_B0 = static_cast<double>(0xDA) / 255.0;
static const double H_SEPARATOR_R1 = static_cast<double>(0xF8) / 255.0;
static const double H_SEPARATOR_G1 = static_cast<double>(0xF8) / 255.0;
static const double H_SEPARATOR_B1 = static_cast<double>(0xF8) / 255.0;
static const double H_SLIDER_TRACK_R = static_cast<double>(0xDF) / 255.0;
static const double H_SLIDER_TRACK_G = static_cast<double>(0xDF) / 255.0;
static const double H_SLIDER_TRACK_B = static_cast<double>(0xDF) / 255.0;
static const double H_SLIDER_TRACK_FILL_R = 1;
static const double H_SLIDER_TRACK_FILL_G = 1;
static const double H_SLIDER_TRACK_FILL_B = 1;
static const double H_SLIDER_TRACK_HEIGHT = 6;
static const double INDICATOR_STROKE_DISABLED_R =
static_cast<double>(0xB4) / 255.0;
static const double INDICATOR_STROKE_DISABLED_G =
static_cast<double>(0xB4) / 255.0;
static const double INDICATOR_STROKE_DISABLED_B =
static_cast<double>(0xB4) / 255.0;
// TODO: these are wrong, what should they be?
static const double INDICATOR_STROKE_PRESSED_R =
static_cast<double>(0x0) / 255.0;
static const double INDICATOR_STROKE_PRESSED_G =
static_cast<double>(0x0) / 255.0;
static const double INDICATOR_STROKE_PRESSED_B =
static_cast<double>(0x0) / 255.0;
static const double INDICATOR_STROKE_R = 0;
static const double INDICATOR_STROKE_G = 0;
static const double INDICATOR_STROKE_B = 0;
static const double MENU_BG_R = 1;
static const double MENU_BG_G = 1;
static const double MENU_BG_B = 1;
static const double MENU_BG_HIGHLIGHT_R = SELECTED_TEXT_BG_R;
static const double MENU_BG_HIGHLIGHT_G = SELECTED_TEXT_BG_G;
static const double MENU_BG_HIGHLIGHT_B = SELECTED_TEXT_BG_B;
static const double MENU_GRADIENT_R0 = 1;
static const double MENU_GRADIENT_G0 = 1;
static const double MENU_GRADIENT_B0 = 1;
static const double MENU_GRADIENT_R1 = static_cast<double>(0xF0) / 255.0;
static const double MENU_GRADIENT_G1 = static_cast<double>(0xF0) / 255.0;
static const double MENU_GRADIENT_B1 = static_cast<double>(0xF0) / 255.0;
// Ideal arrow size for menus.
static const int MENU_IDEAL_ARROW_SIZE = 5;
// Ideal size of the inner circle for selected radio buttons.
static const int MENU_RADIO_BUTTON_INDICATOR_IDEAL_SIZE = 5;
// Ideal size of the inner circle for selected radio buttons.
static const int RADIO_BUTTON_INDICATOR_IDEAL_SIZE = 7;
static const double RADIO_BUTTON_INDICATOR_GRADIENT_DISABLED_R0 =
static_cast<double>(0xB4) / 255.0;
static const double RADIO_BUTTON_INDICATOR_GRADIENT_DISABLED_G0 =
static_cast<double>(0xB4) / 255.0;
static const double RADIO_BUTTON_INDICATOR_GRADIENT_DISABLED_B0 =
static_cast<double>(0xB4) / 255.0;
static const double RADIO_BUTTON_INDICATOR_GRADIENT_DISABLED_R1 =
static_cast<double>(0xB7) / 255.0;
static const double RADIO_BUTTON_INDICATOR_GRADIENT_DISABLED_G1 =
static_cast<double>(0xB7) / 255.0;
static const double RADIO_BUTTON_INDICATOR_GRADIENT_DISABLED_B1 =
static_cast<double>(0xB7) / 255.0;
// TODO: these are wrong, what should they be?
static const double RADIO_BUTTON_INDICATOR_GRADIENT_PRESSED_R0 =
static_cast<double>(0xFF) / 255.0;
static const double RADIO_BUTTON_INDICATOR_GRADIENT_PRESSED_G0 =
static_cast<double>(0xFF) / 255.0;
static const double RADIO_BUTTON_INDICATOR_GRADIENT_PRESSED_B0 =
static_cast<double>(0xFF) / 255.0;
static const double RADIO_BUTTON_INDICATOR_GRADIENT_PRESSED_R1 =
static_cast<double>(0xFF) / 255.0;
static const double RADIO_BUTTON_INDICATOR_GRADIENT_PRESSED_G1 =
static_cast<double>(0xFF) / 255.0;
static const double RADIO_BUTTON_INDICATOR_GRADIENT_PRESSED_B1 =
static_cast<double>(0xFF) / 255.0;
static const double RADIO_BUTTON_INDICATOR_GRADIENT_R0 = 0;
static const double RADIO_BUTTON_INDICATOR_GRADIENT_G0 = 0;
static const double RADIO_BUTTON_INDICATOR_GRADIENT_B0 = 0;
static const double RADIO_BUTTON_INDICATOR_GRADIENT_R1 =
static_cast<double>(0x83) / 255.0;
static const double RADIO_BUTTON_INDICATOR_GRADIENT_G1 =
static_cast<double>(0x83) / 255.0;
static const double RADIO_BUTTON_INDICATOR_GRADIENT_B1 =
static_cast<double>(0x83) / 255.0;
static const double SCROLLBAR_ARROW_BORDER_R = BASE_STROKE_R;
static const double SCROLLBAR_ARROW_BORDER_G = BASE_STROKE_G;
static const double SCROLLBAR_ARROW_BORDER_B = BASE_STROKE_B;
static const double SCROLLBAR_ARROW_FILL_R = 1;
static const double SCROLLBAR_ARROW_FILL_G = 1;
static const double SCROLLBAR_ARROW_FILL_B = 1;
static const double SCROLLBAR_BG_R = static_cast<double>(0xF0) / 255.0;
static const double SCROLLBAR_BG_G = static_cast<double>(0xF0) / 255.0;
static const double SCROLLBAR_BG_B = static_cast<double>(0xF0) / 255.0;
static const double SCROLLBAR_BORDER_R = BASE_STROKE_R;
static const double SCROLLBAR_BORDER_G = BASE_STROKE_G;
static const double SCROLLBAR_BORDER_B = BASE_STROKE_B;
static const int SCROLLBAR_IDEAL_ARROW_SIZE = 11;
static const double SCROLLBAR_THUMB_BG_R = 1;
static const double SCROLLBAR_THUMB_BG_G = 1;
static const double SCROLLBAR_THUMB_BG_B = 1;
static const double TAB_PANE_BORDER_R = BASE_STROKE_R;
static const double TAB_PANE_BORDER_G = BASE_STROKE_G;
static const double TAB_PANE_BORDER_B = BASE_STROKE_B;
static const double OPTIONS_TAB_R0 = static_cast<double>(0xFC) / 255.0;
static const double OPTIONS_TAB_G0 = static_cast<double>(0xFD) / 255.0;
static const double OPTIONS_TAB_B0 = static_cast<double>(0xFE) / 255.0;
static const double OPTIONS_TAB_R1 = static_cast<double>(0x8C) / 255.0;
static const double OPTIONS_TAB_G1 = static_cast<double>(0xA3) / 255.0;
static const double OPTIONS_TAB_B1 = static_cast<double>(0xDD) / 255.0;
static const double OPTIONS_TAB_BORDER_R = static_cast<double>(0xFF) / 255.0;
static const double OPTIONS_TAB_BORDER_G = static_cast<double>(0xFF) / 255.0;
static const double OPTIONS_TAB_BORDER_B = static_cast<double>(0xFF) / 255.0;
static const double TEXT_GRADIENT_R0 = static_cast<double>(0xC5) / 255.0;
static const double TEXT_GRADIENT_G0 = static_cast<double>(0xC5) / 255.0;
static const double TEXT_GRADIENT_B0 = static_cast<double>(0xC5) / 255.0;
static const double TEXT_GRADIENT_R1 = 1;
static const double TEXT_GRADIENT_G1 = 1;
static const double TEXT_GRADIENT_B1 = 1;
static const double TEXT_GRADIENT_R2 = 1;
static const double TEXT_GRADIENT_G2 = 1;
static const double TEXT_GRADIENT_B2 = 1;
static const double TEXT_GRADIENT_STOP_0 = 0;
static const double TEXT_GRADIENT_STOP_1_PIXEL = 4;
static const double TEXT_GRADIENT_STOP_2 = 1;
static const double TREE_ITEM_BG_R = 1;
static const double TREE_ITEM_BG_G = 1;
static const double TREE_ITEM_BG_B = 1;
static const double TREE_ITEM_SELECTED_BG_R = SELECTED_TEXT_BG_R;
static const double TREE_ITEM_SELECTED_BG_G = SELECTED_TEXT_BG_G;
static const double TREE_ITEM_SELECTED_BG_B = SELECTED_TEXT_BG_B;
// Tooltip border.
static const double TOOLTIP_R = 0;
static const double TOOLTIP_G = 0;
static const double TOOLTIP_B = 0;
static const double TOOLTIP_A = 0.5;
// Number of pixels for the shadow.
static const int TOOLTIP_SHADOW_DEPTH = 2;
// Tooltip shadow color.
static const double TOOLTIP_SHADOW_R = 0;
static const double TOOLTIP_SHADOW_G = 0;
static const double TOOLTIP_SHADOW_B = 0;
// Alpha for shadow pixels, from outer-most to inner-most.
static const double TOOLTIP_SHADOW_ALPHAS[] = { 0.1, 0.2 };
// Outermost shadow corner radius is 4-pixel tooltip border corner radius
// plus TOOLTIP_SHADOW_DEPTH.
static const int TOOLTIP_CORNER_RADIUS = 4 + TOOLTIP_SHADOW_DEPTH;
// Widget name of GtkCombBox popup menu.
static const char* kGtkComboBoxPopupMenu = "gtk-combobox-popup-menu";
// Widget name of ChromeOS options tab.
static const char* kChromeOsOptionsTabName = "chromeos-options-tab";
// Check whether given widget's name is the same as name_to_check.
static bool WidgetHasName(GtkWidget* widget, const char* name_to_check) {
const char* widget_name = gtk_widget_get_name(widget);
return widget_name && !strcmp(widget_name, name_to_check);
}
// Check whether given widget uses RGBA visuals.
static bool WidgetHasRGBAVisual(GtkWidget* widget) {
GdkVisual *visual = gtk_widget_get_visual(widget);
return visual &&
visual->depth == 32 &&
visual->red_mask == 0xFF0000 &&
visual->green_mask == 0x00FF00 &&
visual->blue_mask == 0x0000FF;
}
// NOTE: Cairo strokes with the pen between pixels. This results in a line width
// of 1 touching two pixels. To account for this you have to add .5 so that the
// pen ends up touching only one pixel. The pen is best though of as extending
// line_stroke_width / 2 in the opposite direction you are drawing and 0 pixels
// in the direction you are drawing. This means to draw a horizontal line with
// a width of 1 pixel you draw from (x, y + .5) to (x + 1, y + 5) and similary
// a vertical line of 1 pixel is drawn using (x + .5, y) to (x + .5, y + 1).
// See http://www.cairographics.org/FAQ/ for details.
// Strokes a rectangle a single pixel wide.
static void DrawSinglePixelWideRectangle(cairo_t* cr,
int x,
int y,
int w,
int h) {
cairo_set_line_width(cr, 1);
cairo_translate(cr, x, y);
cairo_move_to(cr, 0, .5);
cairo_line_to(cr, w, .5);
cairo_stroke(cr);
cairo_move_to(cr, 0, h - .5);
cairo_line_to(cr, w, h - .5);
cairo_stroke(cr);
cairo_move_to(cr, .5, 0);
cairo_line_to(cr, .5, h);
cairo_stroke(cr);
cairo_move_to(cr, w - .5, 0);
cairo_line_to(cr, w - .5, h);
cairo_stroke(cr);
cairo_translate(cr, -x, -y);
}
// All arrows are drawn down. To get the right directionality the context is
// first rotated by this many degrees.
static double GetRotationAngle(GtkArrowType arrow_type) {
switch (arrow_type) {
case GTK_ARROW_UP:
return M_PI;
case GTK_ARROW_LEFT:
return M_PI / 2;
case GTK_ARROW_RIGHT:
return M_PI * 3 / 2;
default:
return 0;
}
return 0;
}
// Draws a filled arrow.
static void DrawFilledArrow(cairo_t* cr,
GtkArrowType arrow_type,
int x,
int y,
int w,
int h,
int ideal_size) {
cairo_set_line_width(cr, 1);
int size = std::min(h - 1, std::min(ideal_size, w - 1));
if (size % 2 == 0)
size--; // Force the size to be odd.
int arrow_height = size / 2 + 1;
int center_x = x + w / 2;
int center_y = y + h / 2;
cairo_translate(cr, center_x, center_y);
cairo_rotate(cr, GetRotationAngle(arrow_type));
int i;
for (i = 0; i < arrow_height; ++i) {
cairo_move_to(cr, i - size / 2, i - arrow_height / 2 + .5);
cairo_line_to(cr, size / 2 - i + 1, i - arrow_height / 2 + .5);
cairo_stroke(cr);
}
cairo_rotate(cr, -GetRotationAngle(arrow_type));
cairo_translate(cr, -center_x, -center_y);
}
// Adds a rounded rect path to |cr| of the specified size. |stroke_width| gives
// the width of the stroking line and |arc_radius| the radius of the edges of
// the rectangle.
static void AddRoundedRectPath(cairo_t* cr, int w, int h, int stroke_width,
int arc_radius) {
double offset = (stroke_width % 2 == 1) ? .5 : 0;
int half_stroke_width = stroke_width / 2;
cairo_translate(cr, half_stroke_width, half_stroke_width);
w -= 2 * half_stroke_width;
h -= 2 * half_stroke_width;
cairo_move_to(cr, arc_radius, offset);
cairo_line_to(cr, w - arc_radius, offset);
cairo_arc(cr, w - arc_radius - offset, arc_radius + offset, arc_radius,
-M_PI / 2, 0);
cairo_line_to(cr, w - offset, h - arc_radius);
cairo_arc(cr, w - arc_radius - offset, h - arc_radius - offset,
arc_radius, 0, M_PI / 2);
cairo_line_to(cr, arc_radius, h - offset);
cairo_arc(cr, arc_radius + offset, h - arc_radius - offset, arc_radius,
M_PI / 2, M_PI);
cairo_line_to(cr, offset, arc_radius);
cairo_arc(cr, arc_radius + offset, arc_radius + offset, arc_radius, M_PI,
M_PI * 3 / 2);
cairo_translate(cr, -half_stroke_width, -half_stroke_width);
}
// Sets the source color of |cr| to the appropriate indicator color.
static void SetIndicatorStrokeColor(cairo_t* cr, GtkWidget* widget,
bool pressed) {
if (!GTK_WIDGET_SENSITIVE(widget)) {
cairo_set_source_rgb( cr, INDICATOR_STROKE_DISABLED_R,
INDICATOR_STROKE_DISABLED_G,
INDICATOR_STROKE_DISABLED_B);
} else if (pressed) {
cairo_set_source_rgb(cr, INDICATOR_STROKE_PRESSED_R,
INDICATOR_STROKE_DISABLED_G,
INDICATOR_STROKE_DISABLED_B);
} else {
cairo_set_source_rgb(cr, INDICATOR_STROKE_R,
INDICATOR_STROKE_G, INDICATOR_STROKE_B);
}
}
// Sets the color used for many widgets.
static void SetStrokeColor(cairo_t* cr, bool enabled, bool focused) {
if (!enabled) {
cairo_set_source_rgb(cr, DISABLED_BASE_STROKE_R,
DISABLED_BASE_STROKE_G, DISABLED_BASE_STROKE_B);
} if (focused) {
cairo_set_source_rgb(cr, FOCUSED_STROKE_R, FOCUSED_STROKE_G,
FOCUSED_STROKE_B);
} else {
cairo_set_source_rgb(cr, BASE_STROKE_R, BASE_STROKE_G,
BASE_STROKE_B);
}
}
static void DrawTextBorder(cairo_t* cr,
GtkWidget* widget,
int x,
int y,
int w,
int h) {
if (!GTK_WIDGET_HAS_FOCUS(widget)) {
x++;
y++;
w -= 2;
h -= 2;
}
cairo_translate(cr, x, y);
int stroke_width = GTK_WIDGET_HAS_FOCUS(widget)
? FOCUSED_STROKE_WIDTH : STROKE_WIDTH;
cairo_set_line_width(cr, stroke_width);
AddRoundedRectPath(cr, w, h, stroke_width, BORDER_CORNER_RADIUS);
double text_gradient_stop_1 = h ? TEXT_GRADIENT_STOP_1_PIXEL / h : 0;
ScopedPattern pattern(cairo_pattern_create_linear(0, 0, 0, h));
cairo_pattern_add_color_stop_rgb(pattern.get(), TEXT_GRADIENT_STOP_0,
TEXT_GRADIENT_R0, TEXT_GRADIENT_G0,
TEXT_GRADIENT_B0);
cairo_pattern_add_color_stop_rgb(pattern.get(), text_gradient_stop_1,
TEXT_GRADIENT_R1, TEXT_GRADIENT_G1,
TEXT_GRADIENT_B1);
cairo_pattern_add_color_stop_rgb(pattern.get(), TEXT_GRADIENT_STOP_2,
TEXT_GRADIENT_R2, TEXT_GRADIENT_G2,
TEXT_GRADIENT_B2);
cairo_set_source(cr, pattern.get());
cairo_fill_preserve(cr);
SetStrokeColor(cr, GTK_WIDGET_SENSITIVE(widget),
GTK_WIDGET_HAS_FOCUS(widget));
cairo_stroke(cr);
}
// Adds the gradient used for buttons to cr.
static void AddRoundRectGradient(cairo_t* cr,
ScopedPattern* pattern,
int h,
bool pressed) {
pattern->reset(cairo_pattern_create_linear(0, 0, 0, h));
// TODO: need disabled.
if (pressed) {
cairo_pattern_add_color_stop_rgb(pattern->get(), 0, PRESSED_GRADIENT_R0,
PRESSED_GRADIENT_G0, PRESSED_GRADIENT_B0);
cairo_pattern_add_color_stop_rgb(pattern->get(), 1, PRESSED_GRADIENT_R1,
PRESSED_GRADIENT_G1, PRESSED_GRADIENT_B1);
} else {
cairo_pattern_add_color_stop_rgb(pattern->get(), GRADIENT_STOP_0,
GRADIENT_R0, GRADIENT_G0, GRADIENT_B0);
cairo_pattern_add_color_stop_rgb(pattern->get(), GRADIENT_STOP_1,
GRADIENT_R1, GRADIENT_G1, GRADIENT_B1);
cairo_pattern_add_color_stop_rgb(pattern->get(), GRADIENT_STOP_2,
GRADIENT_R2, GRADIENT_G2, GRADIENT_B2);
}
cairo_set_source(cr, pattern->get());
}
// Draws a rounded rect and stroke.
static void DrawRoundRectBorderWithStroke(GdkWindow* window,
GdkRectangle* area,
int x,
int y,
int w,
int h,
bool enabled,
bool pressed,
bool focused,
bool inset) {
if (inset && !focused) {
// Inset the non-focused border slightly so that the focus border visually
// pops out.
x++;
y++;
w -= 2;
h -= 2;
}
ScopedSurface cr(window, area);
cairo_translate(cr.get(), x, y);
int stroke_width = focused ? FOCUSED_STROKE_WIDTH : STROKE_WIDTH;
cairo_set_line_width(cr.get(), stroke_width);
AddRoundedRectPath(cr.get(), w, h, stroke_width, BORDER_CORNER_RADIUS);
ScopedPattern pattern;
AddRoundRectGradient(cr.get(), &pattern, h, pressed);
cairo_fill_preserve(cr.get());
SetStrokeColor(cr.get(), enabled, focused);
cairo_stroke(cr.get());
}
// Draws a check.
static void DrawCheckMark(GtkWidget* widget, GdkWindow* window,
GdkRectangle* area, int x, int y, int w, int h,
bool pressed) {
ScopedSurface cr(window, area);
cairo_translate(cr.get(), x + (w - 8) / 2, y + h / 2);
SetIndicatorStrokeColor(cr.get(), widget, pressed);
cairo_move_to(cr.get(), 0, 0);
cairo_line_to(cr.get(), 3, 2);
cairo_line_to(cr.get(), 8, -4);
cairo_stroke(cr.get());
}
// Draws the indicator for a button button.
static void DrawRadioIndicator(GtkWidget* widget,
GdkWindow* window,
GdkRectangle* area,
int x,
int y,
int w,
int h,
bool selected,
bool pressed,
int ideal_selected_size) {
ScopedSurface cr(window, area);
// Inset the non-focused border.
int offset = GTK_WIDGET_HAS_FOCUS(widget) ? 0 : 1;
int indicator_size = std::min(w, h) - offset - offset;
cairo_translate(cr.get(), x + (w - indicator_size) / 2,
y + (h - indicator_size) / 2);
// Draw the outer circle first.
ScopedPattern pattern;
AddRoundRectGradient(cr.get(), &pattern, indicator_size, pressed);
cairo_arc(cr.get(), indicator_size / 2, indicator_size / 2,
indicator_size / 2, 0, M_PI * 2);
cairo_fill_preserve(cr.get());
cairo_set_line_width(cr.get(), GTK_WIDGET_HAS_FOCUS(widget) ?
FOCUSED_STROKE_WIDTH : STROKE_WIDTH);
if (GTK_WIDGET_HAS_FOCUS(widget)) {
cairo_set_source_rgb(cr.get(), FOCUSED_STROKE_R, FOCUSED_STROKE_G,
FOCUSED_STROKE_B);
} else {
cairo_set_source_rgb(cr.get(), BASE_STROKE_R, BASE_STROKE_G,
BASE_STROKE_B);
}
cairo_stroke(cr.get());
if (!selected)
return;
// Draw selected indicator.
int selected_indicator_size =
std::min(indicator_size - 2, ideal_selected_size);
pattern.reset(cairo_pattern_create_linear(0, 0, 0, h));
if (!GTK_WIDGET_SENSITIVE(widget)) {
cairo_pattern_add_color_stop_rgb(
pattern.get(), 0, RADIO_BUTTON_INDICATOR_GRADIENT_DISABLED_R0,
RADIO_BUTTON_INDICATOR_GRADIENT_DISABLED_G0,
RADIO_BUTTON_INDICATOR_GRADIENT_DISABLED_B0);
cairo_pattern_add_color_stop_rgb(
pattern.get(), 1, RADIO_BUTTON_INDICATOR_GRADIENT_DISABLED_R1,
RADIO_BUTTON_INDICATOR_GRADIENT_DISABLED_G1,
RADIO_BUTTON_INDICATOR_GRADIENT_DISABLED_B1);
} else if (pressed) {
cairo_pattern_add_color_stop_rgb(
pattern.get(), 0, RADIO_BUTTON_INDICATOR_GRADIENT_PRESSED_R0,
RADIO_BUTTON_INDICATOR_GRADIENT_PRESSED_G0,
RADIO_BUTTON_INDICATOR_GRADIENT_PRESSED_B0);
cairo_pattern_add_color_stop_rgb(
pattern.get(), 1, RADIO_BUTTON_INDICATOR_GRADIENT_PRESSED_R1,
RADIO_BUTTON_INDICATOR_GRADIENT_PRESSED_G1,
RADIO_BUTTON_INDICATOR_GRADIENT_PRESSED_B1);
} else {
cairo_pattern_add_color_stop_rgb(
pattern.get(), 0, RADIO_BUTTON_INDICATOR_GRADIENT_R0,
RADIO_BUTTON_INDICATOR_GRADIENT_G0,
RADIO_BUTTON_INDICATOR_GRADIENT_B0);
cairo_pattern_add_color_stop_rgb(
pattern.get(), 1, RADIO_BUTTON_INDICATOR_GRADIENT_R1,
RADIO_BUTTON_INDICATOR_GRADIENT_G1,
RADIO_BUTTON_INDICATOR_GRADIENT_B1);
}
cairo_set_source(cr.get(), pattern.get());
cairo_arc(cr.get(), indicator_size / 2, indicator_size / 2,
selected_indicator_size / 2, 0, M_PI * 2);
cairo_fill_preserve(cr.get());
cairo_set_line_width(cr.get(), 1);
SetIndicatorStrokeColor(cr.get(), widget, pressed);
cairo_stroke(cr.get());
}
// All the theme engine functions decode the params and call the appropriate
// function below.
static void DrawButtonBorder(GtkStyle* style,
GdkWindow* window,
GtkStateType state_type,
GtkShadowType shadow_type,
GdkRectangle* area,
GtkWidget* widget,
gint x,
gint y,
gint w,
gint h) {
DrawRoundRectBorderWithStroke(window, area, x, y, w, h,
GTK_WIDGET_SENSITIVE(widget),
GTK_BUTTON(widget)->depressed,
GTK_WIDGET_HAS_FOCUS(widget),
true);
}
static void DrawCheckboxCheck(GtkStyle* style,
GdkWindow* window,
GtkStateType state_type,
GtkShadowType shadow_type,
GdkRectangle* area,
GtkWidget* widget,
gint x,
gint y,
gint w,
gint h) {
DrawRoundRectBorderWithStroke(window, area, x, y, w, h,
GTK_WIDGET_SENSITIVE(widget),
(state_type == GTK_STATE_ACTIVE),
GTK_WIDGET_HAS_FOCUS(widget),
true);
if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget))) {
DrawCheckMark(widget, window, area, x, y, w, h,
(state_type == GTK_STATE_ACTIVE));
}
}
static void DrawComboboxArrow(GtkStyle* style,
GdkWindow* window,
GtkStateType state_type,
GtkShadowType shadow_type,
GdkRectangle* area,
GtkWidget* widget,
GtkArrowType arrow_type,
gboolean fill,
gint x,
gint y,
gint w,
gint h) {
ScopedSurface cr(window, area);
DrawFilledArrow(cr.get(), arrow_type, x, y, w, h, COMBOBOX_IDEAL_ARROW_SIZE);
}
static void DrawHorizontalSliderThumb(GtkStyle* style,
GdkWindow* window,
GtkStateType state_type,
GtkShadowType shadow_type,
GdkRectangle* area,
GtkWidget* widget,
gint x,
gint y,
gint w,
gint h,
GtkOrientation orientation) {
if (orientation == GTK_ORIENTATION_HORIZONTAL) {
y += 2;
h -= 4;
} else {
x += 2;
w -= 4;
}
DrawRoundRectBorderWithStroke(window, area, x, y, w, h,
GTK_WIDGET_SENSITIVE(widget), false, false,
false);
}
static void DrawHorizontalSliderTrack(GtkStyle* style,
GdkWindow* window,
GtkStateType state_type,
GtkShadowType shadow_type,
GdkRectangle* area,
GtkWidget* widget,
gint x,
gint y,
gint w,
gint h) {
ScopedSurface cr(window, area);
cairo_translate(cr.get(), x, y + (h - H_SLIDER_TRACK_HEIGHT) / 2);
AddRoundedRectPath(cr.get(), w, H_SLIDER_TRACK_HEIGHT, 1,
BORDER_CORNER_RADIUS);
cairo_set_source_rgb(cr.get(), H_SLIDER_TRACK_FILL_R, H_SLIDER_TRACK_FILL_G,
H_SLIDER_TRACK_FILL_G);
cairo_fill_preserve(cr.get());
cairo_set_source_rgb(cr.get(), H_SLIDER_TRACK_R, H_SLIDER_TRACK_G,
H_SLIDER_TRACK_B);
cairo_set_line_width(cr.get(), 1);
cairo_stroke(cr.get());
}
static void DrawMenuArrow(GtkStyle* style,
GdkWindow* window,
GtkStateType state_type,
GtkShadowType shadow_type,
GdkRectangle* area,
GtkWidget* widget,
GtkArrowType arrow_type,
gboolean fill,
gint x,
gint y,
gint w,
gint h) {
ScopedSurface cr(window, area);
// Expand the size so we get a descent arrow. It's ok to do expand the size
// as this size goes into the borders, which we don't render into.
w += 2;
h += 2;
x--;
y--;
DrawFilledArrow(cr.get(), arrow_type, x, y, w, h, MENU_IDEAL_ARROW_SIZE);
}
static void DrawMenuBorder(GtkStyle* style,
GdkWindow* window,
GtkStateType state_type,
GtkShadowType shadow_type,
GdkRectangle* area,
GtkWidget* widget,
gint x,
gint y,
gint w,
gint h) {
ScopedSurface cr(window, area);
cairo_rectangle(cr.get(), x, y, w, h);
ScopedPattern pattern;
if (WidgetHasName(widget, kGtkComboBoxPopupMenu)) {
cairo_set_source_rgb(cr.get(), MENU_BG_R, MENU_BG_G, MENU_BG_B);
} else {
pattern.reset(cairo_pattern_create_linear(0, 0, 0, h));
cairo_pattern_add_color_stop_rgb(pattern.get(), 0, MENU_GRADIENT_R0,
MENU_GRADIENT_G0, MENU_GRADIENT_B0);
cairo_pattern_add_color_stop_rgb(pattern.get(), 1, MENU_GRADIENT_R1,
MENU_GRADIENT_G1, MENU_GRADIENT_B1);
cairo_set_source(cr.get(), pattern.get());
}
cairo_fill(cr.get());
}
static void DrawMenuHorizontalSeparator(GtkStyle* style,
GdkWindow* window,
GtkStateType state_type,
GdkRectangle* area,
GtkWidget* widget,
gint x1,
gint x2,
gint y) {
// The separator is inset by padding and xthickness. Offset by that so the
// separator extends through the whole menu item.
int horizontal_padding;
gtk_widget_style_get(widget, "horizontal-padding", &horizontal_padding,
NULL);
int x_padding = horizontal_padding + widget->style->xthickness;
x1 -= x_padding;
x2 += x_padding + x_padding;
ScopedSurface cr(window, area);
ScopedPattern pattern(cairo_pattern_create_linear(0, 0, x2 - x1, 0));
cairo_pattern_add_color_stop_rgb(pattern.get(), 0, H_SEPARATOR_R0,
H_SEPARATOR_G0, H_SEPARATOR_B0);
cairo_pattern_add_color_stop_rgb(pattern.get(), 1, H_SEPARATOR_R1,
H_SEPARATOR_G1, H_SEPARATOR_B1);
cairo_set_source(cr.get(), pattern.get());
cairo_set_line_width(cr.get(), 1);
cairo_move_to(cr.get(), x1, y + .5);
cairo_line_to(cr.get(), x2, y + .5);
cairo_stroke(cr.get());
}
static void DrawMenuItemBorder(GtkStyle* style,
GdkWindow* window,
GtkStateType state_type,
GtkShadowType shadow_type,
GdkRectangle* area,
GtkWidget* widget,
gint x,
gint y,
gint w,
gint h) {
if (state_type == GTK_STATE_PRELIGHT) {
ScopedSurface cr(window, area);
cairo_set_source_rgb(cr.get(), MENU_BG_HIGHLIGHT_R, MENU_BG_HIGHLIGHT_G,
MENU_BG_HIGHLIGHT_B);
cairo_rectangle(cr.get(), x, y, w, h);
cairo_fill(cr.get());
}
}
static void DrawMenuItemCheck(GtkStyle* style,
GdkWindow* window,
GtkStateType state_type,
GtkShadowType shadow_type,
GdkRectangle* area,
GtkWidget* widget,
gint x,
gint y,
gint w,
gint h) {
if (gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget)))
DrawCheckMark(widget, window, area, x, y, w, h, false);
}
static void DrawMenuItemRadio(GtkStyle* style,
GdkWindow* window,
GtkStateType state_type,
GtkShadowType shadow_type,
GdkRectangle* area,
GtkWidget* widget,
gint x,
gint y,
gint w,
gint h) {
DrawRadioIndicator(widget, window, area, x, y, w, h,
gtk_check_menu_item_get_active(
GTK_CHECK_MENU_ITEM(widget)),
false,
MENU_RADIO_BUTTON_INDICATOR_IDEAL_SIZE);
}
static void DrawRadioButtonIndicator(GtkStyle* style,
GdkWindow* window,
GtkStateType state_type,
GtkShadowType shadow_type,
GdkRectangle* area,
GtkWidget* widget,
gint x,
gint y,
gint w,
gint h) {
DrawRadioIndicator(widget, window, area, x, y, w, h,
gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)),
state_type == GTK_STATE_ACTIVE,
RADIO_BUTTON_INDICATOR_IDEAL_SIZE);
}
static void DrawScrollbarArrow(GtkStyle* style,
GdkWindow* window,
GtkStateType state_type,
GtkShadowType shadow_type,
GdkRectangle* area,
GtkWidget* widget,
GtkArrowType arrow_type,
gboolean fill,
gint x,
gint y,
gint w,
gint h) {
// We want a slightly bigger arrow so we expand to the actual size that
// range gives. GtkRange gives the arrow half the available width, so that
// it's ok to expand the area here.
x = x + w / 2 - w;
y = y + h / 2 - h;
w += w;
h += h;
ScopedSurface cr(window, area);
cairo_set_line_width(cr.get(), 1);
cairo_set_source_rgb(cr.get(), SCROLLBAR_ARROW_FILL_R,
SCROLLBAR_ARROW_FILL_G, SCROLLBAR_ARROW_FILL_B);
cairo_translate(cr.get(), x + w / 2, y + h / 2);
// Nudge things slightly so they look pretty.
if (arrow_type == GTK_ARROW_UP)
cairo_translate(cr.get(), 1, 1);
cairo_rotate(cr.get(), GetRotationAngle(arrow_type));
int arrow_w = std::min(h, std::min(SCROLLBAR_IDEAL_ARROW_SIZE, w));
if (arrow_w % 2 == 0)
arrow_w--; // Force size to be odd.
int arrow_h = arrow_w - 1;
// Create the path first. We don't stroke this path as it doesn't line
// up as nicely as the path below.
cairo_translate(cr.get(), -arrow_w / 2, -arrow_h / 2);
cairo_move_to(cr.get(), 0, .5);
cairo_line_to(cr.get(), arrow_w, .5);
cairo_line_to(cr.get(), arrow_w / 2 + .5, arrow_h - .5);
cairo_line_to(cr.get(), arrow_w / 2 + .5, arrow_h - .5);
cairo_close_path(cr.get());
cairo_fill(cr.get());
// Then the stroke path.
cairo_set_source_rgb(cr.get(), SCROLLBAR_ARROW_BORDER_R,
SCROLLBAR_ARROW_BORDER_G, SCROLLBAR_ARROW_BORDER_B);
cairo_move_to(cr.get(), 0, .5);
cairo_line_to(cr.get(), arrow_w, .5);
cairo_stroke(cr.get());
cairo_move_to(cr.get(), arrow_w - .5, .5);
cairo_line_to(cr.get(), arrow_w / 2 + .5, arrow_h - .5);
cairo_stroke(cr.get());
cairo_move_to(cr.get(), .5, .5);
cairo_line_to(cr.get(), arrow_w / 2 + .5, arrow_h - .5);
cairo_stroke(cr.get());
}
static void DrawScrollbarBorder(GtkStyle* style,
GdkWindow* window,
GtkStateType state_type,
GtkShadowType shadow_type,
GdkRectangle* area,
GtkWidget* widget,
gint x,
gint y,
gint w,
gint h) {
ScopedSurface cr(window, area);
cairo_rectangle(cr.get(), x, y, w, h);
cairo_set_source_rgb(cr.get(), SCROLLBAR_BORDER_R, SCROLLBAR_BORDER_G,
SCROLLBAR_BORDER_B);
cairo_stroke(cr.get());
}
static void DrawScrollbarThumb(GtkStyle* style,
GdkWindow* window,
GtkStateType state_type,
GtkShadowType shadow_type,
GdkRectangle* area,
GtkWidget* widget,
gint x,
gint y,
gint w,
gint h,
GtkOrientation orientation) {
// Draw a slightly smaller thumb.
if (orientation == GTK_ORIENTATION_HORIZONTAL) {
y += 1;
h -= 2;
} else {
x += 1;
w -= 2;
}
ScopedSurface cr(window, area);
cairo_translate(cr.get(), x, y);
cairo_set_line_width(cr.get(), 1);
AddRoundedRectPath(cr.get(), w, h, 1, BORDER_CORNER_RADIUS);
cairo_set_source_rgb(cr.get(), SCROLLBAR_THUMB_BG_R, SCROLLBAR_THUMB_BG_G,
SCROLLBAR_THUMB_BG_B);
cairo_fill_preserve(cr.get());
SetStrokeColor(cr.get(), true, false);
cairo_stroke(cr.get());
}
static void DrawScrollbarTrack(GtkStyle* style,
GdkWindow* window,
GtkStateType state_type,
GtkShadowType shadow_type,
GdkRectangle* area,
GtkWidget* widget,
gint x,
gint y,
gint w,
gint h) {
ScopedSurface cr(window, area);
cairo_set_source_rgb(cr.get(), SCROLLBAR_BG_R, SCROLLBAR_BG_G,
SCROLLBAR_BG_B);
cairo_rectangle(cr.get(), x, y, w, h);
cairo_fill(cr.get());
}
static void DrawTabBorder(GtkStyle* style,
GdkWindow* window,
GtkStateType state_type,
GtkShadowType shadow_type,
GdkRectangle* area,
GtkWidget* widget,
gint x,
gint y,
gint w,
gint h,
GtkPositionType gap_side) {
ScopedSurface cr(window, area);
if (x + w != widget->allocation.x + widget->allocation.width) {
// So that tabs don't visually overlap.
w--;
}
if (state_type != GTK_STATE_NORMAL) {
// Because we have an thickness specified in the rc file non-selected tabs
// shift down. This forces all tabs to be rendered the same.
y--;
h++;
}
bool is_options_tab = WidgetHasName(widget, kChromeOsOptionsTabName);
cairo_translate(cr.get(), x, y);
cairo_move_to(cr.get(), .5, h);
cairo_line_to(cr.get(), .5, BORDER_CORNER_RADIUS);
cairo_arc(cr.get(), BORDER_CORNER_RADIUS + .5, BORDER_CORNER_RADIUS + .5,
BORDER_CORNER_RADIUS, M_PI, M_PI * 3 / 2);
cairo_line_to(cr.get(), BORDER_CORNER_RADIUS, .5);
cairo_line_to(cr.get(), w - BORDER_CORNER_RADIUS, .5);
cairo_arc(cr.get(), w - BORDER_CORNER_RADIUS - .5,
BORDER_CORNER_RADIUS + .5, BORDER_CORNER_RADIUS, -M_PI / 2, 0);
cairo_line_to(cr.get(), w - .5, BORDER_CORNER_RADIUS);
cairo_line_to(cr.get(), w - .5, h);
ScopedPath save_path(cairo_copy_path(cr.get()));
cairo_close_path(cr.get());
ScopedPattern pattern;
if (state_type != GTK_STATE_NORMAL) {
if (is_options_tab) {
// Blueish gradient for options dialog tabs.
pattern.reset(cairo_pattern_create_linear(0, 0, 0, h));
cairo_pattern_add_color_stop_rgb(pattern.get(), 0,
OPTIONS_TAB_R0,
OPTIONS_TAB_G0,
OPTIONS_TAB_B0);
cairo_pattern_add_color_stop_rgb(pattern.get(), 1.0,
OPTIONS_TAB_R1,
OPTIONS_TAB_G1,
OPTIONS_TAB_B1);
} else {
// Gradient background for inactive tabs.
AddRoundRectGradient(cr.get(), &pattern, h, false);
}
cairo_set_source(cr.get(), pattern.get());
} else {
const GdkColor &bg = widget->style->bg[state_type];
cairo_set_source_rgb(cr.get(),
static_cast<double>(bg.red) / 65535.0,
static_cast<double>(bg.green) / 65535.0,
static_cast<double>(bg.blue) / 65535.0);
}
cairo_fill(cr.get());
// Restore tab border path and clean up our saved path.
cairo_append_path(cr.get(), save_path.get());
cairo_set_line_width(cr.get(), 1);
if (!is_options_tab) {
cairo_set_source_rgb(cr.get(), TAB_PANE_BORDER_R, TAB_PANE_BORDER_R,
TAB_PANE_BORDER_R);
}
cairo_stroke(cr.get());
}
static void DrawTabPaneBorder(GtkStyle* style,
GdkWindow* window,
GtkStateType state_type,
GtkShadowType shadow_type,
GdkRectangle* area,
GtkWidget* widget,
gint x,
gint y,
gint w,
gint h,
GtkPositionType gap_side,
gint gap_x,
gint gap_w) {
ScopedSurface cr(window, area);
bool is_options_tab = WidgetHasName(widget, kChromeOsOptionsTabName);
cairo_translate(cr.get(), static_cast<double>(x),
static_cast<double>(y));
if (is_options_tab) {
cairo_set_source_rgb(cr.get(), OPTIONS_TAB_BORDER_R, OPTIONS_TAB_BORDER_G,
OPTIONS_TAB_BORDER_B);
} else {
cairo_set_source_rgb(cr.get(), TAB_PANE_BORDER_R, TAB_PANE_BORDER_G,
TAB_PANE_BORDER_B);
}
cairo_set_line_width(cr.get(), 1);
cairo_move_to(cr.get(), .5, 0);
cairo_line_to(cr.get(), .5, h);
cairo_stroke(cr.get());
cairo_move_to(cr.get(), 0, h - .5);
cairo_line_to(cr.get(), w, h - .5);
cairo_stroke(cr.get());
cairo_move_to(cr.get(), w - .5, 0);
cairo_line_to(cr.get(), w - .5, h);
cairo_stroke(cr.get());
if (is_options_tab) {
// No gap for options tab.
cairo_move_to(cr.get(), 0, .5);
cairo_line_to(cr.get(), w, .5);
cairo_stroke(cr.get());
} else {
if (gap_x > 0) {
cairo_move_to(cr.get(), 0, .5);
cairo_line_to(cr.get(), gap_x + 1, .5);
cairo_stroke(cr.get());
}
if (gap_x + gap_w < w) {
cairo_move_to(cr.get(), gap_x + gap_w - 2, .5);
cairo_line_to(cr.get(), w, .5);
cairo_stroke(cr.get());
}
}
}
static void DrawTextFieldBackground(GtkStyle* style,
GdkWindow* window,
GtkStateType state_type,
GtkShadowType shadow_type,
GdkRectangle* area,
GtkWidget* widget,
gint x,
gint y,
gint w,
gint h) {
if (GTK_IS_ENTRY(widget) && !gtk_entry_get_has_frame(GTK_ENTRY(widget)))
return;
ScopedSurface cr(window, area);
DrawTextBorder(cr.get(), widget, -(widget->allocation.width - w) / 2,
-(widget->allocation.height - h) / 2,
widget->allocation.width,
widget->allocation.height);
}
static void DrawTextFieldBorder(GtkStyle* style,
GdkWindow* window,
GtkStateType state_type,
GtkShadowType shadow_type,
GdkRectangle* area,
GtkWidget* widget,
gint x,
gint y,
gint w,
gint h) {
if (GTK_IS_ENTRY(widget) && !gtk_entry_get_has_frame(GTK_ENTRY(widget)))
return;
ScopedSurface cr(window, area);
DrawTextBorder(cr.get(), widget, x, y, w, h);
}
static void DrawTooltipBorder(GtkStyle* style,
GdkWindow* window,
GtkStateType state_type,
GtkShadowType shadow_type,
GdkRectangle* area,
GtkWidget* widget,
gint x,
gint y,
gint w,
gint h) {
ScopedSurface cr(window, area);
if (WidgetHasRGBAVisual(widget)) {
cairo_rectangle(cr.get(), x, y, w, h);
cairo_set_operator(cr.get(), CAIRO_OPERATOR_CLEAR);
cairo_fill(cr.get());
cairo_set_operator(cr.get(), CAIRO_OPERATOR_SOURCE);
for (int i = 0; i <= TOOLTIP_SHADOW_DEPTH; ++i) {
cairo_save(cr.get());
cairo_translate(cr.get(), x + i, y + i);
AddRoundedRectPath(cr.get(),
w - 2 * i,
h - 2 * i,
1,
TOOLTIP_CORNER_RADIUS - i);
cairo_set_line_width(cr.get(), 1);
if (i < TOOLTIP_SHADOW_DEPTH) {
// Draw shadow.
cairo_set_source_rgba(cr.get(),
TOOLTIP_SHADOW_R,
TOOLTIP_SHADOW_G,
TOOLTIP_SHADOW_B,
TOOLTIP_SHADOW_ALPHAS[i]);
cairo_stroke(cr.get());
} else {
// Draw real border and background in the last iteration
const GdkColor &bg = widget->style->bg[state_type];
cairo_set_source_rgba(cr.get(),
static_cast<double>(bg.red) / 65535.0,
static_cast<double>(bg.green) / 65535.0,
static_cast<double>(bg.blue) / 65535.0,
1.0);
cairo_fill_preserve(cr.get());
cairo_set_source_rgba(cr.get(), TOOLTIP_R, TOOLTIP_G, TOOLTIP_B,
TOOLTIP_A);
cairo_stroke(cr.get());
}
cairo_restore(cr.get());
}
} else {
cairo_set_source_rgb(cr.get(), TOOLTIP_R, TOOLTIP_G, TOOLTIP_B);
cairo_rectangle(cr.get(), x, y, w, h);
cairo_stroke(cr.get());
}
}
static void DrawTreeItemBackground(GtkStyle* style,
GdkWindow* window,
GtkStateType state_type,
GtkShadowType shadow_type,
GdkRectangle* area,
GtkWidget* widget,
gint x,
gint y,
gint w,
gint h) {
ScopedSurface cr(window, area);
if (state_type == GTK_STATE_SELECTED) {
cairo_set_source_rgb(cr.get(), TREE_ITEM_SELECTED_BG_R,
TREE_ITEM_SELECTED_BG_G, TREE_ITEM_SELECTED_BG_B);
} else {
cairo_set_source_rgb(cr.get(), TREE_ITEM_BG_R, TREE_ITEM_BG_G,
TREE_ITEM_BG_B);
}
cairo_rectangle(cr.get(), x, y, w, h);
cairo_fill(cr.get());
}
static void DrawViewportBorder(GtkStyle* style,
GdkWindow* window,
GtkStateType state_type,
GtkShadowType shadow_type,
GdkRectangle* area,
GtkWidget* widget,
gint x,
gint y,
gint w,
gint h) {
// NOTE: we ignore w/h as they are always -1,-1 here.
ScopedSurface cr(window, area);
cairo_set_source_rgb(cr.get(), SCROLLBAR_BORDER_R, SCROLLBAR_BORDER_G,
SCROLLBAR_BORDER_B);
DrawSinglePixelWideRectangle(cr.get(), 0, 0, widget->allocation.width,
widget->allocation.height);
}
// Theme engine functions. These all call into the more specific functions
// above.
void ThemeDrawArrow(GtkStyle* style,
GdkWindow* window,
GtkStateType state_type,
GtkShadowType shadow_type,
GdkRectangle* area,
GtkWidget* widget,
const gchar* detail,
GtkArrowType arrow_type,
gboolean fill,
gint x,
gint y,
gint w,
gint h) {
if (GTK_IS_ARROW(widget) && detail && !strcmp("arrow", detail)) {
DrawComboboxArrow(style, window, state_type, shadow_type, area, widget,
arrow_type, fill, x, y, w, h);
} else if (GTK_IS_RANGE(widget)) {
DrawScrollbarArrow(style, window, state_type, shadow_type, area, widget,
arrow_type, fill, x, y, w, h);
} else if ((GTK_IS_MENU_ITEM(widget) && detail &&
!strcmp(detail, "menuitem")) ||
(GTK_IS_MENU(widget) && detail &&
(!strcmp(detail, "menu_scroll_arrow_up") ||
!strcmp(detail, "menu_scroll_arrow_down")))) {
DrawMenuArrow(style, window, state_type, shadow_type, area, widget,
arrow_type, fill, x, y, w, h);
}
}
void ThemeDrawBox(GtkStyle* style,
GdkWindow* window,
GtkStateType state_type,
GtkShadowType shadow_type,
GdkRectangle* area,
GtkWidget* widget,
const gchar* detail,
gint x,
gint y,
gint w,
gint h) {
if (GTK_IS_BUTTON(widget) && detail && !strcmp("button", detail)) {
DrawButtonBorder(style, window, state_type, shadow_type, area, widget, x, y,
w, h);
} else if (GTK_IS_HSCALE(widget) && detail && !strcmp(detail, "trough")) {
DrawHorizontalSliderTrack(style, window, state_type, shadow_type, area,
widget, x, y, w, h);
} else if (GTK_IS_RANGE(widget) && detail && !strcmp(detail, "trough")) {
DrawScrollbarTrack(style, window, state_type, shadow_type, area, widget, x,
y, w, h);
} else if (GTK_IS_MENU(widget) && detail && !strcmp(detail, "menu")) {
DrawMenuBorder(style, window, state_type, shadow_type, area, widget, x, y,
w, h);
} else if (GTK_IS_MENU_ITEM(widget) && detail &&
!strcmp(detail, "menuitem")) {
DrawMenuItemBorder(style, window, state_type, shadow_type, area, widget, x,
y, w, h);
}
}
void ThemeDrawBoxGap(GtkStyle* style,
GdkWindow* window,
GtkStateType state_type,
GtkShadowType shadow_type,
GdkRectangle* area,
GtkWidget* widget,
const gchar* detail,
gint x,
gint y,
gint w,
gint h,
GtkPositionType gap_side,
gint gap_x,
gint gap_w) {
if (GTK_IS_NOTEBOOK(widget) && detail && !strcmp(detail, "notebook")) {
DrawTabPaneBorder(style, window, state_type, shadow_type, area, widget,
x, y, w, h, gap_side, gap_x, gap_w);
}
}
void ThemeDrawCheck(GtkStyle* style,
GdkWindow* window,
GtkStateType state_type,
GtkShadowType shadow_type,
GdkRectangle* area,
GtkWidget* widget,
const gchar* detail,
gint x,
gint y,
gint w,
gint h) {
if (GTK_IS_CHECK_BUTTON(widget) && detail && !strcmp("checkbutton", detail)) {
DrawCheckboxCheck(style, window, state_type, shadow_type, area, widget, x,
y, w, h);
} else if (GTK_IS_CHECK_MENU_ITEM(widget) && detail &&
!strcmp("check", detail)) {
DrawMenuItemCheck(style, window, state_type, shadow_type, area, widget, x,
y, w, h);
}
}
void ThemeDrawExtension(GtkStyle* style,
GdkWindow* window,
GtkStateType state_type,
GtkShadowType shadow_type,
GdkRectangle* area,
GtkWidget* widget,
const gchar* detail,
gint x,
gint y,
gint w,
gint h,
GtkPositionType gap_side) {
if (GTK_IS_NOTEBOOK(widget) && detail && !strcmp(detail, "tab")) {
DrawTabBorder(style, window, state_type, shadow_type, area, widget, x, y,
w, h, gap_side);
}
}
void ThemeDrawFlatBox(GtkStyle* style,
GdkWindow* window,
GtkStateType state_type,
GtkShadowType shadow_type,
GdkRectangle* area,
GtkWidget* widget,
const gchar* detail,
gint x,
gint y,
gint w,
gint h) {
if ((GTK_IS_ENTRY(widget) && detail && !strcmp(detail, "entry_bg")) ||
(GTK_IS_TEXT_VIEW(widget) && detail && !strcmp(detail, "textview"))) {
DrawTextFieldBackground(style, window, state_type, shadow_type, area,
widget, x, y, w, h);
} else if (GTK_IS_WINDOW(widget) && detail && !strcmp(detail, "tooltip")) {
// NOTE: the if checks GTK_IS_WINDOW as that is what GtkTooltip supplies to
// this function.
DrawTooltipBorder(style, window, state_type, shadow_type, area, widget,
x, y, w, h);
} else if (GTK_IS_TREE_VIEW(widget)) {
DrawTreeItemBackground(style, window, state_type, shadow_type, area, widget,
x, y, w, h);
}
}
void ThemeDrawFocus(GtkStyle* style,
GdkWindow* window,
GtkStateType state_type,
GdkRectangle* area,
GtkWidget* widget,
const gchar* detail,
gint x,
gint y,
gint w,
gint h) {
// Focus is currently rendered in the border/background, so this does nothing.
}
void ThemeDrawHline(GtkStyle* style,
GdkWindow* window,
GtkStateType state_type,
GdkRectangle* area,
GtkWidget* widget,
const gchar* detail,
gint x1,
gint x2,
gint y) {
if (GTK_IS_MENU_ITEM(widget) && detail && !strcmp(detail, "menuitem")) {
DrawMenuHorizontalSeparator(style, window, state_type, area, widget, x1,
x2, y);
}
}
void ThemeDrawOption(GtkStyle* style,
GdkWindow* window,
GtkStateType state_type,
GtkShadowType shadow_type,
GdkRectangle* area,
GtkWidget* widget,
const gchar* detail,
gint x,
gint y,
gint w,
gint h) {
if (GTK_IS_RADIO_BUTTON(widget) && detail && !strcmp("radiobutton", detail)) {
DrawRadioButtonIndicator(style, window, state_type, shadow_type, area,
widget, x, y, w, h);
} else if (GTK_IS_CHECK_MENU_ITEM(widget) && detail &&
!strcmp(detail, "option")) {
DrawMenuItemRadio(style, window, state_type, shadow_type, area, widget,
x, y, w, h);
}
}
void ThemeDrawShadow(GtkStyle* style,
GdkWindow* window,
GtkStateType state_type,
GtkShadowType shadow_type,
GdkRectangle* area,
GtkWidget* widget,
const gchar* detail,
gint x,
gint y,
gint w,
gint h) {
if ((GTK_IS_ENTRY(widget) && detail && !strcmp(detail, "entry")) ||
(GTK_IS_TEXT_VIEW(widget) && detail && !strcmp(detail, "textview"))) {
DrawTextFieldBorder(style, window, state_type, shadow_type, area, widget, x,
y, w, h);
} else if (GTK_IS_SCROLLED_WINDOW(widget) && detail &&
!strcmp(detail, "scrolled_window")) {
DrawScrollbarBorder(style, window, state_type, shadow_type, area, widget,
x, y, w, h);
} else if (GTK_IS_VIEWPORT(widget) && detail &&
!strcmp(detail, "viewport")) {
DrawViewportBorder(style, window, state_type, shadow_type, area, widget, x,
y, w, h);
}
}
void ThemeDrawSlider(GtkStyle* style,
GdkWindow* window,
GtkStateType state_type,
GtkShadowType shadow_type,
GdkRectangle* area,
GtkWidget* widget,
const gchar* detail,
gint x,
gint y,
gint w,
gint h,
GtkOrientation orientation) {
if (GTK_IS_RANGE(widget) && detail && !strcmp(detail, "slider")) {
DrawScrollbarThumb(style, window, state_type, shadow_type, area, widget, x,
y, w, h, orientation);
} else if (GTK_IS_HSCALE(widget) && detail && !strcmp(detail, "hscale")) {
DrawHorizontalSliderThumb(style, window, state_type, shadow_type, area,
widget, x, y, w, h, orientation);
}
}
void ThemeDrawVline(GtkStyle* style,
GdkWindow* window,
GtkStateType state_type,
GdkRectangle* area,
GtkWidget* widget,
const gchar* detail,
gint y1,
gint y2,
gint x) {
// We currently don't have any vertical separators. If we do need them, be
// sure and special case so that we don't draw the separator for comboboxs:
}