summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorkarandeepb <karandeepb@chromium.org>2016-02-16 16:44:39 -0800
committerCommit bot <commit-bot@chromium.org>2016-02-17 00:45:30 +0000
commit675dee76d8e2d0d1d844665147c45f7c4468c7be (patch)
tree53b1715193c9592b2956ce88fda1f3868bc9134a
parent365cf04600cd4126bc3fa6e62820d4d712f959ad (diff)
downloadchromium_src-675dee76d8e2d0d1d844665147c45f7c4468c7be.zip
chromium_src-675dee76d8e2d0d1d844665147c45f7c4468c7be.tar.gz
chromium_src-675dee76d8e2d0d1d844665147c45f7c4468c7be.tar.bz2
MacViews: Add native drop shadow to dialogs on OSX 10.9.
On OSX 10.10 and later, the Window manager is able to gather shadow information from the alpha channel of the composited layer, but this isn't supported on OSX 10.9. This CL adds native shadows to OSX 10.9 by using a strategy similar to ChromeMac's -[ConstrainedWindowCustomWindow drawRect:]. This involves giving the window an opaque background and drawing over the edges with `clear` color to make the corners round. To do this, implement path_mac.mm which converts Skia Paths to NSBezierPath. This is similar to the methods in path_win.cc and path_x11.cc. On Mac, hit testing masks are inferred from the alpha channel. Using the window manager to draw the shadow ensures we get the shadow corresponding to the alpha channel, and removes the need to use views::BubbleWindowTargeter. Since, native shadows are now supported, this CL also removes raster shadows on all Mac Versions by setting all BubbleBorders on Mac to NO_ASSETS. BUG=543671, 507965 Review URL: https://codereview.chromium.org/1633403002 Cr-Commit-Position: refs/heads/master@{#375729}
-rw-r--r--ui/gfx/BUILD.gn3
-rw-r--r--ui/gfx/gfx.gyp2
-rw-r--r--ui/gfx/gfx_tests.gyp1
-rw-r--r--ui/gfx/path_mac.h21
-rw-r--r--ui/gfx/path_mac.mm125
-rw-r--r--ui/gfx/path_mac_unittest.mm258
-rw-r--r--ui/views/bubble/bubble_border.cc45
-rw-r--r--ui/views/bubble/bubble_border.h12
-rw-r--r--ui/views/bubble/bubble_frame_view.cc40
-rw-r--r--ui/views/cocoa/bridged_content_view.h6
-rw-r--r--ui/views/cocoa/bridged_content_view.mm78
-rw-r--r--ui/views/cocoa/bridged_native_widget.mm15
-rw-r--r--ui/views/window/dialog_delegate.cc5
13 files changed, 578 insertions, 33 deletions
diff --git a/ui/gfx/BUILD.gn b/ui/gfx/BUILD.gn
index a2edb90..114d3b0 100644
--- a/ui/gfx/BUILD.gn
+++ b/ui/gfx/BUILD.gn
@@ -145,6 +145,8 @@ component("gfx") {
"nine_image_painter.h",
"path.cc",
"path.h",
+ "path_mac.h",
+ "path_mac.mm",
"path_win.cc",
"path_win.h",
"path_x11.cc",
@@ -692,6 +694,7 @@ test("gfx_unittests") {
"image/image_util_unittest.cc",
"mac/coordinate_conversion_unittest.mm",
"nine_image_painter_unittest.cc",
+ "path_mac_unittest.mm",
"platform_font_mac_unittest.mm",
"range/range_mac_unittest.mm",
"range/range_unittest.cc",
diff --git a/ui/gfx/gfx.gyp b/ui/gfx/gfx.gyp
index a9c20d8..e211997 100644
--- a/ui/gfx/gfx.gyp
+++ b/ui/gfx/gfx.gyp
@@ -233,6 +233,8 @@
'paint_throbber.h',
'path.cc',
'path.h',
+ 'path_mac.h',
+ 'path_mac.mm',
'path_win.cc',
'path_win.h',
'path_x11.cc',
diff --git a/ui/gfx/gfx_tests.gyp b/ui/gfx/gfx_tests.gyp
index 6c72699..bd6d14c 100644
--- a/ui/gfx/gfx_tests.gyp
+++ b/ui/gfx/gfx_tests.gyp
@@ -63,6 +63,7 @@
'image/image_util_unittest.cc',
'mac/coordinate_conversion_unittest.mm',
'nine_image_painter_unittest.cc',
+ 'path_mac_unittest.mm',
'platform_font_linux_unittest.cc',
'platform_font_mac_unittest.mm',
'range/range_mac_unittest.mm',
diff --git a/ui/gfx/path_mac.h b/ui/gfx/path_mac.h
new file mode 100644
index 0000000..f20fa62
--- /dev/null
+++ b/ui/gfx/path_mac.h
@@ -0,0 +1,21 @@
+// Copyright 2016 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.
+
+#ifndef UI_GFX_PATH_MAC_H_
+#define UI_GFX_PATH_MAC_H_
+
+#include "ui/gfx/gfx_export.h"
+
+@class NSBezierPath;
+class SkPath;
+
+namespace gfx {
+
+// Returns an autoreleased NSBezierPath corresponding to |path|. Caller should
+// call retain on the returned object, if it wishes to take ownership.
+GFX_EXPORT NSBezierPath* CreateNSBezierPathFromSkPath(const SkPath& path);
+
+} // namespace gfx
+
+#endif // UI_GFX_PATH_MAC_H_
diff --git a/ui/gfx/path_mac.mm b/ui/gfx/path_mac.mm
new file mode 100644
index 0000000..109f807
--- /dev/null
+++ b/ui/gfx/path_mac.mm
@@ -0,0 +1,125 @@
+// Copyright 2016 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.
+
+#import "ui/gfx/path_mac.h"
+
+#import <Cocoa/Cocoa.h>
+
+#include "third_party/skia/include/core/SkRegion.h"
+#include "ui/gfx/path.h"
+
+namespace {
+
+// Convert a quadratic bezier curve to a cubic bezier curve. Based on the
+// implementation of the private SkConvertQuadToCubic method inside Skia.
+void ConvertQuadToCubicBezier(NSPoint quad[3], NSPoint cubic[4]) {
+ // The resultant cubic will have the same endpoints.
+ cubic[0] = quad[0];
+ cubic[3] = quad[2];
+
+ const double scale = 2.0 / 3.0;
+
+ cubic[1].x = quad[0].x + scale * (quad[1].x - quad[0].x);
+ cubic[1].y = quad[0].y + scale * (quad[1].y - quad[0].y);
+
+ cubic[2].x = quad[2].x + scale * (quad[1].x - quad[2].x);
+ cubic[2].y = quad[2].y + scale * (quad[1].y - quad[2].y);
+}
+
+} // namespace
+
+namespace gfx {
+
+NSBezierPath* CreateNSBezierPathFromSkPath(const SkPath& path) {
+ NSBezierPath* result = [NSBezierPath bezierPath];
+ SkPath::RawIter iter(path);
+ SkPoint sk_points[4] = {{0.0}};
+ SkPath::Verb verb;
+ NSPoint points[4];
+ while ((verb = iter.next(sk_points)) != SkPath::kDone_Verb) {
+ for (size_t i = 0; i < arraysize(points); i++)
+ points[i] = NSMakePoint(sk_points[i].x(), sk_points[i].y());
+
+ switch (verb) {
+ case SkPath::kMove_Verb: {
+ [result moveToPoint:points[0]];
+ break;
+ }
+ case SkPath::kLine_Verb: {
+ DCHECK(NSEqualPoints([result currentPoint], points[0]));
+ [result lineToPoint:points[1]];
+ break;
+ }
+ case SkPath::kQuad_Verb: {
+ DCHECK(NSEqualPoints([result currentPoint], points[0]));
+ NSPoint quad[] = {points[0], points[1], points[2]};
+ // NSBezierPath does not support quadratic bezier curves. Hence convert
+ // to cubic bezier curve.
+ ConvertQuadToCubicBezier(quad, points);
+ [result curveToPoint:points[3]
+ controlPoint1:points[1]
+ controlPoint2:points[2]];
+ break;
+ }
+ case SkPath::kConic_Verb: {
+ DCHECK(NSEqualPoints([result currentPoint], points[0]));
+ // Approximate with quads. Use two for now, increase if more precision
+ // is needed.
+ const size_t kSubdivisionLevels = 1;
+ const size_t kQuadCount = 1 << kSubdivisionLevels;
+ // The quads will share endpoints, so we need one more point than twice
+ // the number of quads.
+ const size_t kPointCount = 1 + 2 * kQuadCount;
+ SkPoint quads[kPointCount];
+ SkPath::ConvertConicToQuads(sk_points[0], sk_points[1], sk_points[2],
+ iter.conicWeight(), quads,
+ kSubdivisionLevels);
+ NSPoint ns_quads[kPointCount];
+ for (size_t i = 0; i < kPointCount; i++)
+ ns_quads[i] = NSMakePoint(quads[i].x(), quads[i].y());
+
+ for (size_t i = 0; i < kQuadCount; i++) {
+ NSPoint quad[] = {ns_quads[2 * i], ns_quads[2 * i + 1],
+ ns_quads[2 * i + 2]};
+ ConvertQuadToCubicBezier(quad, points);
+ DCHECK(NSEqualPoints([result currentPoint], points[0]));
+ [result curveToPoint:points[3]
+ controlPoint1:points[1]
+ controlPoint2:points[2]];
+ }
+ break;
+ }
+ case SkPath::kCubic_Verb: {
+ DCHECK(NSEqualPoints([result currentPoint], points[0]));
+ [result curveToPoint:points[3]
+ controlPoint1:points[1]
+ controlPoint2:points[2]];
+ break;
+ }
+ case SkPath::kClose_Verb: {
+ [result closePath];
+ break;
+ }
+ default: { NOTREACHED(); }
+ }
+ }
+
+ // Set up the fill type.
+ switch (path.getFillType()) {
+ case SkPath::kWinding_FillType:
+ [result setWindingRule:NSNonZeroWindingRule];
+ break;
+ case SkPath::kEvenOdd_FillType:
+ [result setWindingRule:NSEvenOddWindingRule];
+ break;
+ case SkPath::kInverseWinding_FillType:
+ case SkPath::kInverseEvenOdd_FillType:
+ NOTREACHED() << "NSBezierCurve does not support inverse fill types.";
+ break;
+ }
+
+ return result;
+}
+
+} // namespace gfx
diff --git a/ui/gfx/path_mac_unittest.mm b/ui/gfx/path_mac_unittest.mm
new file mode 100644
index 0000000..d1d4a99
--- /dev/null
+++ b/ui/gfx/path_mac_unittest.mm
@@ -0,0 +1,258 @@
+// Copyright 2016 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.
+
+#import "ui/gfx/path_mac.h"
+
+#include <cmath>
+#include <vector>
+
+#import <Cocoa/Cocoa.h>
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/skia/include/core/SkRegion.h"
+#include "ui/gfx/geometry/rect.h"
+#include "ui/gfx/geometry/rect_conversions.h"
+#include "ui/gfx/path.h"
+
+namespace gfx {
+
+namespace {
+
+// Returns the point at a distance of |radius| from the point (|centre_x|,
+// |centre_y|), and angle |degrees| from the positive horizontal axis, measured
+// anti-clockwise.
+NSPoint GetRadialPoint(double radius,
+ double degrees,
+ double centre_x,
+ double centre_y) {
+ const double radian = (degrees * SK_ScalarPI) / 180;
+ return NSMakePoint(centre_x + radius * std::cos(radian),
+ centre_y + radius * std::sin(radian));
+}
+
+// Returns the area of a circle with the given |radius|.
+double CalculateCircleArea(double radius) {
+ return SK_ScalarPI * radius * radius;
+}
+
+// Returns the area of a simple polygon. |path| should represent a simple
+// polygon.
+double CalculatePolygonArea(NSBezierPath* path) {
+ // If path represents a single polygon, it will have MoveTo, followed by
+ // multiple LineTo, followed By ClosePath, followed by another MoveTo
+ // NSBezierPathElement.
+ const size_t element_count = [path elementCount];
+ NSPoint points[3];
+ std::vector<NSPoint> poly;
+
+ for (size_t i = 0; i < element_count - 1; i++) {
+ NSBezierPathElement element =
+ [path elementAtIndex:i associatedPoints:points];
+ poly.push_back(points[0]);
+ DCHECK_EQ(element,
+ i ? (i == element_count - 2 ? NSClosePathBezierPathElement
+ : NSLineToBezierPathElement)
+ : NSMoveToBezierPathElement);
+ }
+ DCHECK_EQ([path elementAtIndex:element_count - 1], NSMoveToBezierPathElement);
+
+ // Shoelace Algorithm to find the area of a simple polygon.
+ DCHECK(NSEqualPoints(poly.front(), poly.back()));
+ double area = 0;
+ for (size_t i = 0; i < poly.size() - 1; i++)
+ area += poly[i].x * poly[i + 1].y - poly[i].y * poly[i + 1].x;
+
+ return std::fabs(area) / 2.0;
+}
+
+// Returns the area of a rounded rectangle with the given |width|, |height| and
+// |radius|.
+double CalculateRoundedRectangleArea(double width,
+ double height,
+ double radius) {
+ const double inside_width = width - 2 * radius;
+ const double inside_height = height - 2 * radius;
+ return inside_width * inside_height +
+ 2 * radius * (inside_width + inside_height) +
+ CalculateCircleArea(radius);
+}
+
+// Returns the bounding box of |path| as a Rect.
+Rect GetBoundingBox(NSBezierPath* path) {
+ const NSRect bounds = [path bounds];
+ return ToNearestRect(RectF(bounds.origin.x, bounds.origin.y,
+ bounds.size.width, bounds.size.height));
+}
+
+} // namespace
+
+// Check that empty NSBezierPath is returned for empty SkPath.
+TEST(CreateNSBezierPathFromSkPathTest, EmptyPath) {
+ NSBezierPath* result = CreateNSBezierPathFromSkPath(SkPath());
+ EXPECT_TRUE([result isEmpty]);
+}
+
+// Check that the returned NSBezierPath has the correct winding rule.
+TEST(CreateNSBezierPathFromSkPathTest, FillType) {
+ SkPath path;
+ path.setFillType(SkPath::kWinding_FillType);
+ NSBezierPath* result = CreateNSBezierPathFromSkPath(path);
+ EXPECT_EQ(NSNonZeroWindingRule, [result windingRule]);
+
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ result = CreateNSBezierPathFromSkPath(path);
+ EXPECT_EQ(NSEvenOddWindingRule, [result windingRule]);
+}
+
+// Check that a path containing multiple subpaths, in this case two rectangles,
+// is correctly converted to a NSBezierPath.
+TEST(CreateNSBezierPathFromSkPathTest, TwoRectanglesPath) {
+ const SkRect rects[] = {
+ {0, 0, 50, 50}, {100, 100, 150, 150},
+ };
+ const NSPoint inside_points[] = {
+ {1, 1}, {1, 49}, {49, 49}, {49, 1}, {25, 25},
+ {101, 101}, {101, 149}, {149, 149}, {149, 101}, {125, 125}};
+ const NSPoint outside_points[] = {{-1, -1}, {-1, 51}, {51, 51}, {51, -1},
+ {99, 99}, {99, 151}, {151, 151}, {151, 99},
+ {75, 75}, {-5, -5}};
+ ASSERT_EQ(arraysize(inside_points), arraysize(outside_points));
+ const Rect expected_bounds(0, 0, 150, 150);
+
+ SkPath path;
+ path.addRect(rects[0]);
+ path.addRect(rects[1]);
+ NSBezierPath* result = CreateNSBezierPathFromSkPath(path);
+
+ // Check points near the boundary of the path and verify that they are
+ // reported correctly as being inside/outside the path.
+ for (size_t i = 0; i < arraysize(inside_points); i++) {
+ EXPECT_TRUE([result containsPoint:inside_points[i]]);
+ EXPECT_FALSE([result containsPoint:outside_points[i]]);
+ }
+
+ // Check that the returned result has the correct bounding box. GetBoundingBox
+ // rounds the coordinates to nearest integer values.
+ EXPECT_EQ(expected_bounds, GetBoundingBox(result));
+}
+
+// Test that an SKPath containing a circle is converted correctly to a
+// NSBezierPath.
+TEST(CreateNSBezierPathFromSkPathTest, CirclePath) {
+ const int kRadius = 5;
+ const int kCentreX = 10;
+ const int kCentreY = 15;
+ const double kCushion = 0.1;
+ // Expected bounding box of the circle.
+ const Rect expected_bounds(kCentreX - kRadius, kCentreY - kRadius,
+ 2 * kRadius, 2 * kRadius);
+
+ SkPath path;
+ path.addCircle(SkIntToScalar(kCentreX), SkIntToScalar(kCentreY),
+ SkIntToScalar(kRadius));
+ NSBezierPath* result = CreateNSBezierPathFromSkPath(path);
+
+ // Check points near the boundary of the circle and verify that they are
+ // reported correctly as being inside/outside the path.
+ for (size_t deg = 0; deg < 360; deg++) {
+ NSPoint inside_point =
+ GetRadialPoint(kRadius - kCushion, deg, kCentreX, kCentreY);
+ NSPoint outside_point =
+ GetRadialPoint(kRadius + kCushion, deg, kCentreX, kCentreY);
+ EXPECT_TRUE([result containsPoint:inside_point]);
+ EXPECT_FALSE([result containsPoint:outside_point]);
+ }
+
+ // Check that the returned result has the correct bounding box. GetBoundingBox
+ // rounds the coordinates to nearest integer values.
+ EXPECT_EQ(expected_bounds, GetBoundingBox(result));
+
+ // Check area of converted path is correct up to a certain tolerance value. To
+ // find the area of the NSBezierPath returned, flatten it i.e. convert it to a
+ // polygon.
+ [NSBezierPath setDefaultFlatness:0.01];
+ NSBezierPath* polygon = [result bezierPathByFlatteningPath];
+ const double kTolerance = 0.14;
+ EXPECT_NEAR(CalculateCircleArea(kRadius), CalculatePolygonArea(polygon),
+ kTolerance);
+}
+
+// Test that an SKPath containing a rounded rectangle is converted correctly to
+// a NSBezierPath.
+TEST(CreateNSBezierPathFromSkPathTest, RoundedRectanglePath) {
+ const int kRectangleWidth = 50;
+ const int kRectangleHeight = 100;
+ const int kCornerRadius = 5;
+ const double kCushion = 0.1;
+ // Expected bounding box of the rounded rectangle.
+ const Rect expected_bounds(kRectangleWidth, kRectangleHeight);
+
+ SkRRect rrect;
+ rrect.setRectXY(SkRect::MakeWH(kRectangleWidth, kRectangleHeight),
+ kCornerRadius, kCornerRadius);
+
+ const NSPoint inside_points[] = {
+ // Bottom left corner.
+ {kCornerRadius / 2.0, kCornerRadius / 2.0},
+ // Bottom right corner.
+ {kRectangleWidth - kCornerRadius / 2.0, kCornerRadius / 2.0},
+ // Top Right corner.
+ {kRectangleWidth - kCornerRadius / 2.0,
+ kRectangleHeight - kCornerRadius / 2.0},
+ // Top left corner.
+ {kCornerRadius / 2.0, kRectangleHeight - kCornerRadius / 2.0},
+ // Bottom middle.
+ {kRectangleWidth / 2.0, kCushion},
+ // Right middle.
+ {kRectangleWidth - kCushion, kRectangleHeight / 2.0},
+ // Top middle.
+ {kRectangleWidth / 2.0, kRectangleHeight - kCushion},
+ // Left middle.
+ {kCushion, kRectangleHeight / 2.0}};
+ const NSPoint outside_points[] = {
+ // Bottom left corner.
+ {0, 0},
+ // Bottom right corner.
+ {kRectangleWidth, 0},
+ // Top right corner.
+ {kRectangleWidth, kRectangleHeight},
+ // Top left corner.
+ {0, kRectangleHeight},
+ // Bottom middle.
+ {kRectangleWidth / 2.0, -kCushion},
+ // Right middle.
+ {kRectangleWidth + kCushion, kRectangleHeight / 2.0},
+ // Top middle.
+ {kRectangleWidth / 2.0, kRectangleHeight + kCushion},
+ // Left middle.
+ {-kCushion, kRectangleHeight / 2.0}};
+ ASSERT_EQ(arraysize(inside_points), arraysize(outside_points));
+
+ SkPath path;
+ path.addRRect(rrect);
+ NSBezierPath* result = CreateNSBezierPathFromSkPath(path);
+
+ // Check points near the boundary of the path and verify that they are
+ // reported correctly as being inside/outside the path.
+ for (size_t i = 0; i < arraysize(inside_points); i++) {
+ EXPECT_TRUE([result containsPoint:inside_points[i]]);
+ EXPECT_FALSE([result containsPoint:outside_points[i]]);
+ }
+
+ // Check that the returned result has the correct bounding box. GetBoundingBox
+ // rounds the coordinates to nearest integer values.
+ EXPECT_EQ(expected_bounds, GetBoundingBox(result));
+
+ // Check area of converted path is correct up to a certain tolerance value. To
+ // find the area of the NSBezierPath returned, flatten it i.e. convert it to a
+ // polygon.
+ [NSBezierPath setDefaultFlatness:0.01];
+ NSBezierPath* polygon = [result bezierPathByFlatteningPath];
+ const double kTolerance = 0.14;
+ EXPECT_NEAR(CalculateRoundedRectangleArea(kRectangleWidth, kRectangleHeight,
+ kCornerRadius),
+ CalculatePolygonArea(polygon), kTolerance);
+}
+
+} // namespace gfx
diff --git a/ui/views/bubble/bubble_border.cc b/ui/views/bubble/bubble_border.cc
index c2892ca..d17b13d 100644
--- a/ui/views/bubble/bubble_border.cc
+++ b/ui/views/bubble/bubble_border.cc
@@ -12,6 +12,7 @@
#include "ui/base/resource/resource_bundle.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/geometry/rect.h"
+#include "ui/gfx/path.h"
#include "ui/gfx/skia_util.h"
#include "ui/resources/grit/ui_resources.h"
#include "ui/views/painter.h"
@@ -144,8 +145,13 @@ BubbleBorder::BubbleBorder(Arrow arrow, Shadow shadow, SkColor color)
shadow_(shadow),
background_color_(color),
use_theme_background_color_(false) {
- DCHECK(shadow < SHADOW_COUNT);
- images_ = GetBorderImages(shadow);
+#if defined(OS_MACOSX)
+ // On Mac, use the NO_ASSETS bubble border. WindowServer on Mac is able to
+ // generate drop shadows for dialogs, hence we don't use raster shadows.
+ shadow_ = NO_ASSETS;
+#endif // OS_MACOSX
+ DCHECK(shadow_ < SHADOW_COUNT);
+ images_ = GetBorderImages(shadow_);
}
BubbleBorder::~BubbleBorder() {}
@@ -211,6 +217,15 @@ int BubbleBorder::GetArrowOffset(const gfx::Size& border_size) const {
return std::max(min, std::min(arrow_offset_, edge_length - min));
}
+bool BubbleBorder::GetArrowPath(const gfx::Rect& view_bounds,
+ gfx::Path* path) const {
+ if (!has_arrow(arrow_) || arrow_paint_type_ != PAINT_NORMAL)
+ return false;
+
+ GetArrowPathFromArrowBounds(GetArrowRect(view_bounds), path);
+ return true;
+}
+
void BubbleBorder::Paint(const views::View& view, gfx::Canvas* canvas) {
gfx::Rect bounds(view.GetContentsBounds());
bounds.Inset(-GetBorderThickness(), -GetBorderThickness());
@@ -328,9 +343,8 @@ gfx::Rect BubbleBorder::GetArrowRect(const gfx::Rect& bounds) const {
return gfx::Rect(origin, gfx::Size(width, height));
}
-void BubbleBorder::DrawArrow(gfx::Canvas* canvas,
- const gfx::Rect& arrow_bounds) const {
- canvas->DrawImageInt(*GetArrowImage(), arrow_bounds.x(), arrow_bounds.y());
+void BubbleBorder::GetArrowPathFromArrowBounds(const gfx::Rect& arrow_bounds,
+ SkPath* path) const {
const bool horizontal = is_arrow_on_horizontal(arrow_);
const int thickness = images_->arrow_interior_thickness;
float tip_x = horizontal ? arrow_bounds.CenterPoint().x() :
@@ -344,16 +358,21 @@ void BubbleBorder::DrawArrow(gfx::Canvas* canvas,
const int offset_to_next_vertex = positive_offset ?
images_->arrow_interior_thickness : -images_->arrow_interior_thickness;
- SkPath path;
- path.incReserve(4);
- path.moveTo(SkDoubleToScalar(tip_x), SkDoubleToScalar(tip_y));
- path.lineTo(SkDoubleToScalar(tip_x + offset_to_next_vertex),
- SkDoubleToScalar(tip_y + offset_to_next_vertex));
+ path->incReserve(4);
+ path->moveTo(SkDoubleToScalar(tip_x), SkDoubleToScalar(tip_y));
+ path->lineTo(SkDoubleToScalar(tip_x + offset_to_next_vertex),
+ SkDoubleToScalar(tip_y + offset_to_next_vertex));
const int multiplier = horizontal ? 1 : -1;
- path.lineTo(SkDoubleToScalar(tip_x - multiplier * offset_to_next_vertex),
- SkDoubleToScalar(tip_y + multiplier * offset_to_next_vertex));
- path.close();
+ path->lineTo(SkDoubleToScalar(tip_x - multiplier * offset_to_next_vertex),
+ SkDoubleToScalar(tip_y + multiplier * offset_to_next_vertex));
+ path->close();
+}
+void BubbleBorder::DrawArrow(gfx::Canvas* canvas,
+ const gfx::Rect& arrow_bounds) const {
+ canvas->DrawImageInt(*GetArrowImage(), arrow_bounds.x(), arrow_bounds.y());
+ SkPath path;
+ GetArrowPathFromArrowBounds(arrow_bounds, &path);
SkPaint paint;
paint.setStyle(SkPaint::kFill_Style);
paint.setColor(background_color_);
diff --git a/ui/views/bubble/bubble_border.h b/ui/views/bubble/bubble_border.h
index fac3a71..fd3b7c5 100644
--- a/ui/views/bubble/bubble_border.h
+++ b/ui/views/bubble/bubble_border.h
@@ -13,7 +13,10 @@
#include "ui/views/background.h"
#include "ui/views/border.h"
+class SkPath;
+
namespace gfx {
+class Path;
class Rect;
}
@@ -204,6 +207,13 @@ class VIEWS_EXPORT BubbleBorder : public Border {
// Gets the arrow offset to use.
int GetArrowOffset(const gfx::Size& border_size) const;
+ // Retreives the arrow path given |view_bounds|. |view_bounds| should be local
+ // bounds of the view.
+ // Returns false if |path| is unchanged, which is the case when there is no
+ // painted arrow.
+ // The returned path does not account for arrow stroke and shadow.
+ bool GetArrowPath(const gfx::Rect& view_bounds, gfx::Path* path) const;
+
// Overridden from Border:
void Paint(const View& view, gfx::Canvas* canvas) override;
gfx::Insets GetInsets() const override;
@@ -220,6 +230,8 @@ class VIEWS_EXPORT BubbleBorder : public Border {
gfx::Size GetSizeForContentsSize(const gfx::Size& contents_size) const;
gfx::ImageSkia* GetArrowImage() const;
gfx::Rect GetArrowRect(const gfx::Rect& bounds) const;
+ void GetArrowPathFromArrowBounds(const gfx::Rect& arrow_bounds,
+ SkPath* path) const;
void DrawArrow(gfx::Canvas* canvas, const gfx::Rect& arrow_bounds) const;
internal::BorderImages* GetImagesForTest() const;
diff --git a/ui/views/bubble/bubble_frame_view.cc b/ui/views/bubble/bubble_frame_view.cc
index 5d64e22..c102835 100644
--- a/ui/views/bubble/bubble_frame_view.cc
+++ b/ui/views/bubble/bubble_frame_view.cc
@@ -155,30 +155,42 @@ int BubbleFrameView::NonClientHitTest(const gfx::Point& point) {
void BubbleFrameView::GetWindowMask(const gfx::Size& size,
gfx::Path* window_mask) {
- // NOTE: this only provides implementations for the types used by dialogs.
- if ((bubble_border_->arrow() != BubbleBorder::NONE &&
- bubble_border_->arrow() != BubbleBorder::FLOAT) ||
- (bubble_border_->shadow() != BubbleBorder::SMALL_SHADOW &&
- bubble_border_->shadow() != BubbleBorder::NO_SHADOW_OPAQUE_BORDER))
+ if (bubble_border_->shadow() != BubbleBorder::SMALL_SHADOW &&
+ bubble_border_->shadow() != BubbleBorder::NO_SHADOW_OPAQUE_BORDER &&
+ bubble_border_->shadow() != BubbleBorder::NO_ASSETS)
+ return;
+
+ // We don't return a mask for windows with arrows unless they use
+ // BubbleBorder::NO_ASSETS.
+ if (bubble_border_->shadow() != BubbleBorder::NO_ASSETS &&
+ bubble_border_->arrow() != BubbleBorder::NONE &&
+ bubble_border_->arrow() != BubbleBorder::FLOAT)
return;
// Use a window mask roughly matching the border in the image assets.
- static const int kBorderStrokeSize = 1;
- static const SkScalar kCornerRadius = SkIntToScalar(6);
+ const int kBorderStrokeSize =
+ bubble_border_->shadow() == BubbleBorder::NO_ASSETS ? 0 : 1;
+ const SkScalar kCornerRadius =
+ SkIntToScalar(bubble_border_->GetBorderCornerRadius());
const gfx::Insets border_insets = bubble_border_->GetInsets();
- SkRect rect = { SkIntToScalar(border_insets.left() - kBorderStrokeSize),
- SkIntToScalar(border_insets.top() - kBorderStrokeSize),
- SkIntToScalar(size.width() - border_insets.right() +
- kBorderStrokeSize),
- SkIntToScalar(size.height() - border_insets.bottom() +
- kBorderStrokeSize) };
- if (bubble_border_->shadow() == BubbleBorder::NO_SHADOW_OPAQUE_BORDER) {
+ SkRect rect = {
+ SkIntToScalar(border_insets.left() - kBorderStrokeSize),
+ SkIntToScalar(border_insets.top() - kBorderStrokeSize),
+ SkIntToScalar(size.width() - border_insets.right() + kBorderStrokeSize),
+ SkIntToScalar(size.height() - border_insets.bottom() +
+ kBorderStrokeSize)};
+
+ if (bubble_border_->shadow() == BubbleBorder::NO_SHADOW_OPAQUE_BORDER ||
+ bubble_border_->shadow() == BubbleBorder::NO_ASSETS) {
window_mask->addRoundRect(rect, kCornerRadius, kCornerRadius);
} else {
static const int kBottomBorderShadowSize = 2;
rect.fBottom += SkIntToScalar(kBottomBorderShadowSize);
window_mask->addRect(rect);
}
+ gfx::Path arrow_path;
+ if (bubble_border_->GetArrowPath(gfx::Rect(size), &arrow_path))
+ window_mask->addPath(arrow_path, 0, 0);
}
void BubbleFrameView::ResetWindowControls() {
diff --git a/ui/views/cocoa/bridged_content_view.h b/ui/views/cocoa/bridged_content_view.h
index 9f6fbb5..f4b6a86 100644
--- a/ui/views/cocoa/bridged_content_view.h
+++ b/ui/views/cocoa/bridged_content_view.h
@@ -47,6 +47,9 @@ class View;
// Whether dragging on the view moves the window.
BOOL mouseDownCanMoveWindow_;
+
+ // The cached window mask. Only used for non-rectangular windows on 10.9.
+ base::scoped_nsobject<NSBezierPath> windowMask_;
}
@property(readonly, nonatomic) views::View* hostedView;
@@ -73,6 +76,9 @@ class View;
// contentRect (also this NSView's frame), as given by a ui::LocatedEvent.
- (void)updateTooltipIfRequiredAt:(const gfx::Point&)locationInContent;
+// Update windowMask_ depending on the current view bounds.
+- (void)updateWindowMask;
+
@end
#endif // UI_VIEWS_COCOA_BRIDGED_CONTENT_VIEW_H_
diff --git a/ui/views/cocoa/bridged_content_view.mm b/ui/views/cocoa/bridged_content_view.mm
index 718207c..6022a447 100644
--- a/ui/views/cocoa/bridged_content_view.mm
+++ b/ui/views/cocoa/bridged_content_view.mm
@@ -5,6 +5,7 @@
#import "ui/views/cocoa/bridged_content_view.h"
#include "base/logging.h"
+#import "base/mac/mac_util.h"
#import "base/mac/scoped_nsobject.h"
#include "base/strings/sys_string_conversions.h"
#include "skia/ext/skia_utils_mac.h"
@@ -17,6 +18,9 @@
#include "ui/gfx/canvas_paint_mac.h"
#include "ui/gfx/geometry/rect.h"
#import "ui/gfx/mac/coordinate_conversion.h"
+#include "ui/gfx/path.h"
+#import "ui/gfx/path_mac.h"
+#include "ui/gfx/scoped_ns_graphics_context_save_gstate_mac.h"
#include "ui/strings/grit/ui_strings.h"
#include "ui/views/controls/menu/menu_config.h"
#include "ui/views/controls/menu/menu_controller.h"
@@ -27,6 +31,17 @@ using views::MenuController;
namespace {
+// Returns true if all four corners of |rect| are contained inside |path|.
+bool IsRectInsidePath(NSRect rect, NSBezierPath* path) {
+ return [path containsPoint:rect.origin] &&
+ [path containsPoint:NSMakePoint(rect.origin.x + rect.size.width,
+ rect.origin.y)] &&
+ [path containsPoint:NSMakePoint(rect.origin.x,
+ rect.origin.y + rect.size.height)] &&
+ [path containsPoint:NSMakePoint(rect.origin.x + rect.size.width,
+ rect.origin.y + rect.size.height)];
+}
+
// Convert a |point| in |source_window|'s AppKit coordinate system (origin at
// the bottom left of the window) to |target_window|'s content rect, with the
// origin at the top left of the content area.
@@ -252,6 +267,30 @@ gfx::Rect GetFirstRectForRangeHelper(const ui::TextInputClient* client,
}
}
+- (void)updateWindowMask {
+ DCHECK(![self inLiveResize]);
+ DCHECK(base::mac::IsOSMavericksOrEarlier());
+ DCHECK(hostedView_);
+
+ views::Widget* widget = hostedView_->GetWidget();
+ if (!widget->non_client_view())
+ return;
+
+ const NSRect frameRect = [self bounds];
+ gfx::Path mask;
+ widget->non_client_view()->GetWindowMask(gfx::Size(frameRect.size), &mask);
+ if (mask.isEmpty())
+ return;
+
+ windowMask_.reset([gfx::CreateNSBezierPathFromSkPath(mask) retain]);
+
+ // Convert to AppKit coordinate system.
+ NSAffineTransform* flipTransform = [NSAffineTransform transform];
+ [flipTransform translateXBy:0.0 yBy:frameRect.size.height];
+ [flipTransform scaleXBy:1.0 yBy:-1.0];
+ [windowMask_ transformUsingAffineTransform:flipTransform];
+}
+
// BridgedContentView private implementation.
- (void)handleKeyEvent:(NSEvent*)theEvent {
@@ -393,6 +432,18 @@ gfx::Rect GetFirstRectForRangeHelper(const ui::TextInputClient* client,
hostedView_->SetSize(gfx::Size(newSize.width, newSize.height));
}
+- (void)viewDidEndLiveResize {
+ [super viewDidEndLiveResize];
+
+ // We prevent updating the window mask and clipping the border around the
+ // view, during a live resize. Hence update the window mask and redraw the
+ // view after resize has completed.
+ if (base::mac::IsOSMavericksOrEarlier()) {
+ [self updateWindowMask];
+ [self setNeedsDisplay:YES];
+ }
+}
+
- (void)drawRect:(NSRect)dirtyRect {
// Note that BridgedNativeWidget uses -[NSWindow setAutodisplay:NO] to
// suppress calls to this when the window is known to be hidden.
@@ -407,6 +458,33 @@ gfx::Rect GetFirstRectForRangeHelper(const ui::TextInputClient* client,
yRadius:radius] fill];
}
+ // On OS versions earlier than Yosemite, to generate a drop shadow, we set an
+ // opaque background. This causes windows with non rectangular shapes to have
+ // square corners. To get around this, fill the path outside the window
+ // boundary with clearColor and tell Cococa to regenerate drop shadow. See
+ // crbug.com/543671.
+ if (windowMask_ && ![self inLiveResize] &&
+ !IsRectInsidePath(dirtyRect, windowMask_)) {
+ DCHECK(base::mac::IsOSMavericksOrEarlier());
+ gfx::ScopedNSGraphicsContextSaveGState state;
+
+ // The outer rectangular path corresponding to the window.
+ NSBezierPath* outerPath = [NSBezierPath bezierPathWithRect:[self bounds]];
+
+ [outerPath appendBezierPath:windowMask_];
+ [outerPath setWindingRule:NSEvenOddWindingRule];
+ [[NSGraphicsContext currentContext]
+ setCompositingOperation:NSCompositeCopy];
+ [[NSColor clearColor] set];
+
+ // Fill the region between windowMask_ and its outer rectangular path
+ // with clear color. This causes the window to have the shape described
+ // by windowMask_.
+ [outerPath fill];
+ // Regerate drop shadow around the window boundary.
+ [[self window] invalidateShadow];
+ }
+
// If there's a layer, painting occurs in BridgedNativeWidget::OnPaintLayer().
if (hostedView_->GetWidget()->GetLayer())
return;
diff --git a/ui/views/cocoa/bridged_native_widget.mm b/ui/views/cocoa/bridged_native_widget.mm
index 6a4971b..6f5dec0 100644
--- a/ui/views/cocoa/bridged_native_widget.mm
+++ b/ui/views/cocoa/bridged_native_widget.mm
@@ -682,6 +682,14 @@ void BridgedNativeWidget::OnSizeChanged() {
if ([window_ inLiveResize])
MaybeWaitForFrame(new_size);
}
+
+ // 10.9 is unable to generate a window shadow from the composited CALayer, so
+ // use Quartz.
+ // We don't update the window mask during a live resize, instead it is done
+ // after the resize is completed in viewDidEndLiveResize: in
+ // BridgedContentView.
+ if (base::mac::IsOSMavericksOrEarlier() && ![window_ inLiveResize])
+ [bridged_view_ updateWindowMask];
}
void BridgedNativeWidget::OnVisibilityChanged() {
@@ -865,7 +873,12 @@ void BridgedNativeWidget::CreateLayer(ui::LayerType layer_type,
// native shape is what's most appropriate for displaying sheets on Mac.
if (translucent && !native_widget_mac_->IsWindowModalSheet()) {
[window_ setOpaque:NO];
- [window_ setBackgroundColor:[NSColor clearColor]];
+ // For Mac OS versions earlier than Yosemite, the Window server isn't able
+ // to generate a window shadow from the composited CALayer. To get around
+ // this, let the window background remain opaque and clip the window
+ // boundary in drawRect method of BridgedContentView. See crbug.com/543671.
+ if (base::mac::IsOSYosemiteOrLater())
+ [window_ setBackgroundColor:[NSColor clearColor]];
}
UpdateLayerProperties();
diff --git a/ui/views/window/dialog_delegate.cc b/ui/views/window/dialog_delegate.cc
index 64759d9..e6b179e 100644
--- a/ui/views/window/dialog_delegate.cc
+++ b/ui/views/window/dialog_delegate.cc
@@ -200,12 +200,7 @@ NonClientFrameView* DialogDelegate::CreateDialogFrameView(Widget* widget) {
new BubbleFrameView(gfx::Insets(kPanelVertMargin, kButtonHEdgeMarginNew,
0, kButtonHEdgeMarginNew),
gfx::Insets());
-#if defined(OS_MACOSX)
- // On Mac, dialogs have no border stroke and use a shadow provided by the OS.
- const BubbleBorder::Shadow kShadow = BubbleBorder::NO_ASSETS;
-#else
const BubbleBorder::Shadow kShadow = BubbleBorder::SMALL_SHADOW;
-#endif
scoped_ptr<BubbleBorder> border(
new BubbleBorder(BubbleBorder::FLOAT, kShadow, gfx::kPlaceholderColor));
border->set_use_theme_background_color(true);