blob: 21d07d69eccacdfd5d8d68023c25110b9786f493 [file] [log] [blame]
// 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