910e62b5创建于 1月15日历史提交
// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef UI_VIEWS_BUBBLE_BUBBLE_BORDER_H_
#define UI_VIEWS_BUBBLE_BUBBLE_BORDER_H_

#include <optional>
#include <utility>

#include "base/gtest_prod_util.h"
#include "base/memory/raw_ptr.h"
#include "build/build_config.h"
#include "third_party/skia/include/core/SkColor.h"
#include "third_party/skia/include/core/SkRRect.h"
#include "ui/base/metadata/base_type_conversion.h"
#include "ui/color/color_id.h"
#include "ui/color/color_variant.h"
#include "ui/gfx/color_palette.h"
#include "ui/gfx/geometry/insets.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/rounded_corners_f.h"
#include "ui/views/background.h"
#include "ui/views/border.h"
#include "ui/views/views_export.h"

class SkRRect;
struct SkRect;

namespace gfx {
class Canvas;
}

namespace ui {
class ColorProvider;
}

namespace views {

// Renders a border, with optional arrow, and a custom dropshadow.
// This can be used to produce floating "bubble" objects with rounded corners.
class VIEWS_EXPORT BubbleBorder : public Border {
 public:
  // Possible locations for the (optional) arrow.
  // 0 bit specifies left or right.
  // 1 bit specifies top or bottom.
  // 2 bit specifies horizontal or vertical.
  // 3 bit specifies whether the arrow at the center of its residing edge.
  enum ArrowMask {
    RIGHT = 0x01,
    BOTTOM = 0x02,
    VERTICAL = 0x04,
    CENTER = 0x08,
  };

  enum Arrow {
    TOP_LEFT = 0,
    TOP_RIGHT = RIGHT,
    BOTTOM_LEFT = BOTTOM,
    BOTTOM_RIGHT = BOTTOM | RIGHT,
    LEFT_TOP = VERTICAL,
    RIGHT_TOP = VERTICAL | RIGHT,
    LEFT_BOTTOM = VERTICAL | BOTTOM,
    RIGHT_BOTTOM = VERTICAL | BOTTOM | RIGHT,
    TOP_CENTER = CENTER,
    BOTTOM_CENTER = CENTER | BOTTOM,
    LEFT_CENTER = CENTER | VERTICAL,
    RIGHT_CENTER = CENTER | VERTICAL | RIGHT,
    NONE = 16,   // No arrow. Positioned under the supplied rect.
    FLOAT = 17,  // No arrow. Centered over the supplied rect.
  };

  enum Shadow {
    STANDARD_SHADOW = 0,
#if BUILDFLAG(IS_CHROMEOS)
    // CHROMEOS_SYSTEM_UI_SHADOW uses ChromeOS system UI shadow style.
    CHROMEOS_SYSTEM_UI_SHADOW,
#endif
    // NO_SHADOW don't draw a stroke or a shadow. This is used for platforms
    // that provide their own shadows or UIs that doesn't need shadows.
    NO_SHADOW,
    SHADOW_COUNT,

#if BUILDFLAG(IS_MAC)
    // On Mac, the native window server should provide its own shadow for
    // windows that could overlap the browser window.
    DIALOG_SHADOW = NO_SHADOW,
#elif BUILDFLAG(IS_CHROMEOS)
    DIALOG_SHADOW = CHROMEOS_SYSTEM_UI_SHADOW,
#else
    DIALOG_SHADOW = STANDARD_SHADOW,
#endif
  };

  // The border is stroked at 1px, but for the purposes of reserving space we
  // have to deal in dip coordinates, so round up to 1dip.
  static constexpr int kBorderThicknessDip = 1;

  // Specific to MD bubbles: size of shadow blur (outside the bubble) in DIP.
  static constexpr int kShadowBlur = 6;

  // Space between the anchor view and a visible arrow if one is present.
  static constexpr int kVisibleArrowGap = 4;

  // Length of the visible arrow (distance from the bubble to the tip of the
  // arrow) if one is present.
  static constexpr int kVisibleArrowLength = 8;

  // Radius (half-width) of the visible arrow, when one is present.
  static constexpr int kVisibleArrowRadius = 9;

  // Distance between the edge of the bubble widget and the edge of the visible
  // arrow if one is present.
  static constexpr int kVisibleArrowBuffer = 12;

  BubbleBorder(Arrow arrow, Shadow shadow);

  BubbleBorder(const BubbleBorder&) = delete;
  BubbleBorder& operator=(const BubbleBorder&) = delete;

  ~BubbleBorder() override;

  static bool has_arrow(Arrow a) { return a < NONE; }

  static bool is_arrow_on_left(Arrow a) {
    return has_arrow(a) && (a == LEFT_CENTER || !(a & (RIGHT | CENTER)));
  }

  static bool is_arrow_on_top(Arrow a) {
    return has_arrow(a) && (a == TOP_CENTER || !(a & (BOTTOM | CENTER)));
  }

  static bool is_arrow_on_horizontal(Arrow a) {
    return a >= NONE ? false : !(int{a} & VERTICAL);
  }

  static bool is_arrow_at_center(Arrow a) {
    return has_arrow(a) && !!(int{a} & CENTER);
  }

  static Arrow horizontal_mirror(Arrow a) {
    return (a == TOP_CENTER || a == BOTTOM_CENTER || a >= NONE)
               ? a
               : static_cast<Arrow>(int{a} ^ RIGHT);
  }

  static Arrow vertical_mirror(Arrow a) {
    return (a == LEFT_CENTER || a == RIGHT_CENTER || a >= NONE)
               ? a
               : static_cast<Arrow>(int{a} ^ BOTTOM);
  }

  // Returns the insets required by a border and shadow based on
  // |shadow_elevation|. This is only used for MD bubbles. A null
  // |shadow_elevation| will yield the default BubbleBorder MD insets.
  static gfx::Insets GetBorderAndShadowInsets(
      const std::optional<int>& shadow_elevation = std::nullopt,
      const std::optional<bool>& draw_border_stroke = std::nullopt,
      Shadow shadow_type = Shadow::STANDARD_SHADOW);

  // Draws a border and shadow outside the |rect| on |canvas|. |color_provider|
  // is passed into GetBorderAndShadowFlags to obtain the shadow color.
  static void DrawBorderAndShadow(SkRect rect,
                                  gfx::Canvas* canvas,
                                  const ui::ColorProvider* color_provider);

  void set_rounded_corners(const gfx::RoundedCornersF& radii) {
    rounded_corners_ = radii;
  }
  const gfx::RoundedCornersF& rounded_corners() const {
    return rounded_corners_;
  }

  // Get or set the arrow type.
  void set_arrow(Arrow arrow) { arrow_ = arrow; }
  Arrow arrow() const { return arrow_; }

  void set_visible_arrow(bool visible_arrow) { visible_arrow_ = visible_arrow; }
  bool visible_arrow() const { return visible_arrow_; }

  // Sets whether to draw the border stroke. Passing `nullopt` uses the default
  // behavior (see comments on `ShouldDrawStroke()` below).
  void set_draw_border_stroke(std::optional<bool> draw_border_stroke) {
    draw_border_stroke_ = std::move(draw_border_stroke);
  }

  // Get the shadow type.
  Shadow shadow() const { return shadow_; }

  // Sets a desired pixel distance between the arrow tip and the outside edge of
  // the neighboring border image. For example:        |----offset----|
  // '(' represents shadow around the '{' edge:        ((({           ^   })))
  // The arrow will still anchor to the same location but the bubble will shift
  // location to place the arrow |offset| pixels from the perpendicular edge.
  void set_arrow_offset(int offset) { arrow_offset_ = offset; }
  int arrow_offset() const { return arrow_offset_; }

  // 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) {
    md_shadow_elevation_ = shadow_elevation;
  }

  // Set a flag to avoid the bubble's shadow overlapping the anchor.
  void set_avoid_shadow_overlap(bool value) { avoid_shadow_overlap_ = value; }

  // Sets an explicit insets value to be used.
  void set_insets(const gfx::Insets& insets) { insets_ = insets; }

  // Get the desired widget bounds (in screen coordinates) given the anchor rect
  // and bubble content size; calculated from shadow and arrow image dimensions.
  virtual gfx::Rect GetBounds(const gfx::Rect& anchor_rect,
                              const gfx::Size& contents_size) const;

  // Overridden from Border:
  void Paint(const View& view, gfx::Canvas* canvas) override;
  gfx::Insets GetInsets() const override;
  gfx::Size GetMinimumSize() const override;
  void OnViewThemeChanged(View* view) override;

  // Sets and activates the visible `arrow`. The position of the visible arrow
  // on the edge of the `popup_bounds` is determined using the
  // `anchor_rect`. While the side of the arrow is already determined by
  // `arrow`, the placement along the side is chosen to point towards the
  // `anchor_rect`. For a horizontal bubble with an arrow on either the left
  // or right side, the arrow is placed to point towards the vertical center of
  // `anchor_rect`. For a vertical arrow that is either on top of below the
  // bubble, the placement depends on the specifics of `arrow`:
  //
  //  * A right-aligned arrow (TOP_RIGHT, BOTTOM_RIGHT) optimizes the arrow
  //  position to point at the right edge of the `element_bounds`.
  //  * A center-aligned arrow (TOP_CENTER, BOTTOM_CENTER) points towards the
  //  horizontal center of `element_bounds`.
  //  * Otherwise, the arrow points towards the left edge of `element_bounds`.
  //
  // If it is not possible for the arrow to point towards the targeted point
  // because there is no overlap between the bubble and the element in the
  // significant direction, the arrow is placed at the most extreme allowed
  // position that is closest to the targeted point.
  //
  // Note that `popup_bounds` can be slightly shifted to accommodate appended
  // arrow and make the whole popup visually pointing to the anchor element.
  // `popup_min_y` limits this shift, which can be used to prevent overlapping
  // the browser top elements (e.g., the address bar). The `popup_bounds`
  // initial value is expected to not violate the `popup_min_y` restriction.
  //
  // Returns false if the arrow cannot be added due to missing space on the
  // bubble border.
  bool AddArrowToBubbleCornerAndPointTowardsAnchor(const gfx::Rect& anchor_rect,
                                                   gfx::Rect& popup_bounds,
                                                   int popup_min_y);

  // Returns a constant reference to the |visible_arrow_rect_| for teseting
  // purposes.
  const gfx::Rect& GetVisibibleArrowRectForTesting() {
    return visible_arrow_rect_;
  }

 private:
  FRIEND_TEST_ALL_PREFIXES(BubbleBorderTest, GetSizeForContentsSizeTest);
  FRIEND_TEST_ALL_PREFIXES(BubbleBorderTest, GetBoundsOriginTest);
  FRIEND_TEST_ALL_PREFIXES(BubbleBorderTest, ShadowTypes);
  FRIEND_TEST_ALL_PREFIXES(BubbleBorderTest, VisibleArrowSizesAreConsistent);
  FRIEND_TEST_ALL_PREFIXES(BubbleBorderTest,
                           MoveContentsBoundsToPlaceVisibleArrow);

  // Returns the translation vector for a bubble to make space for
  // inserting the visible arrow at the right position for |arrow_|.
  // |include_gap| controls if the displacement accounts for the
  // kVisibleArrowGap.
  static gfx::Vector2d GetContentsBoundsOffsetToPlaceVisibleArrow(
      BubbleBorder::Arrow arrow,
      bool include_gap = true);

  // The border and arrow stroke size used in image assets, in pixels.
  static constexpr int kStroke = 1;

  gfx::Size GetSizeForContentsSize(const gfx::Size& contents_size) const;

  // Calculates and assigns the |visible_arrow_rect_| for the given
  // |contents_bounds| and the |anchor_point| in which the arrow is rendered to.
  void CalculateVisibleArrowRect(const gfx::Rect& contents_bounds,
                                 const gfx::Point& anchor_point) const;

  // Returns the region within |view| representing the client area. This can be
  // set as a canvas clip to ensure any fill or shadow from the border does not
  // draw over the contents of the bubble.
  SkRRect GetClientRect(const View& view) const;

  // Returns whether to draw the border stroke. By default the stroke is drawn
  // iff there is a visible shadow and it does not have a custom elevation.
  bool ShouldDrawStroke() const;

  // Paint for the NO_SHADOW shadow type. This just paints transparent pixels
  // to make the window shape based on insets and GetBorderCornerRadius().
  void PaintNoShadow(const View& view, gfx::Canvas* canvas);

  // Paint a visible arrow pointing to the anchor region.
  void PaintVisibleArrow(const View& view, gfx::Canvas* canvas);

  Arrow arrow_;
  int arrow_offset_ = 0;

  gfx::RoundedCornersF rounded_corners_;

  // Whether a visible arrow should be present.
  bool visible_arrow_ = false;
  // Cached arrow bounding box, calculated when bounds are calculated.
  mutable gfx::Rect visible_arrow_rect_;

  std::optional<bool> draw_border_stroke_;
  Shadow shadow_;
  std::optional<int> md_shadow_elevation_;
  bool avoid_shadow_overlap_ = false;
  std::optional<gfx::Insets> insets_;
};

// A Background that clips itself to the specified BubbleBorder and uses the
// color of the BubbleBorder.
class VIEWS_EXPORT BubbleBackground : public Background {
 public:
  explicit BubbleBackground(BubbleBorder* border) : border_(border) {}

  BubbleBackground(const BubbleBackground&) = delete;
  BubbleBackground& operator=(const BubbleBackground&) = delete;

  // Overridden from Background:
  void Paint(gfx::Canvas* canvas, View* view) const override;

 private:
  const raw_ptr<BubbleBorder> border_;
};

}  // namespace views

EXPORT_ENUM_CONVERTERS(views::BubbleBorder::Arrow, VIEWS_EXPORT)

#endif  // UI_VIEWS_BUBBLE_BUBBLE_BORDER_H_