blob: 471ce91911da30767b2315e17a79bc630629614f [file] [log] [blame]
// Copyright 2020 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.
#include "chrome/browser/chromeos/input_method/ui/suggestion_view.h"
#include "base/macros.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/app/vector_icons/vector_icons.h"
#include "chrome/browser/chromeos/input_method/ui/suggestion_details.h"
#include "chrome/grit/generated_resources.h"
#include "ui/accessibility/ax_enums.mojom.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/gfx/paint_vector_icon.h"
#include "ui/views/background.h"
#include "ui/views/border.h"
#include "ui/views/controls/image_view.h"
#include "ui/views/controls/label.h"
#include "ui/views/layout/box_layout.h"
#include "ui/views/widget/widget.h"
namespace ui {
namespace ime {
namespace {
const int kAnnotationLabelChildSpacing = 4;
const int kArrowIconSize = 14;
const int kDownIconHorizontalPadding = 2;
const int kDownIconSize = 16;
const int kEnterKeyHorizontalPadding = 2;
// Creates the index label, and returns it (never returns nullptr).
// The label text is not set in this function.
std::unique_ptr<views::Label> CreateIndexLabel() {
auto index_label = std::make_unique<views::Label>();
index_label->SetFontList(gfx::FontList({kFontStyle}, gfx::Font::NORMAL,
kIndexFontSize,
gfx::Font::Weight::MEDIUM));
index_label->SetEnabledColor(kSuggestionColor);
index_label->SetHorizontalAlignment(gfx::ALIGN_CENTER);
index_label->SetBorder(
views::CreateEmptyBorder(gfx::Insets(kPadding / 2, 0)));
return index_label;
}
// Creates the suggestion label, and returns it (never returns nullptr).
// The label text is not set in this function.
std::unique_ptr<views::StyledLabel> CreateSuggestionLabel() {
auto suggestion_label = std::make_unique<views::StyledLabel>();
suggestion_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
suggestion_label->SetBorder(
views::CreateEmptyBorder(gfx::Insets(kPadding / 2, 0)));
suggestion_label->SetAutoColorReadabilityEnabled(false);
// StyledLabel eats event, probably because it has to handle links.
// Explicitly sets can_process_events_within_subtree to false for
// SuggestionView's hover to work correctly.
suggestion_label->SetCanProcessEventsWithinSubtree(false);
return suggestion_label;
}
std::unique_ptr<views::ImageView> CreateDownIcon() {
auto icon = std::make_unique<views::ImageView>();
icon->SetBorder(views::CreateEmptyBorder(gfx::Insets(
0, kDownIconHorizontalPadding, 0, kDownIconHorizontalPadding)));
return icon;
}
std::unique_ptr<views::Label> CreateEnterLabel() {
auto label = std::make_unique<views::Label>();
label->SetEnabledColor(kSuggestionColor);
label->SetText(l10n_util::GetStringUTF16(IDS_SUGGESTION_ENTER_KEY));
label->SetFontList(gfx::FontList({kFontStyle}, gfx::Font::NORMAL,
kAnnotationFontSize,
gfx::Font::Weight::MEDIUM));
label->SetBorder(
views::CreateEmptyBorder(gfx::Insets(0, kEnterKeyHorizontalPadding)));
return label;
}
std::unique_ptr<views::Label> CreateTabLabel() {
auto label = std::make_unique<views::Label>();
label->SetEnabledColor(kSuggestionColor);
label->SetText(l10n_util::GetStringUTF16(IDS_SUGGESTION_TAB_KEY));
label->SetFontList(gfx::FontList({kFontStyle}, gfx::Font::NORMAL,
kAnnotationFontSize,
gfx::Font::Weight::MEDIUM));
return label;
}
std::unique_ptr<views::View> CreateKeyContainer() {
auto container = std::make_unique<views::View>();
container->SetLayoutManager(std::make_unique<views::BoxLayout>(
views::BoxLayout::Orientation::kHorizontal));
// TODO(crbug/1099044): Use color from ash_color_provider and move SetBorder
// to OnThemeChanged
const SkColor kKeyContainerBorderColor =
SkColorSetA(SK_ColorBLACK, 0x24); // 14%
container->SetBorder(views::CreateRoundedRectBorder(
kAnnotationBorderThickness, kAnnotationCornerRadius, gfx::Insets(),
kKeyContainerBorderColor));
return container;
}
} // namespace
SuggestionView::SuggestionView(PressedCallback callback)
: views::Button(std::move(callback)) {
index_label_ = AddChildView(CreateIndexLabel());
index_label_->SetVisible(false);
suggestion_label_ = AddChildView(CreateSuggestionLabel());
annotation_container_ = AddChildView(CreateAnnotationContainer());
down_and_enter_annotation_label_ =
annotation_container_->AddChildView(CreateDownAndEnterAnnotationLabel());
tab_annotation_label_ =
annotation_container_->AddChildView(CreateTabAnnotationLabel());
annotation_container_->SetVisible(false);
down_and_enter_annotation_label_->SetVisible(false);
tab_annotation_label_->SetVisible(false);
SetFocusBehavior(views::View::FocusBehavior::ACCESSIBLE_ONLY);
}
SuggestionView::~SuggestionView() = default;
std::unique_ptr<views::View> SuggestionView::CreateAnnotationContainer() {
auto label = std::make_unique<views::View>();
label->SetLayoutManager(std::make_unique<views::BoxLayout>(
views::BoxLayout::Orientation::kHorizontal));
// AnnotationLabel's ChildViews eat events simmilar to StyledLabel.
// Explicitly sets can_process_events_within_subtree to false for
// AnnotationLabel's hover to work correctly.
label->SetCanProcessEventsWithinSubtree(false);
return label;
}
std::unique_ptr<views::View>
SuggestionView::CreateDownAndEnterAnnotationLabel() {
auto label = std::make_unique<views::View>();
label->SetBorder(
views::CreateEmptyBorder(gfx::Insets(0, kAnnotationPaddingLeft, 0, 0)));
label
->SetLayoutManager(std::make_unique<views::BoxLayout>(
views::BoxLayout::Orientation::kHorizontal))
->set_between_child_spacing(kAnnotationLabelChildSpacing);
down_icon_ =
label->AddChildView(CreateKeyContainer())->AddChildView(CreateDownIcon());
arrow_icon_ = label->AddChildView(std::make_unique<views::ImageView>());
label->AddChildView(CreateKeyContainer())->AddChildView(CreateEnterLabel());
return label;
}
std::unique_ptr<views::View> SuggestionView::CreateTabAnnotationLabel() {
auto label = std::make_unique<views::View>();
label->SetBorder(
views::CreateEmptyBorder(gfx::Insets(0, kAnnotationPaddingLeft, 0, 0)));
label->SetLayoutManager(std::make_unique<views::BoxLayout>(
views::BoxLayout::Orientation::kHorizontal));
label->AddChildView(CreateTabLabel());
return label;
}
void SuggestionView::SetView(const SuggestionDetails& details) {
SetSuggestionText(details.text, details.confirmed_length, details.text_color);
suggestion_width_ = suggestion_label_->GetPreferredSize().width();
down_and_enter_annotation_label_->SetVisible(details.show_accept_annotation);
tab_annotation_label_->SetVisible(details.show_quick_accept_annotation);
annotation_container_->SetVisible(details.show_accept_annotation ||
details.show_quick_accept_annotation);
}
void SuggestionView::SetViewWithIndex(const std::u16string& index,
const std::u16string& text) {
index_label_->SetText(index);
index_label_->SetVisible(true);
index_width_ = index_label_->GetPreferredSize().width();
suggestion_label_->SetText(text);
suggestion_width_ = suggestion_label_->GetPreferredSize().width();
}
void SuggestionView::SetSuggestionText(const std::u16string& text,
const size_t confirmed_length,
SkColor text_color) {
// SetText clears the existing style only if the text to set is different from
// the previous one.
suggestion_label_->SetText(base::EmptyString16());
suggestion_label_->SetText(text);
gfx::FontList kSuggestionFont({kFontStyle}, gfx::Font::NORMAL,
kSuggestionFontSize, gfx::Font::Weight::NORMAL);
if (confirmed_length != 0) {
views::StyledLabel::RangeStyleInfo confirmed_style;
confirmed_style.custom_font = kSuggestionFont;
confirmed_style.override_color = kConfirmedTextColor;
suggestion_label_->AddStyleRange(gfx::Range(0, confirmed_length),
confirmed_style);
}
views::StyledLabel::RangeStyleInfo suggestion_style;
suggestion_style.custom_font = kSuggestionFont;
suggestion_style.override_color = text_color;
suggestion_label_->AddStyleRange(gfx::Range(confirmed_length, text.length()),
suggestion_style);
// TODO(crbug/1099146): Add tests to check view's height and width with
// confirmed length.
// Maximum width for suggestion.
suggestion_label_->SizeToFit(448);
}
void SuggestionView::SetHighlighted(bool highlighted) {
if (highlighted_ == highlighted)
return;
highlighted_ = highlighted;
if (highlighted) {
NotifyAccessibilityEvent(ax::mojom::Event::kSelection, false);
// TODO(crbug/1099044): Use System Color for button highlight.
SetBackground(views::CreateSolidBackground(kButtonHighlightColor));
} else {
SetBackground(nullptr);
}
SchedulePaint();
}
void SuggestionView::OnThemeChanged() {
down_icon_->SetImage(
gfx::CreateVectorIcon(kKeyboardArrowDownIcon, kDownIconSize,
GetNativeTheme()->GetSystemColor(
ui::NativeTheme::kColorId_DefaultIconColor)));
arrow_icon_->SetImage(
gfx::CreateVectorIcon(kKeyboardArrowRightIcon, kArrowIconSize,
GetNativeTheme()->GetSystemColor(
ui::NativeTheme::kColorId_DefaultIconColor)));
views::View::OnThemeChanged();
}
void SuggestionView::Layout() {
int left = kPadding;
if (index_label_->GetVisible()) {
index_label_->SetBounds(left, 0, index_width_, height());
left += index_width_ + kPadding;
}
suggestion_label_->SetBounds(left, 0, suggestion_width_, height());
if (annotation_container_->GetVisible()) {
int annotation_left = left + suggestion_width_;
int container_right = bounds().right();
int annotation_width = container_right - annotation_left - kPadding;
annotation_container_->SetBounds(annotation_left, kAnnotationPaddingTop,
annotation_width,
kAnnotationPaddingBottom);
}
}
gfx::Size SuggestionView::CalculatePreferredSize() const {
gfx::Size size;
if (index_label_->GetVisible()) {
size = index_label_->GetPreferredSize();
size.SetToMax(gfx::Size(index_width_, 0));
size.Enlarge(kPadding, 0);
}
gfx::Size suggestion_size = suggestion_label_->GetPreferredSize();
suggestion_size.SetToMax(gfx::Size(suggestion_width_, 0));
size.Enlarge(suggestion_size.width() + 2 * kPadding, 0);
size.SetToMax(suggestion_size);
if (annotation_container_->GetVisible()) {
gfx::Size annotation_size = annotation_container_->GetPreferredSize();
size.Enlarge(annotation_size.width(), 0);
}
if (min_width_ > size.width())
size.Enlarge(min_width_ - size.width(), 0);
return size;
}
void SuggestionView::SetMinWidth(int min_width) {
min_width_ = min_width;
}
std::u16string SuggestionView::GetSuggestionForTesting() {
return suggestion_label_->GetText();
}
BEGIN_METADATA(SuggestionView, views::Button)
END_METADATA
} // namespace ime
} // namespace ui