Commit a3df3751 authored by Alex Newcomer's avatar Alex Newcomer Committed by Commit Bot

cros: Make touchable app context menus use proper shadows

- Add MD shadows by...
  - Adding a member variable to BubbleBorder: std::Optional<int> md_shadow_elevation.
  - Modify static function GetBorderAndShadowFlags to support
    custom shadow elevation.
  - Adding a static function to cache ShadowValues.
    - I created a static function (GetShadowValues) here because other static
      functions assume that the default shadows are used.
  - Make GetBorderAndShadowInsets use gfx::ShadowValues::GetMargins when an elevation
    is set.

Bug: 826912
Change-Id: I93837da730fd5344aa026f67f1bff5ce3c766a98
Reviewed-on: https://chromium-review.googlesource.com/993275
Commit-Queue: Alex Newcomer <newcomer@chromium.org>
Reviewed-by: default avatarMichael Wasserman <msw@chromium.org>
Cr-Commit-Position: refs/heads/master@{#549379}
parent 8d99c3fa
...@@ -182,7 +182,13 @@ BubbleBorder::BubbleBorder(Arrow arrow, Shadow shadow, SkColor color) ...@@ -182,7 +182,13 @@ BubbleBorder::BubbleBorder(Arrow arrow, Shadow shadow, SkColor color)
BubbleBorder::~BubbleBorder() {} BubbleBorder::~BubbleBorder() {}
// static // static
gfx::Insets BubbleBorder::GetBorderAndShadowInsets() { gfx::Insets BubbleBorder::GetBorderAndShadowInsets(
base::Optional<int> elevation) {
if (elevation.has_value()) {
return -gfx::ShadowValue::GetMargin(GetShadowValues(elevation)) +
gfx::Insets(kBorderThicknessDip);
}
constexpr gfx::Insets blur(kShadowBlur + kBorderThicknessDip); constexpr gfx::Insets blur(kShadowBlur + kBorderThicknessDip);
constexpr gfx::Insets offset(-kShadowVerticalOffset, 0, kShadowVerticalOffset, constexpr gfx::Insets offset(-kShadowVerticalOffset, 0, kShadowVerticalOffset,
0); 0);
...@@ -378,8 +384,11 @@ void BubbleBorder::Paint(const views::View& view, gfx::Canvas* canvas) { ...@@ -378,8 +384,11 @@ void BubbleBorder::Paint(const views::View& view, gfx::Canvas* canvas) {
} }
gfx::Insets BubbleBorder::GetInsets() const { gfx::Insets BubbleBorder::GetInsets() const {
if (UseMaterialDesign()) if (UseMaterialDesign()) {
return (shadow_ == NO_ASSETS) ? gfx::Insets() : GetBorderAndShadowInsets(); return (shadow_ == NO_ASSETS)
? gfx::Insets()
: GetBorderAndShadowInsets(md_shadow_elevation_);
}
// The insets contain the stroke and shadow pixels outside the bubble fill. // The insets contain the stroke and shadow pixels outside the bubble fill.
const int inset = GetBorderThickness(); const int inset = GetBorderThickness();
...@@ -401,14 +410,20 @@ gfx::Size BubbleBorder::GetMinimumSize() const { ...@@ -401,14 +410,20 @@ gfx::Size BubbleBorder::GetMinimumSize() const {
} }
// static // static
const cc::PaintFlags& BubbleBorder::GetBorderAndShadowFlags() { const gfx::ShadowValues& BubbleBorder::GetShadowValues(
// This object is always the same, so construct it once and cache. base::Optional<int> elevation) {
static const base::NoDestructor<cc::PaintFlags> flags([] { // The shadows are always the same for any elevation, so construct them once
cc::PaintFlags f; // and cache.
constexpr SkColor kBorderColor = SkColorSetA(SK_ColorBLACK, 0x26); static base::NoDestructor<std::map<int, gfx::ShadowValues>> shadow_map;
f.setColor(kBorderColor); if (shadow_map->find(elevation.value_or(-1)) != shadow_map->end())
f.setAntiAlias(true); return shadow_map->find(elevation.value_or(-1))->second;
gfx::ShadowValues shadows;
if (elevation.has_value()) {
DCHECK(elevation.value() >= 0);
shadows = gfx::ShadowValues(
gfx::ShadowValue::MakeMdShadowValues(elevation.value()));
} else {
constexpr int kSmallShadowVerticalOffset = 2; constexpr int kSmallShadowVerticalOffset = 2;
constexpr int kSmallShadowBlur = 4; constexpr int kSmallShadowBlur = 4;
constexpr SkColor kSmallShadowColor = SkColorSetA(SK_ColorBLACK, 0x33); constexpr SkColor kSmallShadowColor = SkColorSetA(SK_ColorBLACK, 0x33);
...@@ -416,15 +431,38 @@ const cc::PaintFlags& BubbleBorder::GetBorderAndShadowFlags() { ...@@ -416,15 +431,38 @@ const cc::PaintFlags& BubbleBorder::GetBorderAndShadowFlags() {
// gfx::ShadowValue counts blur pixels both inside and outside the shape, // gfx::ShadowValue counts blur pixels both inside and outside the shape,
// whereas these blur values only describe the outside portion, hence they // whereas these blur values only describe the outside portion, hence they
// must be doubled. // must be doubled.
f.setLooper(gfx::CreateShadowDrawLooper({ shadows = gfx::ShadowValues({
{gfx::Vector2d(0, kSmallShadowVerticalOffset), 2 * kSmallShadowBlur, {gfx::Vector2d(0, kSmallShadowVerticalOffset), 2 * kSmallShadowBlur,
kSmallShadowColor}, kSmallShadowColor},
{gfx::Vector2d(0, kShadowVerticalOffset), 2 * kShadowBlur, {gfx::Vector2d(0, kShadowVerticalOffset), 2 * kShadowBlur,
kLargeShadowColor}, kLargeShadowColor},
})); });
return f; }
}());
return *flags; shadow_map->insert(
std::pair<int, gfx::ShadowValues>(elevation.value_or(-1), shadows));
return shadow_map->find(elevation.value_or(-1))->second;
}
// static
const cc::PaintFlags& BubbleBorder::GetBorderAndShadowFlags(
base::Optional<int> elevation) {
// The flags are always the same for any elevation, so construct them once and
// cache.
static base::NoDestructor<std::map<int, cc::PaintFlags>> flag_map;
if (flag_map->find(elevation.value_or(-1)) != flag_map->end())
return flag_map->find(elevation.value_or(-1))->second;
cc::PaintFlags flags;
constexpr SkColor kBorderColor = SkColorSetA(SK_ColorBLACK, 0x26);
flags.setColor(kBorderColor);
flags.setAntiAlias(true);
flags.setLooper(gfx::CreateShadowDrawLooper(GetShadowValues(elevation)));
flag_map->insert(
std::pair<int, cc::PaintFlags>(elevation.value_or(-1), flags));
return flag_map->find(elevation.value_or(-1))->second;
} }
gfx::Size BubbleBorder::GetSizeForContentsSize( gfx::Size BubbleBorder::GetSizeForContentsSize(
...@@ -560,7 +598,8 @@ void BubbleBorder::PaintMd(const View& view, gfx::Canvas* canvas) { ...@@ -560,7 +598,8 @@ void BubbleBorder::PaintMd(const View& view, gfx::Canvas* canvas) {
canvas->sk_canvas()->clipRRect(r_rect, SkClipOp::kDifference, canvas->sk_canvas()->clipRRect(r_rect, SkClipOp::kDifference,
true /*doAntiAlias*/); true /*doAntiAlias*/);
DrawBorderAndShadow(std::move(r_rect), &cc::PaintCanvas::drawRRect, canvas); DrawBorderAndShadow(std::move(r_rect), &cc::PaintCanvas::drawRRect, canvas,
md_shadow_elevation_);
} }
void BubbleBorder::PaintNoAssets(const View& view, gfx::Canvas* canvas) { void BubbleBorder::PaintNoAssets(const View& view, gfx::Canvas* canvas) {
......
...@@ -13,6 +13,7 @@ ...@@ -13,6 +13,7 @@
#include "build/build_config.h" #include "build/build_config.h"
#include "ui/gfx/canvas.h" #include "ui/gfx/canvas.h"
#include "ui/gfx/image/image_skia.h" #include "ui/gfx/image/image_skia.h"
#include "ui/gfx/shadow_value.h"
#include "ui/views/background.h" #include "ui/views/background.h"
#include "ui/views/border.h" #include "ui/views/border.h"
...@@ -172,24 +173,29 @@ class VIEWS_EXPORT BubbleBorder : public Border { ...@@ -172,24 +173,29 @@ class VIEWS_EXPORT BubbleBorder : public Border {
a : static_cast<Arrow>(a ^ BOTTOM); a : static_cast<Arrow>(a ^ BOTTOM);
} }
// Returns the insets required by a border and shadow. This is only used for // Returns the insets required by a border and shadow based on
// MD bubbles. // |shadow_elevation|. This is only used for MD bubbles. A null
static gfx::Insets GetBorderAndShadowInsets(); // |shadow_elevation| will yield the default BubbleBorder MD insets.
static gfx::Insets GetBorderAndShadowInsets(
base::Optional<int> shadow_elevation = base::nullopt);
// Draws a border and shadow outside the |rect| on |canvas|, using |draw| as // Draws a border and shadow based on |shadow_elevation| outside the |rect| on
// the draw function. Templated so as to accept either SkRect or SkRRect. // |canvas|, using |draw| as the draw function. Templated so as to accept
// either SkRect or SkRRect.
template <typename T> template <typename T>
static void DrawBorderAndShadow( static void DrawBorderAndShadow(
T rect, T rect,
void (cc::PaintCanvas::*draw)(const T&, const cc::PaintFlags&), void (cc::PaintCanvas::*draw)(const T&, const cc::PaintFlags&),
gfx::Canvas* canvas) { gfx::Canvas* canvas,
base::Optional<int> shadow_elevation = base::nullopt) {
// Provide a 1 px border outside the bounds. // Provide a 1 px border outside the bounds.
const int kBorderStrokeThicknessPx = 1; const int kBorderStrokeThicknessPx = 1;
const SkScalar one_pixel = const SkScalar one_pixel =
SkFloatToScalar(kBorderStrokeThicknessPx / canvas->image_scale()); SkFloatToScalar(kBorderStrokeThicknessPx / canvas->image_scale());
rect.outset(one_pixel, one_pixel); rect.outset(one_pixel, one_pixel);
(canvas->sk_canvas()->*draw)(rect, GetBorderAndShadowFlags()); (canvas->sk_canvas()->*draw)(rect,
GetBorderAndShadowFlags(shadow_elevation));
} }
// Set the corner radius, enables Material Design. // Set the corner radius, enables Material Design.
...@@ -224,9 +230,17 @@ class VIEWS_EXPORT BubbleBorder : public Border { ...@@ -224,9 +230,17 @@ class VIEWS_EXPORT BubbleBorder : public Border {
// location to place the arrow |offset| pixels from the perpendicular edge. // location to place the arrow |offset| pixels from the perpendicular edge.
void set_arrow_offset(int offset) { arrow_offset_ = offset; } void set_arrow_offset(int offset) { arrow_offset_ = offset; }
// Sets the way the arrow is actually painted. Default is PAINT_NORMAL. // Sets the way the arrow is actually painted. Default is PAINT_NORMAL.
void set_paint_arrow(ArrowPaintType value); void set_paint_arrow(ArrowPaintType value);
// Sets the shadow elevation for MD shadows. A null |shadow_elevation| will
// yield the default BubbleBorder MD shadow.
void set_md_shadow_elevation(int shadow_elevation) {
DCHECK(UseMaterialDesign()) << "Setting a non-default MD shadow elevation "
"requires that the BubbleBorder is using MD";
md_shadow_elevation_ = shadow_elevation;
}
// Get the desired widget bounds (in screen coordinates) given the anchor rect // Get the desired widget bounds (in screen coordinates) given the anchor rect
// and bubble content size; calculated from shadow and arrow image dimensions. // and bubble content size; calculated from shadow and arrow image dimensions.
virtual gfx::Rect GetBounds(const gfx::Rect& anchor_rect, virtual gfx::Rect GetBounds(const gfx::Rect& anchor_rect,
...@@ -262,9 +276,18 @@ class VIEWS_EXPORT BubbleBorder : public Border { ...@@ -262,9 +276,18 @@ class VIEWS_EXPORT BubbleBorder : public Border {
FRIEND_TEST_ALL_PREFIXES(BubbleBorderTest, GetBoundsOriginTest); FRIEND_TEST_ALL_PREFIXES(BubbleBorderTest, GetBoundsOriginTest);
FRIEND_TEST_ALL_PREFIXES(BubbleBorderTest, ShadowTypes); FRIEND_TEST_ALL_PREFIXES(BubbleBorderTest, ShadowTypes);
// Returns the paint flags to use for painting the border and shadow. This is // Returns the shadows based on |shadow_elevation| to use for painting the
// only used for MD bubbles. // border and shadow, and for getting insets. This is only used for MD
static const cc::PaintFlags& GetBorderAndShadowFlags(); // bubbles. A null |shadow_elevation| will yield the default BubbleBorder MD
// ShadowValues.
static const gfx::ShadowValues& GetShadowValues(
base::Optional<int> shadow_elevation = base::nullopt);
// Returns the paint flags to use for painting the border and shadow based on
// |shadow_elevation|. This is only used for MD bubbles. A null
// |shadow_elevation| will yield the default BubbleBorder MD PaintFlags.
static const cc::PaintFlags& GetBorderAndShadowFlags(
base::Optional<int> shadow_elevation = base::nullopt);
// The border and arrow stroke size used in image assets, in pixels. // The border and arrow stroke size used in image assets, in pixels.
static const int kStroke; static const int kStroke;
...@@ -301,6 +324,8 @@ class VIEWS_EXPORT BubbleBorder : public Border { ...@@ -301,6 +324,8 @@ class VIEWS_EXPORT BubbleBorder : public Border {
// Corner radius for the bubble border. If supplied the border will use // Corner radius for the bubble border. If supplied the border will use
// material design. // material design.
base::Optional<int> corner_radius_; base::Optional<int> corner_radius_;
// Elevation for the MD shadow. Requires material design.
base::Optional<SkColor> md_shadow_elevation_;
ArrowPaintType arrow_paint_type_; ArrowPaintType arrow_paint_type_;
BubbleAlignment alignment_; BubbleAlignment alignment_;
Shadow shadow_; Shadow shadow_;
......
...@@ -50,8 +50,10 @@ MenuConfig::MenuConfig() ...@@ -50,8 +50,10 @@ MenuConfig::MenuConfig()
show_delay(400), show_delay(400),
corner_radius(0), corner_radius(0),
touchable_corner_radius(8), touchable_corner_radius(8),
touchable_anchor_offset(8),
touchable_menu_height(36), touchable_menu_height(36),
touchable_menu_width(256), touchable_menu_width(256),
touchable_menu_shadow_elevation(12),
vertical_touchable_menu_item_padding(8) { vertical_touchable_menu_item_padding(8) {
Init(); Init();
} }
......
...@@ -143,12 +143,18 @@ struct VIEWS_EXPORT MenuConfig { ...@@ -143,12 +143,18 @@ struct VIEWS_EXPORT MenuConfig {
// Radius of the rounded corners of the touchable menu border // Radius of the rounded corners of the touchable menu border
int touchable_corner_radius; int touchable_corner_radius;
// Anchor offset for touchable menus created by a touch event.
int touchable_anchor_offset;
// Height of child MenuItemViews for touchable menus. // Height of child MenuItemViews for touchable menus.
int touchable_menu_height; int touchable_menu_height;
// Width of touchable menus. // Width of touchable menus.
int touchable_menu_width; int touchable_menu_width;
// Shadow elevation of touchable menus.
int touchable_menu_shadow_elevation;
// Vertical padding for touchable menus. // Vertical padding for touchable menus.
int vertical_touchable_menu_item_padding; int vertical_touchable_menu_item_padding;
......
...@@ -2144,6 +2144,12 @@ gfx::Rect MenuController::CalculateBubbleMenuBounds(MenuItemView* item, ...@@ -2144,6 +2144,12 @@ gfx::Rect MenuController::CalculateBubbleMenuBounds(MenuItemView* item,
pref.set_width(std::min(pref.width(), pref.set_width(std::min(pref.width(),
item->GetDelegate()->GetMaxWidthForMenu(item))); item->GetDelegate()->GetMaxWidthForMenu(item)));
const MenuConfig& menu_config = MenuConfig::instance();
// Shadow insets are built into MenuScrollView's preferred size so it must be
// compensated for when determining the bounds of touchable menus.
gfx::Insets shadow_insets = BubbleBorder::GetBorderAndShadowInsets(
menu_config.touchable_menu_shadow_elevation);
int x, y; int x, y;
if (state_.anchor == MENU_ANCHOR_BUBBLE_ABOVE || if (state_.anchor == MENU_ANCHOR_BUBBLE_ABOVE ||
state_.anchor == MENU_ANCHOR_BUBBLE_BELOW) { state_.anchor == MENU_ANCHOR_BUBBLE_BELOW) {
...@@ -2164,25 +2170,31 @@ gfx::Rect MenuController::CalculateBubbleMenuBounds(MenuItemView* item, ...@@ -2164,25 +2170,31 @@ gfx::Rect MenuController::CalculateBubbleMenuBounds(MenuItemView* item,
} else if (state_.anchor == MENU_ANCHOR_BUBBLE_TOUCHABLE_ABOVE) { } else if (state_.anchor == MENU_ANCHOR_BUBBLE_TOUCHABLE_ABOVE) {
// Align the left edges of the menu and anchor, and the bottom of the menu // Align the left edges of the menu and anchor, and the bottom of the menu
// with the top of the anchor. // with the top of the anchor.
x = owner_bounds.origin().x(); x = owner_bounds.origin().x() - shadow_insets.left();
y = owner_bounds.origin().y() - pref.height(); y = owner_bounds.origin().y() - pref.height() + shadow_insets.bottom() -
menu_config.touchable_anchor_offset;
// Align the right of the container with the right of the app icon. // Align the right of the container with the right of the app icon.
if (x + pref.width() > state_.monitor_bounds.width()) if (x + pref.width() > state_.monitor_bounds.width())
x = owner_bounds.right() - pref.width(); x = owner_bounds.right() - pref.width() + shadow_insets.right();
// Align the top of the menu with the bottom of the anchor. // Align the top of the menu with the bottom of the anchor.
if (y < 0) if (y < 0) {
y = owner_bounds.bottom(); y = owner_bounds.bottom() - shadow_insets.top() +
menu_config.touchable_anchor_offset;
}
} else if (state_.anchor == MENU_ANCHOR_BUBBLE_TOUCHABLE_LEFT) { } else if (state_.anchor == MENU_ANCHOR_BUBBLE_TOUCHABLE_LEFT) {
// Align the right of the menu with the left of the anchor, and the top of // Align the right of the menu with the left of the anchor, and the top of
// the menu with the top of the anchor. // the menu with the top of the anchor.
x = owner_bounds.origin().x() - pref.width(); x = owner_bounds.origin().x() - pref.width() + shadow_insets.right() -
y = owner_bounds.origin().y(); menu_config.touchable_anchor_offset;
y = owner_bounds.origin().y() - shadow_insets.top();
// Align the left of the menu with the right of the anchor. // Align the left of the menu with the right of the anchor.
if (x < 0) if (x < 0) {
x = owner_bounds.right(); x = owner_bounds.right() + shadow_insets.left() +
menu_config.touchable_anchor_offset;
}
// Align the bottom of the menu to the bottom of the anchor. // Align the bottom of the menu to the bottom of the anchor.
if (y + pref.height() > state_.monitor_bounds.height()) if (y + pref.height() > state_.monitor_bounds.height())
y = owner_bounds.bottom() - pref.height(); y = owner_bounds.bottom() - pref.height() + shadow_insets.bottom();
} else { } else {
if (state_.anchor == MENU_ANCHOR_BUBBLE_RIGHT) if (state_.anchor == MENU_ANCHOR_BUBBLE_RIGHT)
x = owner_bounds.right() - kBubbleTipSizeLeftRight; x = owner_bounds.right() - kBubbleTipSizeLeftRight;
......
...@@ -310,6 +310,8 @@ void MenuScrollViewContainer::CreateBubbleBorder() { ...@@ -310,6 +310,8 @@ void MenuScrollViewContainer::CreateBubbleBorder() {
->use_touchable_layout()) { ->use_touchable_layout()) {
const MenuConfig& menu_config = MenuConfig::instance(); const MenuConfig& menu_config = MenuConfig::instance();
bubble_border_->SetCornerRadius(menu_config.touchable_corner_radius); bubble_border_->SetCornerRadius(menu_config.touchable_corner_radius);
bubble_border_->set_md_shadow_elevation(
menu_config.touchable_menu_shadow_elevation);
scroll_view_->GetContents()->SetBorder(CreateEmptyBorder( scroll_view_->GetContents()->SetBorder(CreateEmptyBorder(
gfx::Insets(menu_config.vertical_touchable_menu_item_padding, 0))); gfx::Insets(menu_config.vertical_touchable_menu_item_padding, 0)));
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment