chromium/chrome/browser/ui/ash/input_method/grammar_suggestion_window.cc

// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/ui/ash/input_method/grammar_suggestion_window.h"

#include "chrome/browser/ui/ash/input_method/border_factory.h"
#include "chrome/browser/ui/ash/input_method/colors.h"
#include "chrome/browser/ui/ash/input_method/suggestion_details.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/base/mojom/dialog_button.mojom.h"
#include "ui/chromeos/styles/cros_styles.h"
#include "ui/gfx/color_palette.h"
#include "ui/gfx/paint_vector_icon.h"
#include "ui/views/bubble/bubble_border.h"
#include "ui/views/bubble/bubble_frame_view.h"
#include "ui/views/layout/box_layout.h"
#include "ui/views/layout/layout_provider.h"
#include "ui/views/vector_icons.h"
#include "ui/wm/core/window_animations.h"

namespace ui {
namespace ime {

namespace {

constexpr int kGrammarPaddingSize = 4;
constexpr float kSuggestionBorderRadius = 2;
// Large enough to make the background a circle.
constexpr float kIconBorderRadius = 100;
constexpr int kWindowOffsetY = -4;

bool ShouldHighlight(const views::Button& button) {
  return button.GetState() == views::Button::STATE_HOVERED ||
         button.GetState() == views::Button::STATE_PRESSED;
}

}  // namespace

GrammarSuggestionWindow::GrammarSuggestionWindow(gfx::NativeView parent,
                                                 AssistiveDelegate* delegate)
    : delegate_(delegate) {
  DialogDelegate::SetButtons(static_cast<int>(ui::mojom::DialogButton::kNone));
  SetCanActivate(false);
  DCHECK(parent);
  set_parent_window(parent);
  set_margins(gfx::Insets::TLBR(kGrammarPaddingSize, kGrammarPaddingSize,
                                kGrammarPaddingSize, kGrammarPaddingSize));

  SetArrow(views::BubbleBorder::Arrow::BOTTOM_LEFT);
  SetLayoutManager(std::make_unique<views::BoxLayout>(
      views::BoxLayout::Orientation::kHorizontal));

  suggestion_button_ = AddChildView(
      std::make_unique<CompletionSuggestionView>(base::BindRepeating(
          &AssistiveDelegate::AssistiveWindowButtonClicked,
          base::Unretained(delegate_),
          AssistiveWindowButton{
              .id = ui::ime::ButtonId::kSuggestion,
              .window_type =
                  ash::ime::AssistiveWindowType::kGrammarSuggestion})));
  suggestion_button_->SetBackground(nullptr);
  suggestion_button_->SetFocusBehavior(views::View::FocusBehavior::NEVER);
  suggestion_button_->SetVisible(true);

  ignore_button_ =
      AddChildView(std::make_unique<views::ImageButton>(base::BindRepeating(
          &AssistiveDelegate::AssistiveWindowButtonClicked,
          base::Unretained(delegate_),
          AssistiveWindowButton{
              .id = ui::ime::ButtonId::kIgnoreSuggestion,
              .window_type = ash::ime::AssistiveWindowType::kGrammarSuggestion,
          })));
  ignore_button_->SetImageHorizontalAlignment(views::ImageButton::ALIGN_CENTER);
  ignore_button_->SetImageVerticalAlignment(views::ImageButton::ALIGN_MIDDLE);
  ignore_button_->SetFocusBehavior(views::View::FocusBehavior::NEVER);
  ignore_button_->SetVisible(true);

  // Highlights buttons when they are hovered or pressed.
  const auto update_button_highlight = [](views::Button* button) {
    button->SetBackground(ShouldHighlight(*button)
                              ? views::CreateSolidBackground(
                                    ResolveSemanticColor(kButtonHighlightColor))
                              : nullptr);
  };
  subscriptions_.insert(
      {suggestion_button_,
       suggestion_button_->AddStateChangedCallback(base::BindRepeating(
           update_button_highlight, base::Unretained(suggestion_button_)))});
  subscriptions_.insert(
      {ignore_button_,
       ignore_button_->AddStateChangedCallback(base::BindRepeating(
           update_button_highlight, base::Unretained(ignore_button_)))});
}

GrammarSuggestionWindow::~GrammarSuggestionWindow() = default;

void GrammarSuggestionWindow::OnThemeChanged() {
  ignore_button_->SetBorder(
      views::CreateEmptyBorder(views::LayoutProvider::Get()->GetInsetsMetric(
          views::INSETS_VECTOR_IMAGE_BUTTON)));

  ignore_button_->SetImageModel(
      views::Button::ButtonState::STATE_NORMAL,
      ui::ImageModel::FromVectorIcon(
          views::kCloseIcon,
          ResolveSemanticColor(cros_styles::ColorName::kTextColorPrimary)));

  BubbleDialogDelegateView::OnThemeChanged();
}

views::Widget* GrammarSuggestionWindow::InitWidget() {
  views::Widget* widget = BubbleDialogDelegateView::CreateBubble(this);

  wm::SetWindowVisibilityAnimationTransition(widget->GetNativeView(),
                                             wm::ANIMATE_NONE);

  GetBubbleFrameView()->SetBubbleBorder(
      GetBorderForWindow(WindowBorderType::Suggestion));
  GetBubbleFrameView()->OnThemeChanged();
  return widget;
}

void GrammarSuggestionWindow::Show() {
  GetWidget()->Show();
}

void GrammarSuggestionWindow::Hide() {
  GetWidget()->Close();
}

void GrammarSuggestionWindow::SetSuggestion(const std::u16string& suggestion) {
  suggestion_button_->SetView(SuggestionDetails{.text = suggestion});
}

void GrammarSuggestionWindow::SetButtonHighlighted(
    const AssistiveWindowButton& button,
    bool highlighted) {
  if (highlighted && button.id == current_highlighted_button_id_) {
    return;
  }

  suggestion_button_->SetBackground(nullptr);
  ignore_button_->SetBackground(nullptr);

  if (highlighted) {
    switch (button.id) {
      case ButtonId::kSuggestion:
        suggestion_button_->SetBackground(views::CreateRoundedRectBackground(
            ResolveSemanticColor(kButtonHighlightColor),
            kSuggestionBorderRadius));
        break;
      case ButtonId::kIgnoreSuggestion:
        ignore_button_->SetBackground(views::CreateRoundedRectBackground(
            ResolveSemanticColor(kButtonHighlightColor), kIconBorderRadius));
        break;
      default:
        break;
    }
  }
}

void GrammarSuggestionWindow::SetBounds(gfx::Rect bounds) {
  bounds.Offset(0, kWindowOffsetY);
  SetAnchorRect(bounds);
}

CompletionSuggestionView*
GrammarSuggestionWindow::GetSuggestionButtonForTesting() {
  return suggestion_button_;
}

views::Button* GrammarSuggestionWindow::GetIgnoreButtonForTesting() {
  return ignore_button_;
}

BEGIN_METADATA(GrammarSuggestionWindow)
END_METADATA

}  // namespace ime
}  // namespace ui