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/rounded_window.h"
10 #include "base/i18n/rtl.h"
11 #include "base/logging.h"
12 #include "chrome/browser/ui/gtk/gtk_util.h"
13 #include "ui/base/gtk/gtk_signal_registrar.h"
14 #include "ui/gfx/gtk_compat.h"
20 const char* kRoundedData
= "rounded-window-data";
22 // If the border radius is less than |kMinRoundedBorderSize|, we don't actually
23 // round the corners, we just truncate the corner.
24 const int kMinRoundedBorderSize
= 8;
26 struct RoundedWindowData
{
27 // Expected window size. Used to detect when we need to reshape the window.
31 // Color of the border.
32 GdkColor border_color
;
34 // Radius of the edges in pixels.
37 // Which corners should be rounded?
40 // Which sides of the window should have an internal border?
43 // Keeps track of attached signal handlers.
44 ui::GtkSignalRegistrar signals
;
47 // Callback from GTK to release allocated memory.
48 void FreeRoundedWindowData(gpointer data
) {
49 delete static_cast<RoundedWindowData
*>(data
);
57 // Returns a list of points that either form the outline of the status bubble
58 // (|type| == FRAME_MASK) or form the inner border around the inner edge
59 // (|type| == FRAME_STROKE).
60 std::vector
<GdkPoint
> MakeFramePolygonPoints(RoundedWindowData
* data
,
62 using gtk_util::MakeBidiGdkPoint
;
63 int width
= data
->expected_width
;
64 int height
= data
->expected_height
;
65 int corner_size
= data
->corner_size
;
67 std::vector
<GdkPoint
> points
;
69 bool ltr
= !base::i18n::IsRTL();
70 // If we have a stroke, we have to offset some of our points by 1 pixel.
71 // We have to inset by 1 pixel when we draw horizontal lines that are on the
72 // bottom or when we draw vertical lines that are closer to the end (end is
74 int y_off
= (type
== FRAME_MASK
) ? 0 : -1;
75 // We use this one for LTR.
76 int x_off_l
= ltr
? y_off
: 0;
77 // We use this one for RTL.
78 int x_off_r
= !ltr
? -y_off
: 0;
80 // Build up points starting with the bottom left corner and continuing
83 // Bottom left corner.
84 if (type
== FRAME_MASK
||
85 (data
->drawn_borders
& (BORDER_LEFT
| BORDER_BOTTOM
))) {
86 if (data
->rounded_edges
& ROUNDED_BOTTOM_LEFT
) {
87 if (corner_size
>= kMinRoundedBorderSize
) {
88 // We are careful to only add points that are horizontal or vertically
89 // offset from the previous point (not both). This avoids rounding
90 // differences when two points are connected.
91 for (int x
= 0; x
<= corner_size
; ++x
) {
92 int y
= static_cast<int>(sqrt(static_cast<double>(
93 (corner_size
* corner_size
) - (x
* x
))));
95 points
.push_back(MakeBidiGdkPoint(
96 corner_size
- x
+ x_off_r
+ 1,
97 height
- (corner_size
- y
) + y_off
, width
, ltr
));
99 points
.push_back(MakeBidiGdkPoint(
100 corner_size
- x
+ x_off_r
,
101 height
- (corner_size
- y
) + y_off
, width
, ltr
));
104 points
.push_back(MakeBidiGdkPoint(
105 corner_size
+ x_off_l
, height
+ y_off
, width
, ltr
));
106 points
.push_back(MakeBidiGdkPoint(
107 x_off_r
, height
- corner_size
, width
, ltr
));
110 points
.push_back(MakeBidiGdkPoint(x_off_r
, height
+ y_off
, width
, ltr
));
115 if (type
== FRAME_MASK
||
116 (data
->drawn_borders
& (BORDER_LEFT
| BORDER_TOP
))) {
117 if (data
->rounded_edges
& ROUNDED_TOP_LEFT
) {
118 if (corner_size
>= kMinRoundedBorderSize
) {
119 for (int x
= corner_size
; x
>= 0; --x
) {
120 int y
= static_cast<int>(sqrt(static_cast<double>(
121 (corner_size
* corner_size
) - (x
* x
))));
122 points
.push_back(MakeBidiGdkPoint(corner_size
- x
+ x_off_r
,
123 corner_size
- y
, width
, ltr
));
125 points
.push_back(MakeBidiGdkPoint(corner_size
- x
+ 1 + x_off_r
,
126 corner_size
- y
, width
, ltr
));
130 points
.push_back(MakeBidiGdkPoint(
131 x_off_r
, corner_size
- 1, width
, ltr
));
132 points
.push_back(MakeBidiGdkPoint(
133 corner_size
+ x_off_r
- 1, 0, width
, ltr
));
136 points
.push_back(MakeBidiGdkPoint(x_off_r
, 0, width
, ltr
));
141 if (type
== FRAME_MASK
||
142 (data
->drawn_borders
& (BORDER_TOP
| BORDER_RIGHT
))) {
143 if (data
->rounded_edges
& ROUNDED_TOP_RIGHT
) {
144 if (corner_size
>= kMinRoundedBorderSize
) {
145 for (int x
= 0; x
<= corner_size
; ++x
) {
146 int y
= static_cast<int>(sqrt(static_cast<double>(
147 (corner_size
* corner_size
) - (x
* x
))));
149 points
.push_back(MakeBidiGdkPoint(
150 width
- (corner_size
- x
) + x_off_l
- 1,
151 corner_size
- y
, width
, ltr
));
153 points
.push_back(MakeBidiGdkPoint(
154 width
- (corner_size
- x
) + x_off_l
,
155 corner_size
- y
, width
, ltr
));
158 points
.push_back(MakeBidiGdkPoint(
159 width
- corner_size
+ 1 + x_off_l
, 0, width
, ltr
));
160 points
.push_back(MakeBidiGdkPoint(
161 width
+ x_off_l
, corner_size
- 1, width
, ltr
));
164 points
.push_back(MakeBidiGdkPoint(
165 width
+ x_off_l
, 0, width
, ltr
));
169 // Bottom right corner.
170 if (type
== FRAME_MASK
||
171 (data
->drawn_borders
& (BORDER_RIGHT
| BORDER_BOTTOM
))) {
172 if (data
->rounded_edges
& ROUNDED_BOTTOM_RIGHT
) {
173 if (corner_size
>= kMinRoundedBorderSize
) {
174 for (int x
= corner_size
; x
>= 0; --x
) {
175 int y
= static_cast<int>(sqrt(static_cast<double>(
176 (corner_size
* corner_size
) - (x
* x
))));
177 points
.push_back(MakeBidiGdkPoint(
178 width
- (corner_size
- x
) + x_off_l
,
179 height
- (corner_size
- y
) + y_off
, width
, ltr
));
181 points
.push_back(MakeBidiGdkPoint(
182 width
- (corner_size
- x
) + x_off_l
- 1,
183 height
- (corner_size
- y
) + y_off
, width
, ltr
));
187 points
.push_back(MakeBidiGdkPoint(
188 width
+ x_off_l
, height
- corner_size
, width
, ltr
));
189 points
.push_back(MakeBidiGdkPoint(
190 width
- corner_size
+ x_off_r
, height
+ y_off
, width
, ltr
));
193 points
.push_back(MakeBidiGdkPoint(
194 width
+ x_off_l
, height
+ y_off
, width
, ltr
));
201 // Set the window shape in needed, lets our owner do some drawing (if it wants
202 // to), and finally draw the border.
203 gboolean
OnRoundedWindowExpose(GtkWidget
* widget
,
204 GdkEventExpose
* event
) {
205 RoundedWindowData
* data
= static_cast<RoundedWindowData
*>(
206 g_object_get_data(G_OBJECT(widget
), kRoundedData
));
208 GtkAllocation allocation
;
209 gtk_widget_get_allocation(widget
, &allocation
);
211 if (data
->expected_width
!= allocation
.width
||
212 data
->expected_height
!= allocation
.height
) {
213 data
->expected_width
= allocation
.width
;
214 data
->expected_height
= allocation
.height
;
216 // We need to update the shape of the status bubble whenever our GDK
217 // window changes shape.
218 std::vector
<GdkPoint
> mask_points
= MakeFramePolygonPoints(
220 GdkRegion
* mask_region
= gdk_region_polygon(&mask_points
[0],
223 gdk_window_shape_combine_region(gtk_widget_get_window(widget
),
225 gdk_region_destroy(mask_region
);
228 GdkDrawable
* drawable
= GDK_DRAWABLE(event
->window
);
229 GdkGC
* gc
= gdk_gc_new(drawable
);
230 gdk_gc_set_clip_rectangle(gc
, &event
->area
);
231 gdk_gc_set_rgb_fg_color(gc
, &data
->border_color
);
233 // Stroke the frame border.
234 std::vector
<GdkPoint
> points
= MakeFramePolygonPoints(
236 if (data
->drawn_borders
== BORDER_ALL
) {
237 // If we want to have borders everywhere, we need to draw a polygon instead
238 // of a set of lines.
239 gdk_draw_polygon(drawable
, gc
, FALSE
, &points
[0], points
.size());
240 } else if (!points
.empty()) {
241 gdk_draw_lines(drawable
, gc
, &points
[0], points
.size());
245 return FALSE
; // Propagate so our children paint, etc.
248 // On theme changes, window shapes are reset, but we detect whether we need to
249 // reshape a window by whether its allocation has changed so force it to reset
250 // the window shape on next expose.
251 void OnStyleSet(GtkWidget
* widget
, GtkStyle
* previous_style
) {
253 RoundedWindowData
* data
= static_cast<RoundedWindowData
*>(
254 g_object_get_data(G_OBJECT(widget
), kRoundedData
));
256 data
->expected_width
= -1;
257 data
->expected_height
= -1;
262 void ActAsRoundedWindow(
263 GtkWidget
* widget
, const GdkColor
& color
, int corner_size
,
264 int rounded_edges
, int drawn_borders
) {
266 DCHECK(!g_object_get_data(G_OBJECT(widget
), kRoundedData
));
268 gtk_widget_set_app_paintable(widget
, TRUE
);
270 RoundedWindowData
* data
= new RoundedWindowData
;
271 data
->signals
.Connect(widget
, "expose-event",
272 G_CALLBACK(OnRoundedWindowExpose
), NULL
);
273 data
->signals
.Connect(widget
, "style-set", G_CALLBACK(OnStyleSet
), NULL
);
275 data
->expected_width
= -1;
276 data
->expected_height
= -1;
278 data
->border_color
= color
;
279 data
->corner_size
= corner_size
;
281 data
->rounded_edges
= rounded_edges
;
282 data
->drawn_borders
= drawn_borders
;
284 g_object_set_data_full(G_OBJECT(widget
), kRoundedData
,
285 data
, FreeRoundedWindowData
);
287 if (gtk_widget_get_visible(widget
))
288 gtk_widget_queue_draw(widget
);
291 void StopActingAsRoundedWindow(GtkWidget
* widget
) {
292 g_object_set_data(G_OBJECT(widget
), kRoundedData
, NULL
);
294 if (gtk_widget_get_realized(widget
))
295 gdk_window_shape_combine_mask(gtk_widget_get_window(widget
), NULL
, 0, 0);
297 if (gtk_widget_get_visible(widget
))
298 gtk_widget_queue_draw(widget
);
301 bool IsActingAsRoundedWindow(GtkWidget
* widget
) {
302 return g_object_get_data(G_OBJECT(widget
), kRoundedData
) != NULL
;
305 void SetRoundedWindowEdgesAndBorders(GtkWidget
* widget
,
310 RoundedWindowData
* data
= static_cast<RoundedWindowData
*>(
311 g_object_get_data(G_OBJECT(widget
), kRoundedData
));
313 data
->corner_size
= corner_size
;
314 data
->rounded_edges
= rounded_edges
;
315 data
->drawn_borders
= drawn_borders
;
318 void SetRoundedWindowBorderColor(GtkWidget
* widget
, GdkColor color
) {
320 RoundedWindowData
* data
= static_cast<RoundedWindowData
*>(
321 g_object_get_data(G_OBJECT(widget
), kRoundedData
));
323 data
->border_color
= color
;
326 } // namespace gtk_util