OLD | NEW |
| (Empty) |
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. | |
4 | |
5 #include "chrome/browser/ui/gtk/rounded_window.h" | |
6 | |
7 #include <gtk/gtk.h> | |
8 #include <math.h> | |
9 | |
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" | |
15 | |
16 namespace gtk_util { | |
17 | |
18 namespace { | |
19 | |
20 const char* kRoundedData = "rounded-window-data"; | |
21 | |
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; | |
25 | |
26 struct RoundedWindowData { | |
27 // Expected window size. Used to detect when we need to reshape the window. | |
28 int expected_width; | |
29 int expected_height; | |
30 | |
31 // Color of the border. | |
32 GdkColor border_color; | |
33 | |
34 // Radius of the edges in pixels. | |
35 int corner_size; | |
36 | |
37 // Which corners should be rounded? | |
38 int rounded_edges; | |
39 | |
40 // Which sides of the window should have an internal border? | |
41 int drawn_borders; | |
42 | |
43 // Keeps track of attached signal handlers. | |
44 ui::GtkSignalRegistrar signals; | |
45 }; | |
46 | |
47 // Callback from GTK to release allocated memory. | |
48 void FreeRoundedWindowData(gpointer data) { | |
49 delete static_cast<RoundedWindowData*>(data); | |
50 } | |
51 | |
52 enum FrameType { | |
53 FRAME_MASK, | |
54 FRAME_STROKE, | |
55 }; | |
56 | |
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, | |
61 FrameType type) { | |
62 using gtk_util::MakeBidiGdkPoint; | |
63 int width = data->expected_width; | |
64 int height = data->expected_height; | |
65 int corner_size = data->corner_size; | |
66 | |
67 std::vector<GdkPoint> points; | |
68 | |
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 | |
73 // right for ltr). | |
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; | |
79 | |
80 // Build up points starting with the bottom left corner and continuing | |
81 // clockwise. | |
82 | |
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)))); | |
94 if (x > 0) { | |
95 points.push_back(MakeBidiGdkPoint( | |
96 corner_size - x + x_off_r + 1, | |
97 height - (corner_size - y) + y_off, width, ltr)); | |
98 } | |
99 points.push_back(MakeBidiGdkPoint( | |
100 corner_size - x + x_off_r, | |
101 height - (corner_size - y) + y_off, width, ltr)); | |
102 } | |
103 } else { | |
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)); | |
108 } | |
109 } else { | |
110 points.push_back(MakeBidiGdkPoint(x_off_r, height + y_off, width, ltr)); | |
111 } | |
112 } | |
113 | |
114 // Top left corner. | |
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)); | |
124 if (x > 0) { | |
125 points.push_back(MakeBidiGdkPoint(corner_size - x + 1 + x_off_r, | |
126 corner_size - y, width, ltr)); | |
127 } | |
128 } | |
129 } else { | |
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)); | |
134 } | |
135 } else { | |
136 points.push_back(MakeBidiGdkPoint(x_off_r, 0, width, ltr)); | |
137 } | |
138 } | |
139 | |
140 // Top right corner. | |
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)))); | |
148 if (x > 0) { | |
149 points.push_back(MakeBidiGdkPoint( | |
150 width - (corner_size - x) + x_off_l - 1, | |
151 corner_size - y, width, ltr)); | |
152 } | |
153 points.push_back(MakeBidiGdkPoint( | |
154 width - (corner_size - x) + x_off_l, | |
155 corner_size - y, width, ltr)); | |
156 } | |
157 } else { | |
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)); | |
162 } | |
163 } else { | |
164 points.push_back(MakeBidiGdkPoint( | |
165 width + x_off_l, 0, width, ltr)); | |
166 } | |
167 } | |
168 | |
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)); | |
180 if (x > 0) { | |
181 points.push_back(MakeBidiGdkPoint( | |
182 width - (corner_size - x) + x_off_l - 1, | |
183 height - (corner_size - y) + y_off, width, ltr)); | |
184 } | |
185 } | |
186 } else { | |
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)); | |
191 } | |
192 } else { | |
193 points.push_back(MakeBidiGdkPoint( | |
194 width + x_off_l, height + y_off, width, ltr)); | |
195 } | |
196 } | |
197 | |
198 return points; | |
199 } | |
200 | |
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)); | |
207 | |
208 GtkAllocation allocation; | |
209 gtk_widget_get_allocation(widget, &allocation); | |
210 | |
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; | |
215 | |
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( | |
219 data, FRAME_MASK); | |
220 GdkRegion* mask_region = gdk_region_polygon(&mask_points[0], | |
221 mask_points.size(), | |
222 GDK_EVEN_ODD_RULE); | |
223 gdk_window_shape_combine_region(gtk_widget_get_window(widget), | |
224 mask_region, 0, 0); | |
225 gdk_region_destroy(mask_region); | |
226 } | |
227 | |
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); | |
232 | |
233 // Stroke the frame border. | |
234 std::vector<GdkPoint> points = MakeFramePolygonPoints( | |
235 data, FRAME_STROKE); | |
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()); | |
242 } | |
243 | |
244 g_object_unref(gc); | |
245 return FALSE; // Propagate so our children paint, etc. | |
246 } | |
247 | |
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) { | |
252 DCHECK(widget); | |
253 RoundedWindowData* data = static_cast<RoundedWindowData*>( | |
254 g_object_get_data(G_OBJECT(widget), kRoundedData)); | |
255 DCHECK(data); | |
256 data->expected_width = -1; | |
257 data->expected_height = -1; | |
258 } | |
259 | |
260 } // namespace | |
261 | |
262 void ActAsRoundedWindow( | |
263 GtkWidget* widget, const GdkColor& color, int corner_size, | |
264 int rounded_edges, int drawn_borders) { | |
265 DCHECK(widget); | |
266 DCHECK(!g_object_get_data(G_OBJECT(widget), kRoundedData)); | |
267 | |
268 gtk_widget_set_app_paintable(widget, TRUE); | |
269 | |
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); | |
274 | |
275 data->expected_width = -1; | |
276 data->expected_height = -1; | |
277 | |
278 data->border_color = color; | |
279 data->corner_size = corner_size; | |
280 | |
281 data->rounded_edges = rounded_edges; | |
282 data->drawn_borders = drawn_borders; | |
283 | |
284 g_object_set_data_full(G_OBJECT(widget), kRoundedData, | |
285 data, FreeRoundedWindowData); | |
286 | |
287 if (gtk_widget_get_visible(widget)) | |
288 gtk_widget_queue_draw(widget); | |
289 } | |
290 | |
291 void StopActingAsRoundedWindow(GtkWidget* widget) { | |
292 g_object_set_data(G_OBJECT(widget), kRoundedData, NULL); | |
293 | |
294 if (gtk_widget_get_realized(widget)) | |
295 gdk_window_shape_combine_mask(gtk_widget_get_window(widget), NULL, 0, 0); | |
296 | |
297 if (gtk_widget_get_visible(widget)) | |
298 gtk_widget_queue_draw(widget); | |
299 } | |
300 | |
301 bool IsActingAsRoundedWindow(GtkWidget* widget) { | |
302 return g_object_get_data(G_OBJECT(widget), kRoundedData) != NULL; | |
303 } | |
304 | |
305 void SetRoundedWindowEdgesAndBorders(GtkWidget* widget, | |
306 int corner_size, | |
307 int rounded_edges, | |
308 int drawn_borders) { | |
309 DCHECK(widget); | |
310 RoundedWindowData* data = static_cast<RoundedWindowData*>( | |
311 g_object_get_data(G_OBJECT(widget), kRoundedData)); | |
312 DCHECK(data); | |
313 data->corner_size = corner_size; | |
314 data->rounded_edges = rounded_edges; | |
315 data->drawn_borders = drawn_borders; | |
316 } | |
317 | |
318 void SetRoundedWindowBorderColor(GtkWidget* widget, GdkColor color) { | |
319 DCHECK(widget); | |
320 RoundedWindowData* data = static_cast<RoundedWindowData*>( | |
321 g_object_get_data(G_OBJECT(widget), kRoundedData)); | |
322 DCHECK(data); | |
323 data->border_color = color; | |
324 } | |
325 | |
326 } // namespace gtk_util | |
OLD | NEW |