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.

#include "ui/views/examples/button_example.h"

#include <memory>
#include <utility>

#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/metadata/metadata_header_macros.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/gfx/geometry/insets.h"
#include "ui/gfx/image/image.h"
#include "ui/gfx/scoped_canvas.h"
#include "ui/gfx/skia_paint_util.h"
#include "ui/views/accessibility/view_accessibility.h"
#include "ui/views/animation/ink_drop.h"
#include "ui/views/animation/ink_drop_highlight.h"
#include "ui/views/background.h"
#include "ui/views/controls/button/image_button.h"
#include "ui/views/controls/button/label_button.h"
#include "ui/views/controls/button/md_text_button.h"
#include "ui/views/controls/button/md_text_button_with_down_arrow.h"
#include "ui/views/examples/examples_color_id.h"
#include "ui/views/examples/examples_window.h"
#include "ui/views/examples/grit/views_examples_resources.h"
#include "ui/views/layout/box_layout.h"
#include "ui/views/layout/box_layout_view.h"
#include "ui/views/layout/fill_layout.h"
#include "ui/views/metadata/view_factory.h"
#include "ui/views/resources/grit/views_resources.h"
#include "ui/views/style/platform_style.h"
#include "ui/views/vector_icons.h"
#include "ui/views/view.h"
#include "ui/views/view_utils.h"

using base::ASCIIToUTF16;

namespace {
const char16_t kLabelButton[] = u"Label Button";
const char16_t kLongText[] =
    u"Start of Really Really Really Really Really Really "
    u"Really Really Really Really Really Really Really "
    u"Really Really Really Really Really Long Button Text";
}  // namespace

namespace views::examples {

// Creates a rounded rect with a border plus shadow. This is used by FabButton
// to draw the button background.
class SolidRoundRectPainterWithShadow : public Painter {
 public:
  SolidRoundRectPainterWithShadow(SkColor bg_color,
                                  SkColor stroke_color,
                                  const gfx::RoundedCornersF& corner_radii,
                                  const gfx::Insets& insets,
                                  SkBlendMode blend_mode,
                                  bool antialias,
                                  bool has_shadow)
      : bg_color_(bg_color),
        stroke_color_(stroke_color),
        corner_radii_(corner_radii),
        insets_(insets),
        blend_mode_(blend_mode),
        antialias_(antialias),
        has_shadow_(has_shadow) {}

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

  ~SolidRoundRectPainterWithShadow() override = default;

  // Painter:
  gfx::Size GetMinimumSize() const override { return gfx::Size(); }
  void Paint(gfx::Canvas* canvas, const gfx::Size& size) override {
    gfx::ScopedCanvas scoped_canvas(canvas);
    const float scale = canvas->UndoDeviceScaleFactor();
    // Taking one of the radiuses and using it. |DrawRoundRect()| doesn't
    // support setting radii separately.
    float scaled_radius = corner_radii_.upper_left() * scale;

    gfx::Rect inset_rect(size);
    inset_rect.Inset(insets_);
    cc::PaintFlags flags;
    // Draw a shadow effect by shrinking the rect and then inserting a
    // shadow looper.
    if (has_shadow_) {
      gfx::Rect shadow_bounds = inset_rect;
      gfx::ShadowValues shadow;
      constexpr int kOffset = 2;
      constexpr int kBlur = 4;
      shadow.emplace_back(gfx::Vector2d(kOffset, kOffset), kBlur,
                          SkColorSetA(SK_ColorBLACK, 0x24));
      shadow_bounds.Inset(-gfx::ShadowValue::GetMargin(shadow));
      inset_rect.Inset(-gfx::ShadowValue::GetMargin(shadow));
      flags.setAntiAlias(true);
      flags.setLooper(gfx::CreateShadowDrawLooper(shadow));
      canvas->DrawRoundRect(shadow_bounds, scaled_radius, flags);
    }

    gfx::RectF fill_rect(gfx::ScaleToEnclosingRect(inset_rect, scale));
    gfx::RectF stroke_rect = fill_rect;

    flags.setBlendMode(blend_mode_);
    flags.setAntiAlias(antialias_);
    flags.setStyle(cc::PaintFlags::kFill_Style);
    flags.setColor(bg_color_);
    canvas->DrawRoundRect(fill_rect, scaled_radius, flags);

    if (stroke_color_ != SK_ColorTRANSPARENT && !has_shadow_) {
      constexpr float kStrokeWidth = 1.0f;
      stroke_rect.Inset(gfx::InsetsF(kStrokeWidth / 2));
      scaled_radius -= kStrokeWidth / 2;
      flags.setStyle(cc::PaintFlags::kStroke_Style);
      flags.setStrokeWidth(kStrokeWidth);
      flags.setColor(stroke_color_);
      canvas->DrawRoundRect(stroke_rect, scaled_radius, flags);
    }
  }

 private:
  const SkColor bg_color_;
  const SkColor stroke_color_;
  const gfx::RoundedCornersF corner_radii_;
  const gfx::Insets insets_;
  const SkBlendMode blend_mode_;
  const bool antialias_;
  const bool has_shadow_;
};

// Floating Action Button (Fab) is a button that has a shadow around the button
// to simulate a floating effect. This class is not used officially in the Views
// library. This is a prototype of a potential way to implement such an effect
// by overriding the hover effect to draw a new background with a shadow.
class FabButton : public views::MdTextButton {
  METADATA_HEADER(FabButton, views::MdTextButton)

 public:
  using MdTextButton::MdTextButton;
  FabButton(const FabButton&) = delete;
  FabButton& operator=(const FabButton&) = delete;
  ~FabButton() override = default;

  void UpdateBackgroundColor() override {
    SkColor bg_color = GetColorProvider()->GetColor(
        ExamplesColorIds::kColorButtonBackgroundFab);
    SetBackground(CreateBackgroundFromPainter(
        std::make_unique<SolidRoundRectPainterWithShadow>(
            bg_color, SK_ColorTRANSPARENT, GetCornerRadii(), gfx::Insets(),
            SkBlendMode::kSrcOver, true, use_shadow_)));
  }

  void OnHoverChanged() {
    use_shadow_ = !use_shadow_;
    UpdateBackgroundColor();
  }

  void OnThemeChanged() override {
    MdTextButton::OnThemeChanged();
    UpdateBackgroundColor();
  }

 private:
  base::CallbackListSubscription highlighted_changed_subscription_ =
      InkDrop::Get(this)->AddHighlightedChangedCallback(
          base::BindRepeating([](FabButton* host) { host->OnHoverChanged(); },
                              base::Unretained(this)));
  bool use_shadow_ = false;
};

BEGIN_METADATA(FabButton)
END_METADATA

ButtonExample::ButtonExample() : ExampleBase("Button") {
  ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
  icon_ = rb.GetImageNamed(IDR_CLOSE_H).ToImageSkia();
}

ButtonExample::~ButtonExample() = default;

void ButtonExample::CreateExampleView(View* container) {
  container->SetUseDefaultFillLayout(true);

  auto view = Builder<BoxLayoutView>()
                  .SetOrientation(BoxLayout::Orientation::kVertical)
                  .SetInsideBorderInsets(gfx::Insets(10))
                  .SetBetweenChildSpacing(10)
                  .SetCrossAxisAlignment(BoxLayout::CrossAxisAlignment::kCenter)
                  .AddChildren(Builder<LabelButton>()
                                   .CopyAddressTo(&label_button_)
                                   .SetText(kLabelButton)
                                   .SetRequestFocusOnPress(true)
                                   .SetCallback(base::BindRepeating(
                                       &ButtonExample::LabelButtonPressed,
                                       base::Unretained(this), label_button_)),
                               Builder<MdTextButton>()
                                   .CopyAddressTo(&md_button_)
                                   .SetText(u"Material Design"),
                               Builder<MdTextButton>()
                                   .CopyAddressTo(&md_disabled_button_)
                                   .SetText(u"Material Design Disabled Button")
                                   .SetState(Button::STATE_DISABLED),
                               Builder<MdTextButton>()
                                   .CopyAddressTo(&md_default_button_)
                                   .SetText(u"Default")
                                   .SetIsDefault(true),
                               Builder<MdTextButton>()
                                   .CopyAddressTo(&md_tonal_button_)
                                   .SetStyle(ui::ButtonStyle::kTonal)
                                   .SetText(u"Tonal"),
                               Builder<MdTextButton>()
                                   .CopyAddressTo(&md_text_button_)
                                   .SetStyle(ui::ButtonStyle::kText)
                                   .SetText(u"Material Text"),
                               Builder<MdTextButton>()
                                   .CopyAddressTo(&md_icon_text_button_)
                                   .SetText(u"Material Text with Icon"),
                               Builder<ImageButton>()
                                   .CopyAddressTo(&image_button_)
                                   .SetAccessibleName(l10n_util::GetStringUTF16(
                                       IDS_BUTTON_IMAGE_BUTTON_AX_LABEL))
                                   .SetRequestFocusOnPress(true)
                                   .SetCallback(base::BindRepeating(
                                       &ButtonExample::ImageButtonPressed,
                                       base::Unretained(this))))
                  .Build();
  md_icon_text_button_->SetImageModel(
      views::Button::ButtonState::STATE_NORMAL,
      ui::ImageModel::FromVectorIcon(views::kInfoIcon));
  view->AddChildView(std::make_unique<FabButton>(
      base::BindRepeating(&ButtonExample::ImageButtonPressed,
                          base::Unretained(this)),
      u"Fab Prototype"));

  view->AddChildView(ImageButton::CreateIconButton(
      base::BindRepeating(&ButtonExample::ImageButtonPressed,
                          base::Unretained(this)),
      views::kLaunchIcon, u"Icon button"));
  view->AddChildView(std::make_unique<views::MdTextButtonWithDownArrow>(
      base::BindRepeating(&ButtonExample::ImageButtonPressed,
                          base::Unretained(this)),
      u"TextButton with down arrow"));

  image_button_->SetImageModel(ImageButton::STATE_NORMAL,
                               ui::ImageModel::FromResourceId(IDR_CLOSE));
  image_button_->SetImageModel(ImageButton::STATE_HOVERED,
                               ui::ImageModel::FromResourceId(IDR_CLOSE_H));
  image_button_->SetImageModel(ImageButton::STATE_PRESSED,
                               ui::ImageModel::FromResourceId(IDR_CLOSE_P));

  container->AddChildView(std::move(view));
}

void ButtonExample::LabelButtonPressed(LabelButton* label_button,
                                       const ui::Event& event) {
  PrintStatus(base::StringPrintf("Label Button Pressed! count: %d", ++count_));
  if (event.IsControlDown()) {
    if (event.IsShiftDown()) {
      label_button->SetText(label_button->GetText().empty() ? kLongText
                            : label_button->GetText().length() > 50
                                ? kLabelButton
                                : u"");
    } else if (event.IsAltDown()) {
      label_button->SetImageModel(
          Button::STATE_NORMAL,
          label_button->GetImage(Button::STATE_NORMAL).isNull()
              ? ui::ImageModel::FromImageSkia(*icon_)
              : ui::ImageModel());
    } else {
      static int alignment = 0;
      label_button->SetHorizontalAlignment(
          static_cast<gfx::HorizontalAlignment>(++alignment % 3));
    }
  } else if (event.IsShiftDown()) {
    if (event.IsAltDown()) {
      // Toggle focusability.
      label_button->GetViewAccessibility().IsAccessibilityFocusable()
          ? label_button->SetFocusBehavior(View::FocusBehavior::NEVER)
          : label_button->SetFocusBehavior(
                PlatformStyle::kDefaultFocusBehavior);
    }
  } else if (event.IsAltDown()) {
    label_button->SetIsDefault(!label_button->GetIsDefault());
  }
  example_view()->GetLayoutManager()->Layout(example_view());
  LOG(ERROR) << '\n' << PrintViewHierarchy(example_view());
}

void ButtonExample::ImageButtonPressed() {
  PrintStatus(base::StringPrintf("Image Button Pressed! count: %d", ++count_));
}

}  // namespace views::examples