Home | History | Annotate | Download | only in gtk
      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