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. 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). 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. 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. 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 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 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 296 bool IsActingAsRoundedWindow(GtkWidget* widget) { 297 return g_object_get_data(G_OBJECT(widget), kRoundedData) != NULL; 298 } 299 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 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