• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright (c) 2011 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 
15 namespace gtk_util {
16 
17 namespace {
18 
19 const char* kRoundedData = "rounded-window-data";
20 
21 // If the border radius is less than |kMinRoundedBorderSize|, we don't actually
22 // round the corners, we just truncate the corner.
23 const int kMinRoundedBorderSize = 8;
24 
25 struct RoundedWindowData {
26   // Expected window size. Used to detect when we need to reshape the window.
27   int expected_width;
28   int expected_height;
29 
30   // Color of the border.
31   GdkColor border_color;
32 
33   // Radius of the edges in pixels.
34   int corner_size;
35 
36   // Which corners should be rounded?
37   int rounded_edges;
38 
39   // Which sides of the window should have an internal border?
40   int drawn_borders;
41 
42   // Keeps track of attached signal handlers.
43   ui::GtkSignalRegistrar signals;
44 };
45 
46 // Callback from GTK to release allocated memory.
FreeRoundedWindowData(gpointer data)47 void FreeRoundedWindowData(gpointer data) {
48   delete static_cast<RoundedWindowData*>(data);
49 }
50 
51 enum FrameType {
52   FRAME_MASK,
53   FRAME_STROKE,
54 };
55 
56 // Returns a list of points that either form the outline of the status bubble
57 // (|type| == FRAME_MASK) or form the inner border around the inner edge
58 // (|type| == FRAME_STROKE).
MakeFramePolygonPoints(RoundedWindowData * data,FrameType type)59 std::vector<GdkPoint> MakeFramePolygonPoints(RoundedWindowData* data,
60                                              FrameType type) {
61   using gtk_util::MakeBidiGdkPoint;
62   int width = data->expected_width;
63   int height = data->expected_height;
64   int corner_size = data->corner_size;
65 
66   std::vector<GdkPoint> points;
67 
68   bool ltr = !base::i18n::IsRTL();
69   // If we have a stroke, we have to offset some of our points by 1 pixel.
70   // We have to inset by 1 pixel when we draw horizontal lines that are on the
71   // bottom or when we draw vertical lines that are closer to the end (end is
72   // right for ltr).
73   int y_off = (type == FRAME_MASK) ? 0 : -1;
74   // We use this one for LTR.
75   int x_off_l = ltr ? y_off : 0;
76   // We use this one for RTL.
77   int x_off_r = !ltr ? -y_off : 0;
78 
79   // Build up points starting with the bottom left corner and continuing
80   // clockwise.
81 
82   // Bottom left corner.
83   if (type == FRAME_MASK ||
84       (data->drawn_borders & (BORDER_LEFT | BORDER_BOTTOM))) {
85     if (data->rounded_edges & ROUNDED_BOTTOM_LEFT) {
86       if (corner_size >= kMinRoundedBorderSize) {
87         // We are careful to only add points that are horizontal or vertically
88         // offset from the previous point (not both).  This avoids rounding
89         // differences when two points are connected.
90         for (int x = 0; x <= corner_size; ++x) {
91           int y = static_cast<int>(sqrt(static_cast<double>(
92               (corner_size * corner_size) - (x * x))));
93           if (x > 0) {
94             points.push_back(MakeBidiGdkPoint(
95                 corner_size - x + x_off_r + 1,
96                 height - (corner_size - y) + y_off, width, ltr));
97           }
98           points.push_back(MakeBidiGdkPoint(
99               corner_size - x + x_off_r,
100               height - (corner_size - y) + y_off, width, ltr));
101         }
102       } else {
103         points.push_back(MakeBidiGdkPoint(
104             corner_size + x_off_l, height + y_off, width, ltr));
105         points.push_back(MakeBidiGdkPoint(
106             x_off_r, height - corner_size, width, ltr));
107       }
108     } else {
109       points.push_back(MakeBidiGdkPoint(x_off_r, height + y_off, width, ltr));
110     }
111   }
112 
113   // Top left corner.
114   if (type == FRAME_MASK ||
115       (data->drawn_borders & (BORDER_LEFT | BORDER_TOP))) {
116     if (data->rounded_edges & ROUNDED_TOP_LEFT) {
117       if (corner_size >= kMinRoundedBorderSize) {
118         for (int x = corner_size; x >= 0; --x) {
119           int y = static_cast<int>(sqrt(static_cast<double>(
120               (corner_size * corner_size) - (x * x))));
121           points.push_back(MakeBidiGdkPoint(corner_size - x + x_off_r,
122               corner_size - y, width, ltr));
123           if (x > 0) {
124             points.push_back(MakeBidiGdkPoint(corner_size - x + 1 + x_off_r,
125                 corner_size - y, width, ltr));
126           }
127         }
128       } else {
129         points.push_back(MakeBidiGdkPoint(
130             x_off_r, corner_size - 1, width, ltr));
131         points.push_back(MakeBidiGdkPoint(
132             corner_size + x_off_r - 1, 0, width, ltr));
133       }
134     } else {
135       points.push_back(MakeBidiGdkPoint(x_off_r, 0, width, ltr));
136     }
137   }
138 
139   // Top right corner.
140   if (type == FRAME_MASK ||
141       (data->drawn_borders & (BORDER_TOP | BORDER_RIGHT))) {
142     if (data->rounded_edges & ROUNDED_TOP_RIGHT) {
143       if (corner_size >= kMinRoundedBorderSize) {
144         for (int x = 0; x <= corner_size; ++x) {
145           int y = static_cast<int>(sqrt(static_cast<double>(
146               (corner_size * corner_size) - (x * x))));
147           if (x > 0) {
148             points.push_back(MakeBidiGdkPoint(
149                 width - (corner_size - x) + x_off_l - 1,
150                 corner_size - y, width, ltr));
151           }
152           points.push_back(MakeBidiGdkPoint(
153               width - (corner_size - x) + x_off_l,
154               corner_size - y, width, ltr));
155         }
156       } else {
157         points.push_back(MakeBidiGdkPoint(
158             width - corner_size + 1 + x_off_l, 0, width, ltr));
159         points.push_back(MakeBidiGdkPoint(
160             width + x_off_l, corner_size - 1, width, ltr));
161       }
162     } else {
163       points.push_back(MakeBidiGdkPoint(
164           width + x_off_l, 0, width, ltr));
165     }
166   }
167 
168   // Bottom right corner.
169   if (type == FRAME_MASK ||
170       (data->drawn_borders & (BORDER_RIGHT | BORDER_BOTTOM))) {
171     if (data->rounded_edges & ROUNDED_BOTTOM_RIGHT) {
172       if (corner_size >= kMinRoundedBorderSize) {
173         for (int x = corner_size; x >= 0; --x) {
174           int y = static_cast<int>(sqrt(static_cast<double>(
175               (corner_size * corner_size) - (x * x))));
176           points.push_back(MakeBidiGdkPoint(
177               width - (corner_size - x) + x_off_l,
178               height - (corner_size - y) + y_off, width, ltr));
179           if (x > 0) {
180             points.push_back(MakeBidiGdkPoint(
181                 width - (corner_size - x) + x_off_l - 1,
182                 height - (corner_size - y) + y_off, width, ltr));
183           }
184         }
185       } else {
186         points.push_back(MakeBidiGdkPoint(
187             width + x_off_l, height - corner_size, width, ltr));
188         points.push_back(MakeBidiGdkPoint(
189             width - corner_size + x_off_r, height + y_off, width, ltr));
190       }
191     } else {
192       points.push_back(MakeBidiGdkPoint(
193           width + x_off_l, height + y_off, width, ltr));
194     }
195   }
196 
197   return points;
198 }
199 
200 // Set the window shape in needed, lets our owner do some drawing (if it wants
201 // to), and finally draw the border.
OnRoundedWindowExpose(GtkWidget * widget,GdkEventExpose * event)202 gboolean OnRoundedWindowExpose(GtkWidget* widget,
203                                GdkEventExpose* event) {
204   RoundedWindowData* data = static_cast<RoundedWindowData*>(
205       g_object_get_data(G_OBJECT(widget), kRoundedData));
206 
207   if (data->expected_width != widget->allocation.width ||
208       data->expected_height != widget->allocation.height) {
209     data->expected_width = widget->allocation.width;
210     data->expected_height = widget->allocation.height;
211 
212     // We need to update the shape of the status bubble whenever our GDK
213     // window changes shape.
214     std::vector<GdkPoint> mask_points = MakeFramePolygonPoints(
215         data, FRAME_MASK);
216     GdkRegion* mask_region = gdk_region_polygon(&mask_points[0],
217                                                 mask_points.size(),
218                                                 GDK_EVEN_ODD_RULE);
219     gdk_window_shape_combine_region(widget->window, mask_region, 0, 0);
220     gdk_region_destroy(mask_region);
221   }
222 
223   GdkDrawable* drawable = GDK_DRAWABLE(event->window);
224   GdkGC* gc = gdk_gc_new(drawable);
225   gdk_gc_set_clip_rectangle(gc, &event->area);
226   gdk_gc_set_rgb_fg_color(gc, &data->border_color);
227 
228   // Stroke the frame border.
229   std::vector<GdkPoint> points = MakeFramePolygonPoints(
230       data, FRAME_STROKE);
231   if (data->drawn_borders == BORDER_ALL) {
232     // If we want to have borders everywhere, we need to draw a polygon instead
233     // of a set of lines.
234     gdk_draw_polygon(drawable, gc, FALSE, &points[0], points.size());
235   } else if (!points.empty()) {
236     gdk_draw_lines(drawable, gc, &points[0], points.size());
237   }
238 
239   g_object_unref(gc);
240   return FALSE;  // Propagate so our children paint, etc.
241 }
242 
243 // On theme changes, window shapes are reset, but we detect whether we need to
244 // reshape a window by whether its allocation has changed so force it to reset
245 // the window shape on next expose.
OnStyleSet(GtkWidget * widget,GtkStyle * previous_style)246 void OnStyleSet(GtkWidget* widget, GtkStyle* previous_style) {
247   DCHECK(widget);
248   RoundedWindowData* data = static_cast<RoundedWindowData*>(
249       g_object_get_data(G_OBJECT(widget), kRoundedData));
250   DCHECK(data);
251   data->expected_width = -1;
252   data->expected_height = -1;
253 }
254 
255 }  // namespace
256 
ActAsRoundedWindow(GtkWidget * widget,const GdkColor & color,int corner_size,int rounded_edges,int drawn_borders)257 void ActAsRoundedWindow(
258     GtkWidget* widget, const GdkColor& color, int corner_size,
259     int rounded_edges, int drawn_borders) {
260   DCHECK(widget);
261   DCHECK(!g_object_get_data(G_OBJECT(widget), kRoundedData));
262 
263   gtk_widget_set_app_paintable(widget, TRUE);
264 
265   RoundedWindowData* data = new RoundedWindowData;
266   data->signals.Connect(widget, "expose-event",
267                         G_CALLBACK(OnRoundedWindowExpose), NULL);
268   data->signals.Connect(widget, "style-set", G_CALLBACK(OnStyleSet), NULL);
269 
270   data->expected_width = -1;
271   data->expected_height = -1;
272 
273   data->border_color = color;
274   data->corner_size = corner_size;
275 
276   data->rounded_edges = rounded_edges;
277   data->drawn_borders = drawn_borders;
278 
279   g_object_set_data_full(G_OBJECT(widget), kRoundedData,
280                          data, FreeRoundedWindowData);
281 
282   if (GTK_WIDGET_VISIBLE(widget))
283     gtk_widget_queue_draw(widget);
284 }
285 
StopActingAsRoundedWindow(GtkWidget * widget)286 void StopActingAsRoundedWindow(GtkWidget* widget) {
287   g_object_set_data(G_OBJECT(widget), kRoundedData, NULL);
288 
289   if (GTK_WIDGET_REALIZED(widget))
290     gdk_window_shape_combine_mask(widget->window, NULL, 0, 0);
291 
292   if (GTK_WIDGET_VISIBLE(widget))
293     gtk_widget_queue_draw(widget);
294 }
295 
IsActingAsRoundedWindow(GtkWidget * widget)296 bool IsActingAsRoundedWindow(GtkWidget* widget) {
297   return g_object_get_data(G_OBJECT(widget), kRoundedData) != NULL;
298 }
299 
SetRoundedWindowEdgesAndBorders(GtkWidget * widget,int corner_size,int rounded_edges,int drawn_borders)300 void SetRoundedWindowEdgesAndBorders(GtkWidget* widget,
301                                      int corner_size,
302                                      int rounded_edges,
303                                      int drawn_borders) {
304   DCHECK(widget);
305   RoundedWindowData* data = static_cast<RoundedWindowData*>(
306       g_object_get_data(G_OBJECT(widget), kRoundedData));
307   DCHECK(data);
308   data->corner_size = corner_size;
309   data->rounded_edges = rounded_edges;
310   data->drawn_borders = drawn_borders;
311 }
312 
SetRoundedWindowBorderColor(GtkWidget * widget,GdkColor color)313 void SetRoundedWindowBorderColor(GtkWidget* widget, GdkColor color) {
314   DCHECK(widget);
315   RoundedWindowData* data = static_cast<RoundedWindowData*>(
316       g_object_get_data(G_OBJECT(widget), kRoundedData));
317   DCHECK(data);
318   data->border_color = color;
319 }
320 
321 }  // namespace gtk_util
322