blob: 49d1f469771a1038379a1e9eeb393b28541fbad6 [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_window_view.h"
#include <string>
#include <utility>
#include "base/bind.h"
#include "base/i18n/number_formatting.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/chromeos/input_method/assistive_window_properties.h"
#include "chrome/browser/chromeos/input_method/ui/assistive_delegate.h"
#include "chrome/browser/chromeos/input_method/ui/border_factory.h"
#include "chrome/browser/chromeos/input_method/ui/suggestion_details.h"
#include "chrome/browser/chromeos/input_method/ui/suggestion_view.h"
#include "chrome/grit/generated_resources.h"
#include "components/strings/grit/components_strings.h"
#include "components/vector_icons/vector_icons.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/base/ui_base_types.h"
#include "ui/gfx/color_palette.h"
#include "ui/gfx/font.h"
#include "ui/gfx/font_list.h"
#include "ui/gfx/geometry/insets.h"
#include "ui/gfx/paint_vector_icon.h"
#include "ui/gfx/text_constants.h"
#include "ui/native_theme/native_theme.h"
#include "ui/native_theme/native_theme_color_id.h"
#include "ui/views/background.h"
#include "ui/views/border.h"
#include "ui/views/bubble/bubble_border.h"
#include "ui/views/bubble/bubble_frame_view.h"
#include "ui/views/controls/button/image_button.h"
#include "ui/views/controls/link.h"
#include "ui/views/layout/box_layout.h"
#include "ui/views/layout/layout_provider.h"
#include "ui/views/widget/widget.h"
#include "ui/wm/core/window_animations.h"
#include "ui/wm/core/window_properties.h"
namespace ui {
namespace ime {
namespace {
bool ShouldHighlight(const views::Button& button) {
return button.GetState() == views::Button::STATE_HOVERED ||
button.GetState() == views::Button::STATE_PRESSED;
}
// TODO(b/1101669): Create abstract HighlightableButton for learn_more button,
// setting_link_, suggestion_view and undo_view.
void SetHighlighted(views::View& view, bool highlighted) {
if (!!view.background() != highlighted) {
view.SetBackground(highlighted
? views::CreateSolidBackground(kButtonHighlightColor)
: nullptr);
}
}
} // namespace
// static
SuggestionWindowView* SuggestionWindowView::Create(
gfx::NativeView parent,
AssistiveDelegate* delegate) {
auto* const view = new SuggestionWindowView(parent, delegate);
views::Widget* const widget =
views::BubbleDialogDelegateView::CreateBubble(view);
wm::SetWindowVisibilityAnimationTransition(widget->GetNativeView(),
wm::ANIMATE_NONE);
return view;
}
std::unique_ptr<views::NonClientFrameView>
SuggestionWindowView::CreateNonClientFrameView(views::Widget* widget) {
std::unique_ptr<views::NonClientFrameView> frame =
views::BubbleDialogDelegateView::CreateNonClientFrameView(widget);
static_cast<views::BubbleFrameView*>(frame.get())
->SetBubbleBorder(GetBorderForWindow(WindowBorderType::Suggestion));
return frame;
}
void SuggestionWindowView::Show(const SuggestionDetails& details) {
ResizeCandidateArea(1);
auto* const candidate =
static_cast<SuggestionView*>(candidate_area_->children().front());
candidate->SetView(details);
if (details.show_setting_link)
candidate->SetMinWidth(setting_link_->GetPreferredSize().width());
setting_link_->SetVisible(details.show_setting_link);
MakeVisible();
}
void SuggestionWindowView::ShowMultipleCandidates(
const chromeos::AssistiveWindowProperties& properties) {
const std::vector<std::u16string>& candidates = properties.candidates;
ResizeCandidateArea(candidates.size());
for (size_t i = 0; i < candidates.size(); ++i) {
auto* const candidate =
static_cast<SuggestionView*>(candidate_area_->children()[i]);
if (properties.show_indices)
candidate->SetViewWithIndex(base::FormatNumber(i + 1), candidates[i]);
else
candidate->SetView({.text = candidates[i]});
}
learn_more_button_->SetVisible(properties.show_setting_link);
MakeVisible();
}
void SuggestionWindowView::SetButtonHighlighted(
const AssistiveWindowButton& button,
bool highlighted) {
if (button.id == ButtonId::kSuggestion) {
const views::View::Views& candidates = candidate_area_->children();
if (button.index < candidates.size()) {
SetCandidateHighlighted(
static_cast<SuggestionView*>(candidates[button.index]), highlighted);
}
} else if (button.id == ButtonId::kSmartInputsSettingLink) {
SetHighlighted(*setting_link_, highlighted);
} else if (button.id == ButtonId::kLearnMore) {
SetHighlighted(*learn_more_button_, highlighted);
}
}
void SuggestionWindowView::OnThemeChanged() {
BubbleDialogDelegateView::OnThemeChanged();
learn_more_button_->SetBorder(views::CreatePaddedBorder(
views::CreateSolidSidedBorder(
1, 0, 0, 0,
GetNativeTheme()->GetSystemColor(
ui::NativeTheme::kColorId_FootnoteContainerBorder)),
views::LayoutProvider::Get()->GetInsetsMetric(
views::INSETS_VECTOR_IMAGE_BUTTON)));
// TODO(crbug/1099044): Update and use cros colors.
constexpr SkColor kSecondaryIconColor = gfx::kGoogleGrey500;
learn_more_button_->SetImage(
views::Button::ButtonState::STATE_NORMAL,
gfx::CreateVectorIcon(vector_icons::kHelpOutlineIcon,
kSecondaryIconColor));
}
SuggestionWindowView::SuggestionWindowView(gfx::NativeView parent,
AssistiveDelegate* delegate)
: delegate_(delegate) {
DCHECK(parent);
SetButtons(ui::DIALOG_BUTTON_NONE);
SetCanActivate(false);
set_parent_window(parent);
set_margins(gfx::Insets());
SetLayoutManager(std::make_unique<views::BoxLayout>(
views::BoxLayout::Orientation::kVertical));
candidate_area_ = AddChildView(std::make_unique<views::View>());
candidate_area_->SetLayoutManager(std::make_unique<views::BoxLayout>(
views::BoxLayout::Orientation::kVertical));
setting_link_ = AddChildView(std::make_unique<views::Link>(
l10n_util::GetStringUTF16(IDS_SUGGESTION_LEARN_MORE)));
setting_link_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
// TODO(crbug/1102215): Implement proper UI layout using Insets constant.
constexpr gfx::Insets insets(0, kPadding, kPadding, kPadding);
setting_link_->SetBorder(views::CreateEmptyBorder(insets));
constexpr int kSettingLinkFontSize = 11;
setting_link_->SetFontList(gfx::FontList({kFontStyle}, gfx::Font::ITALIC,
kSettingLinkFontSize,
gfx::Font::Weight::NORMAL));
const auto on_setting_link_clicked = [](AssistiveDelegate* delegate) {
delegate->AssistiveWindowButtonClicked(
{.id = ButtonId::kSmartInputsSettingLink});
};
setting_link_->SetCallback(
base::BindRepeating(on_setting_link_clicked, delegate_));
setting_link_->SetVisible(false);
learn_more_button_ =
AddChildView(std::make_unique<views::ImageButton>(base::BindRepeating(
&AssistiveDelegate::AssistiveWindowButtonClicked,
base::Unretained(delegate_),
AssistiveWindowButton{
.id = ui::ime::ButtonId::kLearnMore,
.window_type = ui::ime::AssistiveWindowType::kEmojiSuggestion})));
learn_more_button_->SetImageHorizontalAlignment(
views::ImageButton::ALIGN_CENTER);
learn_more_button_->SetImageVerticalAlignment(
views::ImageButton::ALIGN_MIDDLE);
learn_more_button_->SetTooltipText(l10n_util::GetStringUTF16(IDS_LEARN_MORE));
const auto update_button_highlight = [](views::Button* button) {
SetHighlighted(*button, ShouldHighlight(*button));
};
auto subscription =
learn_more_button_->AddStateChangedCallback(base::BindRepeating(
update_button_highlight, base::Unretained(learn_more_button_)));
subscriptions_.insert({learn_more_button_, std::move(subscription)});
learn_more_button_->SetVisible(false);
}
SuggestionWindowView::~SuggestionWindowView() = default;
void SuggestionWindowView::ResizeCandidateArea(size_t size) {
if (highlighted_candidate_)
SetCandidateHighlighted(highlighted_candidate_, false);
const views::View::Views& candidates = candidate_area_->children();
while (candidates.size() > size) {
subscriptions_.erase(
candidate_area_->RemoveChildViewT(candidates.back()).get());
}
for (size_t index = candidates.size(); index < size; ++index) {
auto* const candidate = candidate_area_->AddChildView(
std::make_unique<SuggestionView>(base::BindRepeating(
&AssistiveDelegate::AssistiveWindowButtonClicked,
base::Unretained(delegate_),
AssistiveWindowButton{.id = ui::ime::ButtonId::kSuggestion,
.index = index})));
auto subscription = candidate->AddStateChangedCallback(base::BindRepeating(
[](SuggestionWindowView* window, SuggestionView* button) {
window->SetCandidateHighlighted(button, ShouldHighlight(*button));
},
base::Unretained(this), base::Unretained(candidate)));
subscriptions_.insert({candidate, std::move(subscription)});
}
}
void SuggestionWindowView::MakeVisible() {
candidate_area_->SetVisible(true);
SizeToContents();
}
void SuggestionWindowView::SetCandidateHighlighted(SuggestionView* candidate,
bool highlighted) {
DCHECK(candidate);
DCHECK_EQ(candidate_area_, candidate->parent());
// Can't highlight a highlighted candidate, or unhighlight an unhighlighted
// one.
if (highlighted == (candidate == highlighted_candidate_))
return;
if (highlighted && highlighted_candidate_)
highlighted_candidate_->SetHighlighted(false);
candidate->SetHighlighted(highlighted);
highlighted_candidate_ = highlighted ? candidate : nullptr;
}
BEGIN_METADATA(SuggestionWindowView, views::BubbleDialogDelegateView)
END_METADATA
} // namespace ime
} // namespace ui