1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "chrome/browser/ui/gtk/nine_box.h"
7 #include "base/basictypes.h"
8 #include "base/i18n/rtl.h"
9 #include "base/logging.h"
10 #include "ui/base/resource/resource_bundle.h"
11 #include "ui/gfx/gtk_util.h"
12 #include "ui/gfx/image/cairo_cached_surface.h"
13 #include "ui/gfx/image/image.h"
14 #include "ui/gfx/point.h"
18 // Draw pixbuf |src| into |dst| at position (x, y).
19 void DrawImage(cairo_t
* cr
, GtkWidget
* widget
, gfx::Image
* src
,
20 int x
, int y
, double alpha
) {
24 src
->ToCairo()->SetSource(cr
, widget
, x
, y
);
25 cairo_paint_with_alpha(cr
, alpha
);
28 // Tile pixbuf |src| across |cr| at |x|, |y| for |width| and |height|.
29 void TileImage(cairo_t
* cr
, GtkWidget
* widget
, gfx::Image
* src
,
30 int x
, int y
, int width
, int height
, double alpha
) {
35 src
->ToCairo()->SetSource(cr
, widget
, x
, y
);
36 cairo_pattern_set_extend(cairo_get_source(cr
), CAIRO_EXTEND_REPEAT
);
37 cairo_rectangle(cr
, x
, y
, width
, height
);
40 // Since there is no easy way to apply a mask to a fill operation, we create
41 // a secondary surface and tile into that, then paint it with |alpha|.
42 cairo_surface_t
* target
= cairo_get_target(cr
);
43 cairo_surface_t
* surface
= cairo_surface_create_similar(
44 target
, CAIRO_CONTENT_COLOR_ALPHA
, width
, height
);
45 cairo_t
* tiled
= cairo_create(surface
);
46 src
->ToCairo()->SetSource(tiled
, widget
, 0, 0);
47 cairo_pattern_set_extend(cairo_get_source(tiled
), CAIRO_EXTEND_REPEAT
);
48 cairo_rectangle(tiled
, 0, 0, width
, height
);
51 cairo_set_source_surface(cr
, surface
, x
, y
);
52 cairo_paint_with_alpha(cr
, alpha
);
55 cairo_surface_destroy(surface
);
61 NineBox::NineBox(int top_left
, int top
, int top_right
, int left
, int center
,
62 int right
, int bottom_left
, int bottom
, int bottom_right
)
63 : unref_images_on_destroy_(false) {
64 ui::ResourceBundle
& rb
= ui::ResourceBundle::GetSharedInstance();
66 images_
[0] = top_left
? &rb
.GetNativeImageNamed(top_left
) : NULL
;
67 images_
[1] = top
? &rb
.GetNativeImageNamed(top
) : NULL
;
68 images_
[2] = top_right
? &rb
.GetNativeImageNamed(top_right
) : NULL
;
69 images_
[3] = left
? &rb
.GetNativeImageNamed(left
) : NULL
;
70 images_
[4] = center
? &rb
.GetNativeImageNamed(center
) : NULL
;
71 images_
[5] = right
? &rb
.GetNativeImageNamed(right
) : NULL
;
72 images_
[6] = bottom_left
? &rb
.GetNativeImageNamed(bottom_left
) : NULL
;
73 images_
[7] = bottom
? &rb
.GetNativeImageNamed(bottom
) : NULL
;
74 images_
[8] = bottom_right
? &rb
.GetNativeImageNamed(bottom_right
) : NULL
;
77 NineBox::NineBox(int image
, int top_margin
, int bottom_margin
, int left_margin
,
79 : unref_images_on_destroy_(true) {
80 ui::ResourceBundle
& rb
= ui::ResourceBundle::GetSharedInstance();
81 GdkPixbuf
* pixbuf
= rb
.GetNativeImageNamed(image
).ToGdkPixbuf();
82 int width
= gdk_pixbuf_get_width(pixbuf
);
83 int height
= gdk_pixbuf_get_height(pixbuf
);
84 int inset_width
= left_margin
+ right_margin
;
85 int inset_height
= top_margin
+ bottom_margin
;
87 images_
[0] = new gfx::Image(gdk_pixbuf_new_subpixbuf(
88 pixbuf
, 0, 0, left_margin
, top_margin
));
89 images_
[1] = new gfx::Image(gdk_pixbuf_new_subpixbuf(
90 pixbuf
, left_margin
, 0, width
- inset_width
, top_margin
));
91 images_
[2] = new gfx::Image(gdk_pixbuf_new_subpixbuf(
92 pixbuf
, width
- right_margin
, 0, right_margin
, top_margin
));
93 images_
[3] = new gfx::Image(gdk_pixbuf_new_subpixbuf(
94 pixbuf
, 0, top_margin
, left_margin
, height
- inset_height
));
95 images_
[4] = new gfx::Image(gdk_pixbuf_new_subpixbuf(
96 pixbuf
, left_margin
, top_margin
, width
- inset_width
,
97 height
- inset_height
));
98 images_
[5] = new gfx::Image(gdk_pixbuf_new_subpixbuf(
99 pixbuf
, width
- right_margin
, top_margin
, right_margin
,
100 height
- inset_height
));
101 images_
[6] = new gfx::Image(gdk_pixbuf_new_subpixbuf(
102 pixbuf
, 0, height
- bottom_margin
, left_margin
, bottom_margin
));
103 images_
[7] = new gfx::Image(gdk_pixbuf_new_subpixbuf(
104 pixbuf
, left_margin
, height
- bottom_margin
,
105 width
- inset_width
, bottom_margin
));
106 images_
[8] = new gfx::Image(gdk_pixbuf_new_subpixbuf(
107 pixbuf
, width
- right_margin
, height
- bottom_margin
,
108 right_margin
, bottom_margin
));
111 NineBox::~NineBox() {
112 if (unref_images_on_destroy_
) {
113 for (int i
= 0; i
< 9; i
++) {
119 void NineBox::RenderToWidget(GtkWidget
* dst
) const {
120 RenderToWidgetWithOpacity(dst
, 1.0);
123 void NineBox::RenderToWidgetWithOpacity(GtkWidget
* dst
, double opacity
) const {
124 GtkAllocation allocation
;
125 gtk_widget_get_allocation(dst
, &allocation
);
126 int w
= allocation
.width
;
127 int h
= allocation
.height
;
129 // In case the corners and edges don't all have the same width/height, we draw
130 // the center first, and extend it out in all directions to the edges of the
131 // images with the smallest widths/heights. This way there will be no
132 // unpainted areas, though some corners or edges might overlap the center.
133 int i0w
= images_
[0] ? images_
[0]->Width() : 0;
134 int i2w
= images_
[2] ? images_
[2]->Width() : 0;
135 int i3w
= images_
[3] ? images_
[3]->Width() : 0;
136 int i5w
= images_
[5] ? images_
[5]->Width() : 0;
137 int i6w
= images_
[6] ? images_
[6]->Width() : 0;
138 int i8w
= images_
[8] ? images_
[8]->Width() : 0;
139 int i4x
= std::min(std::min(i0w
, i3w
), i6w
);
140 int i4w
= w
- i4x
- std::min(std::min(i2w
, i5w
), i8w
);
141 int i0h
= images_
[0] ? images_
[0]->Height() : 0;
142 int i1h
= images_
[1] ? images_
[1]->Height() : 0;
143 int i2h
= images_
[2] ? images_
[2]->Height() : 0;
144 int i6h
= images_
[6] ? images_
[6]->Height() : 0;
145 int i7h
= images_
[7] ? images_
[7]->Height() : 0;
146 int i8h
= images_
[8] ? images_
[8]->Height() : 0;
147 int i4y
= std::min(std::min(i0h
, i1h
), i2h
);
148 int i4h
= h
- i4y
- std::min(std::min(i6h
, i7h
), i8h
);
150 cairo_t
* cr
= gdk_cairo_create(GDK_DRAWABLE(gtk_widget_get_window(dst
)));
151 // For widgets that have their own window, the allocation (x,y) coordinates
152 // are GdkWindow relative. For other widgets, the coordinates are relative
153 // to their container.
154 if (!gtk_widget_get_has_window(dst
)) {
155 // Transform our cairo from window to widget coordinates.
156 cairo_translate(cr
, allocation
.x
, allocation
.y
);
159 if (base::i18n::IsRTL()) {
160 cairo_translate(cr
, w
, 0.0f
);
161 cairo_scale(cr
, -1.0f
, 1.0f
);
164 // Top row, center image is horizontally tiled.
165 DrawImage(cr
, dst
, images_
[0], 0, 0, opacity
);
166 TileImage(cr
, dst
, images_
[1], i0w
, 0, w
- i0w
- i2w
, i1h
, opacity
);
167 DrawImage(cr
, dst
, images_
[2], w
- i2w
, 0, opacity
);
169 // Center row, all images are vertically tiled, center is horizontally tiled.
170 TileImage(cr
, dst
, images_
[3], 0, i0h
, i3w
, h
- i0h
- i6h
, opacity
);
171 TileImage(cr
, dst
, images_
[4], i4x
, i4y
, i4w
, i4h
, opacity
);
172 TileImage(cr
, dst
, images_
[5], w
- i5w
, i2h
, i5w
, h
- i2h
- i8h
, opacity
);
174 // Bottom row, center image is horizontally tiled.
175 DrawImage(cr
, dst
, images_
[6], 0, h
- i6h
, opacity
);
176 TileImage(cr
, dst
, images_
[7], i6w
, h
- i7h
, w
- i6w
- i8w
, i7h
, opacity
);
177 DrawImage(cr
, dst
, images_
[8], w
- i8w
, h
- i8h
, opacity
);
182 void NineBox::ContourWidget(GtkWidget
* widget
) const {
183 GtkAllocation allocation
;
184 gtk_widget_get_allocation(widget
, &allocation
);
185 int width
= allocation
.width
;
186 int height
= allocation
.height
;
187 int x1
= gdk_pixbuf_get_width(images_
[0]->ToGdkPixbuf());
188 int x2
= width
- gdk_pixbuf_get_width(images_
[2]->ToGdkPixbuf());
190 // TODO(erg): Far in the future, when we're doing the real gtk3 porting, this
191 // all needs to be switchted to pure cairo operations.
193 // Paint the left and right sides.
194 GdkBitmap
* mask
= gdk_pixmap_new(NULL
, width
, height
, 1);
195 gdk_pixbuf_render_threshold_alpha(images_
[0]->ToGdkPixbuf(), mask
,
199 gdk_pixbuf_render_threshold_alpha(images_
[2]->ToGdkPixbuf(), mask
,
204 // Assume no transparency in the middle rectangle.
205 cairo_t
* cr
= gdk_cairo_create(mask
);
206 cairo_rectangle(cr
, x1
, 0, x2
- x1
, height
);
210 // Mask the widget's window's shape.
211 if (!base::i18n::IsRTL()) {
212 gtk_widget_shape_combine_mask(widget
, mask
, 0, 0);
214 GdkBitmap
* flipped_mask
= gdk_pixmap_new(NULL
, width
, height
, 1);
215 cairo_t
* flipped_cr
= gdk_cairo_create(flipped_mask
);
217 // Clear the target bitmap.
218 cairo_set_operator(flipped_cr
, CAIRO_OPERATOR_CLEAR
);
219 cairo_paint(flipped_cr
);
221 // Apply flipping transformation.
222 cairo_translate(flipped_cr
, width
, 0.0f
);
223 cairo_scale(flipped_cr
, -1.0f
, 1.0f
);
225 // Paint the source bitmap onto the target.
226 cairo_set_operator(flipped_cr
, CAIRO_OPERATOR_SOURCE
);
227 gdk_cairo_set_source_pixmap(flipped_cr
, mask
, 0, 0);
228 cairo_paint(flipped_cr
);
229 cairo_destroy(flipped_cr
);
232 gtk_widget_shape_combine_mask(widget
, flipped_mask
, 0, 0);
233 g_object_unref(flipped_mask
);
236 g_object_unref(mask
);