summaryrefslogtreecommitdiffstats
path: root/chrome/browser/gtk/rounded_window.cc
diff options
context:
space:
mode:
Diffstat (limited to 'chrome/browser/gtk/rounded_window.cc')
-rw-r--r--chrome/browser/gtk/rounded_window.cc321
1 files changed, 321 insertions, 0 deletions
diff --git a/chrome/browser/gtk/rounded_window.cc b/chrome/browser/gtk/rounded_window.cc
new file mode 100644
index 0000000..053f9ad
--- /dev/null
+++ b/chrome/browser/gtk/rounded_window.cc
@@ -0,0 +1,321 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/gtk/rounded_window.h"
+
+#include <gtk/gtk.h>
+#include <math.h>
+
+#include "app/gtk_signal.h"
+#include "app/gtk_signal_registrar.h"
+#include "base/i18n/rtl.h"
+#include "chrome/browser/gtk/gtk_util.h"
+
+namespace gtk_util {
+
+namespace {
+
+const char* kRoundedData = "rounded-window-data";
+
+// If the border radius is less than |kMinRoundedBorderSize|, we don't actually
+// round the corners, we just truncate the corner.
+const int kMinRoundedBorderSize = 8;
+
+struct RoundedWindowData {
+ // Expected window size. Used to detect when we need to reshape the window.
+ int expected_width;
+ int expected_height;
+
+ // Color of the border.
+ GdkColor border_color;
+
+ // Radius of the edges in pixels.
+ int corner_size;
+
+ // Which corners should be rounded?
+ int rounded_edges;
+
+ // Which sides of the window should have an internal border?
+ int drawn_borders;
+
+ // Keeps track of attached signal handlers.
+ GtkSignalRegistrar signals;
+};
+
+// Callback from GTK to release allocated memory.
+void FreeRoundedWindowData(gpointer data) {
+ delete static_cast<RoundedWindowData*>(data);
+}
+
+enum FrameType {
+ FRAME_MASK,
+ FRAME_STROKE,
+};
+
+// Returns a list of points that either form the outline of the status bubble
+// (|type| == FRAME_MASK) or form the inner border around the inner edge
+// (|type| == FRAME_STROKE).
+std::vector<GdkPoint> MakeFramePolygonPoints(RoundedWindowData* data,
+ FrameType type) {
+ using gtk_util::MakeBidiGdkPoint;
+ int width = data->expected_width;
+ int height = data->expected_height;
+ int corner_size = data->corner_size;
+
+ std::vector<GdkPoint> points;
+
+ bool ltr = !base::i18n::IsRTL();
+ // If we have a stroke, we have to offset some of our points by 1 pixel.
+ // We have to inset by 1 pixel when we draw horizontal lines that are on the
+ // bottom or when we draw vertical lines that are closer to the end (end is
+ // right for ltr).
+ int y_off = (type == FRAME_MASK) ? 0 : -1;
+ // We use this one for LTR.
+ int x_off_l = ltr ? y_off : 0;
+ // We use this one for RTL.
+ int x_off_r = !ltr ? -y_off : 0;
+
+ // Build up points starting with the bottom left corner and continuing
+ // clockwise.
+
+ // Bottom left corner.
+ if (type == FRAME_MASK ||
+ (data->drawn_borders & (BORDER_LEFT | BORDER_BOTTOM))) {
+ if (data->rounded_edges & ROUNDED_BOTTOM_LEFT) {
+ if (corner_size >= kMinRoundedBorderSize) {
+ // We are careful to only add points that are horizontal or vertically
+ // offset from the previous point (not both). This avoids rounding
+ // differences when two points are connected.
+ for (int x = 0; x <= corner_size; ++x) {
+ int y = static_cast<int>(sqrt(static_cast<double>(
+ (corner_size * corner_size) - (x * x))));
+ if (x > 0) {
+ points.push_back(MakeBidiGdkPoint(
+ corner_size - x + x_off_r + 1,
+ height - (corner_size - y) + y_off, width, ltr));
+ }
+ points.push_back(MakeBidiGdkPoint(
+ corner_size - x + x_off_r,
+ height - (corner_size - y) + y_off, width, ltr));
+ }
+ } else {
+ points.push_back(MakeBidiGdkPoint(
+ corner_size + x_off_l, height + y_off, width, ltr));
+ points.push_back(MakeBidiGdkPoint(
+ x_off_r, height - corner_size, width, ltr));
+ }
+ } else {
+ points.push_back(MakeBidiGdkPoint(x_off_r, height + y_off, width, ltr));
+ }
+ }
+
+ // Top left corner.
+ if (type == FRAME_MASK ||
+ (data->drawn_borders & (BORDER_LEFT | BORDER_TOP))) {
+ if (data->rounded_edges & ROUNDED_TOP_LEFT) {
+ if (corner_size >= kMinRoundedBorderSize) {
+ for (int x = corner_size; x >= 0; --x) {
+ int y = static_cast<int>(sqrt(static_cast<double>(
+ (corner_size * corner_size) - (x * x))));
+ points.push_back(MakeBidiGdkPoint(corner_size - x + x_off_r,
+ corner_size - y, width, ltr));
+ if (x > 0) {
+ points.push_back(MakeBidiGdkPoint(corner_size - x + 1 + x_off_r,
+ corner_size - y, width, ltr));
+ }
+ }
+ } else {
+ points.push_back(MakeBidiGdkPoint(
+ x_off_r, corner_size - 1, width, ltr));
+ points.push_back(MakeBidiGdkPoint(
+ corner_size + x_off_r - 1, 0, width, ltr));
+ }
+ } else {
+ points.push_back(MakeBidiGdkPoint(x_off_r, 0, width, ltr));
+ }
+ }
+
+ // Top right corner.
+ if (type == FRAME_MASK ||
+ (data->drawn_borders & (BORDER_TOP | BORDER_RIGHT))) {
+ if (data->rounded_edges & ROUNDED_TOP_RIGHT) {
+ if (corner_size >= kMinRoundedBorderSize) {
+ for (int x = 0; x <= corner_size; ++x) {
+ int y = static_cast<int>(sqrt(static_cast<double>(
+ (corner_size * corner_size) - (x * x))));
+ if (x > 0) {
+ points.push_back(MakeBidiGdkPoint(
+ width - (corner_size - x) + x_off_l - 1,
+ corner_size - y, width, ltr));
+ }
+ points.push_back(MakeBidiGdkPoint(
+ width - (corner_size - x) + x_off_l,
+ corner_size - y, width, ltr));
+ }
+ } else {
+ points.push_back(MakeBidiGdkPoint(
+ width - corner_size + 1 + x_off_l, 0, width, ltr));
+ points.push_back(MakeBidiGdkPoint(
+ width + x_off_l, corner_size - 1, width, ltr));
+ }
+ } else {
+ points.push_back(MakeBidiGdkPoint(
+ width + x_off_l, 0, width, ltr));
+ }
+ }
+
+ // Bottom right corner.
+ if (type == FRAME_MASK ||
+ (data->drawn_borders & (BORDER_RIGHT | BORDER_BOTTOM))) {
+ if (data->rounded_edges & ROUNDED_BOTTOM_RIGHT) {
+ if (corner_size >= kMinRoundedBorderSize) {
+ for (int x = corner_size; x >= 0; --x) {
+ int y = static_cast<int>(sqrt(static_cast<double>(
+ (corner_size * corner_size) - (x * x))));
+ points.push_back(MakeBidiGdkPoint(
+ width - (corner_size - x) + x_off_l,
+ height - (corner_size - y) + y_off, width, ltr));
+ if (x > 0) {
+ points.push_back(MakeBidiGdkPoint(
+ width - (corner_size - x) + x_off_l - 1,
+ height - (corner_size - y) + y_off, width, ltr));
+ }
+ }
+ } else {
+ points.push_back(MakeBidiGdkPoint(
+ width + x_off_l, height - corner_size, width, ltr));
+ points.push_back(MakeBidiGdkPoint(
+ width - corner_size + x_off_r, height + y_off, width, ltr));
+ }
+ } else {
+ points.push_back(MakeBidiGdkPoint(
+ width + x_off_l, height + y_off, width, ltr));
+ }
+ }
+
+ return points;
+}
+
+// Set the window shape in needed, lets our owner do some drawing (if it wants
+// to), and finally draw the border.
+gboolean OnRoundedWindowExpose(GtkWidget* widget,
+ GdkEventExpose* event) {
+ RoundedWindowData* data = static_cast<RoundedWindowData*>(
+ g_object_get_data(G_OBJECT(widget), kRoundedData));
+
+ if (data->expected_width != widget->allocation.width ||
+ data->expected_height != widget->allocation.height) {
+ data->expected_width = widget->allocation.width;
+ data->expected_height = widget->allocation.height;
+
+ // We need to update the shape of the status bubble whenever our GDK
+ // window changes shape.
+ std::vector<GdkPoint> mask_points = MakeFramePolygonPoints(
+ data, FRAME_MASK);
+ GdkRegion* mask_region = gdk_region_polygon(&mask_points[0],
+ mask_points.size(),
+ GDK_EVEN_ODD_RULE);
+ gdk_window_shape_combine_region(widget->window, mask_region, 0, 0);
+ gdk_region_destroy(mask_region);
+ }
+
+ GdkDrawable* drawable = GDK_DRAWABLE(event->window);
+ GdkGC* gc = gdk_gc_new(drawable);
+ gdk_gc_set_clip_rectangle(gc, &event->area);
+ gdk_gc_set_rgb_fg_color(gc, &data->border_color);
+
+ // Stroke the frame border.
+ std::vector<GdkPoint> points = MakeFramePolygonPoints(
+ data, FRAME_STROKE);
+ if (data->drawn_borders == BORDER_ALL) {
+ // If we want to have borders everywhere, we need to draw a polygon instead
+ // of a set of lines.
+ gdk_draw_polygon(drawable, gc, FALSE, &points[0], points.size());
+ } else if (points.size() > 0) {
+ gdk_draw_lines(drawable, gc, &points[0], points.size());
+ }
+
+ g_object_unref(gc);
+ return FALSE; // Propagate so our children paint, etc.
+}
+
+// On theme changes, window shapes are reset, but we detect whether we need to
+// reshape a window by whether its allocation has changed so force it to reset
+// the window shape on next expose.
+void OnStyleSet(GtkWidget* widget, GtkStyle* previous_style) {
+ DCHECK(widget);
+ RoundedWindowData* data = static_cast<RoundedWindowData*>(
+ g_object_get_data(G_OBJECT(widget), kRoundedData));
+ DCHECK(data);
+ data->expected_width = -1;
+ data->expected_height = -1;
+}
+
+} // namespace
+
+void ActAsRoundedWindow(
+ GtkWidget* widget, const GdkColor& color, int corner_size,
+ int rounded_edges, int drawn_borders) {
+ DCHECK(widget);
+ DCHECK(!g_object_get_data(G_OBJECT(widget), kRoundedData));
+
+ gtk_widget_set_app_paintable(widget, TRUE);
+
+ RoundedWindowData* data = new RoundedWindowData;
+ data->signals.Connect(widget, "expose-event",
+ G_CALLBACK(OnRoundedWindowExpose), NULL);
+ data->signals.Connect(widget, "style-set", G_CALLBACK(OnStyleSet), NULL);
+
+ data->expected_width = -1;
+ data->expected_height = -1;
+
+ data->border_color = color;
+ data->corner_size = corner_size;
+
+ data->rounded_edges = rounded_edges;
+ data->drawn_borders = drawn_borders;
+
+ g_object_set_data_full(G_OBJECT(widget), kRoundedData,
+ data, FreeRoundedWindowData);
+
+ if (GTK_WIDGET_VISIBLE(widget))
+ gtk_widget_queue_draw(widget);
+}
+
+void StopActingAsRoundedWindow(GtkWidget* widget) {
+ g_object_set_data(G_OBJECT(widget), kRoundedData, NULL);
+
+ if (GTK_WIDGET_REALIZED(widget))
+ gdk_window_shape_combine_mask(widget->window, NULL, 0, 0);
+
+ if (GTK_WIDGET_VISIBLE(widget))
+ gtk_widget_queue_draw(widget);
+}
+
+bool IsActingAsRoundedWindow(GtkWidget* widget) {
+ return g_object_get_data(G_OBJECT(widget), kRoundedData) != NULL;
+}
+
+void SetRoundedWindowEdgesAndBorders(GtkWidget* widget,
+ int corner_size,
+ int rounded_edges,
+ int drawn_borders) {
+ DCHECK(widget);
+ RoundedWindowData* data = static_cast<RoundedWindowData*>(
+ g_object_get_data(G_OBJECT(widget), kRoundedData));
+ DCHECK(data);
+ data->corner_size = corner_size;
+ data->rounded_edges = rounded_edges;
+ data->drawn_borders = drawn_borders;
+}
+
+void SetRoundedWindowBorderColor(GtkWidget* widget, GdkColor color) {
+ DCHECK(widget);
+ RoundedWindowData* data = static_cast<RoundedWindowData*>(
+ g_object_get_data(G_OBJECT(widget), kRoundedData));
+ DCHECK(data);
+ data->border_color = color;
+}
+
+} // namespace gtk_util