chromium/ash/system/tray/hover_highlight_view.cc

// 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 "ash/system/tray/hover_highlight_view.h"

#include <string>

#include "ash/resources/vector_icons/vector_icons.h"
#include "ash/strings/grit/ash_strings.h"
#include "ash/style/ash_color_id.h"
#include "ash/style/style_util.h"
#include "ash/style/typography.h"
#include "ash/system/tray/tray_constants.h"
#include "ash/system/tray/tray_popup_utils.h"
#include "ash/system/tray/tray_utils.h"
#include "ash/system/tray/tri_view.h"
#include "ash/system/tray/unfocusable_label.h"
#include "ash/system/tray/view_click_listener.h"
#include "base/functional/bind.h"
#include "ui/accessibility/ax_enums.mojom.h"
#include "ui/accessibility/ax_node_data.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/base/models/image_model.h"
#include "ui/chromeos/styles/cros_tokens_color_mappings.h"
#include "ui/gfx/paint_vector_icon.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/animation/ink_drop_state.h"
#include "ui/views/border.h"
#include "ui/views/controls/highlight_path_generator.h"
#include "ui/views/controls/image_view.h"
#include "ui/views/controls/label.h"
#include "ui/views/layout/box_layout.h"
#include "ui/views/layout/fill_layout.h"
#include "ui/views/view_utils.h"

namespace ash {

// TODO(https://b/302232457): Rename this class since UX no longer requires a
// highlight to be shown when this view has a mouse hovered on it.
HoverHighlightView::HoverHighlightView(ViewClickListener* listener)
    : listener_(listener) {
  SetCallback(base::BindRepeating(&HoverHighlightView::PerformAction,
                                  base::Unretained(this)));

  TrayPopupUtils::ConfigureRowButtonInkdrop(views::InkDrop::Get(this));
  SetHasInkDropActionOnClick(true);
  SetFocusPainter(TrayPopupUtils::CreateFocusPainter());
  views::FocusRing::Get(this)->SetColorId(cros_tokens::kCrosSysFocusRing);
}

HoverHighlightView::~HoverHighlightView() = default;

void HoverHighlightView::AddRightIcon(const ui::ImageModel& image,
                                      int icon_size) {
  DCHECK(is_populated_);
  DCHECK(!right_view_);

  views::ImageView* right_icon = TrayPopupUtils::CreateMainImageView(
      /*use_wide_layout=*/true);
  right_icon->SetImage(image);
  AddRightView(right_icon);
}

void HoverHighlightView::AddRightView(views::View* view,
                                      std::unique_ptr<views::Border> border) {
  DCHECK(is_populated_);
  DCHECK(!right_view_);

  // When a right view is added, extra padding on the CENTER container should be
  // removed.
  tri_view_->SetContainerBorder(TriView::Container::CENTER, nullptr);

  if (border) {
    tri_view_->SetContainerBorder(TriView::Container::END, std::move(border));
  }

  right_view_ = view;
  right_view_->SetEnabled(GetEnabled());
  tri_view_->AddView(TriView::Container::END, right_view_);
  tri_view_->SetContainerVisible(TriView::Container::END, true);

  GetViewAccessibility().SetRole(ax::mojom::Role::kGenericContainer);
  GetViewAccessibility().SetName(GetViewAccessibility().GetCachedName());
  GetViewAccessibility().SetDescription(
      l10n_util::GetStringUTF16(IDS_ASH_A11Y_ROLE_BUTTON));
  SetAndUpdateAccessibleDefaultAction();
}

void HoverHighlightView::AddAdditionalRightView(views::View* view) {
  DCHECK(is_populated_);
  tri_view_->AddViewAt(TriView::Container::END, view, /*index=*/0);
}

void HoverHighlightView::SetRightViewVisible(bool visible) {
  DCHECK(is_populated_);
  if (!right_view_) {
    return;
  }

  tri_view_->SetContainerVisible(TriView::Container::END, visible);
  right_view_->SetVisible(visible);

  if (!visible ||
      (accessibility_state_ != AccessibilityState::CHECKED_CHECKBOX &&
       accessibility_state_ != AccessibilityState::UNCHECKED_CHECKBOX)) {
    GetViewAccessibility().SetRole(ax::mojom::Role::kGenericContainer);
  }

  DeprecatedLayoutImmediately();
  SetAndUpdateAccessibleDefaultAction();
}

void HoverHighlightView::SetSubText(const std::u16string& sub_text) {
  DCHECK(sub_row_);
  DCHECK(!sub_text.empty());

  if (!sub_text_label_) {
    sub_text_label_ =
        sub_row_->AddChildView(TrayPopupUtils::CreateUnfocusableLabel());
  }

  sub_text_label_->SetEnabledColorId(kColorAshTextColorSecondary);
  sub_text_label_->SetAutoColorReadabilityEnabled(false);
  sub_text_label_->SetText(sub_text);
}

void HoverHighlightView::AddIconAndLabel(const gfx::ImageSkia& image,
                                         const std::u16string& text) {
  DCHECK(!is_populated_);

  std::unique_ptr<views::ImageView> icon(TrayPopupUtils::CreateMainImageView(
      /*use_wide_layout=*/true));
  icon_ = icon.get();
  icon->SetImage(image);
  icon->SetEnabled(GetEnabled());

  AddViewAndLabel(std::move(icon), text);
}

void HoverHighlightView::AddIconAndLabel(const ui::ImageModel& image,
                                         const std::u16string& text) {
  DCHECK(!is_populated_);

  std::unique_ptr<views::ImageView> icon(TrayPopupUtils::CreateMainImageView(
      /*use_wide_layout=*/true));
  icon_ = icon.get();
  icon->SetImage(image);
  icon->SetEnabled(GetEnabled());

  AddViewAndLabel(std::move(icon), text);
}

void HoverHighlightView::AddViewAndLabel(std::unique_ptr<views::View> view,
                                         const std::u16string& text) {
  DCHECK(!is_populated_);
  DCHECK(view);
  is_populated_ = true;

  SetLayoutManager(std::make_unique<views::FillLayout>());
  tri_view_ = TrayPopupUtils::CreateDefaultRowView(
      /*use_wide_layout=*/true);
  AddChildView(tri_view_.get());

  left_view_ = view.get();
  tri_view_->AddView(TriView::Container::START, view.release());

  text_label_ = TrayPopupUtils::CreateUnfocusableLabel();
  text_label_->SetText(text);
  text_label_->SetEnabled(GetEnabled());
  text_label_->SetEnabledColorId(cros_tokens::kCrosSysOnSurface);
  TypographyProvider::Get()->StyleLabel(TypographyToken::kCrosButton2,
                                        *text_label_);
  tri_view_->AddView(TriView::Container::CENTER, text_label_);
  // By default, END container is invisible, so labels in the CENTER should have
  // an extra padding at the end.
  tri_view_->SetContainerBorder(TriView::Container::CENTER,
                                views::CreateEmptyBorder(gfx::Insets::TLBR(
                                    0, 0, 0, kTrayPopupLabelRightPadding)));
  tri_view_->SetContainerVisible(TriView::Container::END, false);

  AddSubRowContainer();

  GetViewAccessibility().SetName(text);
}

void HoverHighlightView::AddLabelRow(const std::u16string& text) {
  DCHECK(!is_populated_);
  is_populated_ = true;

  SetLayoutManager(std::make_unique<views::FillLayout>());
  tri_view_ = TrayPopupUtils::CreateDefaultRowView(
      /*use_wide_layout=*/true);
  AddChildView(tri_view_.get());

  text_label_ = TrayPopupUtils::CreateUnfocusableLabel();
  text_label_->SetText(text);
  text_label_->SetEnabledColorId(cros_tokens::kCrosSysOnSurface);
  TypographyProvider::Get()->StyleLabel(TypographyToken::kCrosButton2,
                                        *text_label_);
  tri_view_->AddView(TriView::Container::CENTER, text_label_);

  AddSubRowContainer();

  GetViewAccessibility().SetName(text);
}

void HoverHighlightView::AddLabelRow(const std::u16string& text,
                                     int start_inset) {
  AddLabelRow(text);

  tri_view_->SetMinSize(TriView::Container::START,
                        gfx::Size(start_inset, kTrayPopupItemMinHeight));
}

void HoverHighlightView::SetExpandable(bool expandable) {
  if (expandable != expandable_) {
    expandable_ = expandable;
    InvalidateLayout();
  }
}

void HoverHighlightView::SetAccessibilityState(
    AccessibilityState accessibility_state) {
  accessibility_state_ = accessibility_state;

  if (accessibility_state_ == AccessibilityState::CHECKED_CHECKBOX) {
    GetViewAccessibility().SetRole(ax::mojom::Role::kCheckBox);
    GetViewAccessibility().SetCheckedState(ax::mojom::CheckedState::kTrue);
  } else if (accessibility_state_ == AccessibilityState::UNCHECKED_CHECKBOX) {
    GetViewAccessibility().SetRole(ax::mojom::Role::kCheckBox);
    GetViewAccessibility().SetCheckedState(ax::mojom::CheckedState::kFalse);
  } else {
    GetViewAccessibility().SetRole(ax::mojom::Role::kGenericContainer);
  }

  if (accessibility_state_ != AccessibilityState::DEFAULT) {
    NotifyAccessibilityEvent(ax::mojom::Event::kCheckedStateChanged, true);
  }
}

void HoverHighlightView::Reset() {
  icon_ = nullptr;
  text_label_ = nullptr;
  sub_text_label_ = nullptr;
  left_view_ = nullptr;
  right_view_ = nullptr;
  sub_row_ = nullptr;
  tri_view_ = nullptr;

  RemoveAllChildViews();
  SetAndUpdateAccessibleDefaultAction();

  is_populated_ = false;
}

void HoverHighlightView::OnSetTooltipText(const std::u16string& tooltip_text) {
  if (text_label_) {
    text_label_->SetTooltipText(tooltip_text);
  }
  if (sub_text_label_) {
    sub_text_label_->SetTooltipText(tooltip_text);
  }
  if (left_view_) {
    DCHECK(views::IsViewClass<views::ImageView>(left_view_));
    static_cast<views::ImageView*>(left_view_)->SetTooltipText(tooltip_text);
  }
}

void HoverHighlightView::PerformAction() {
  if (!listener_) {
    return;
  }

  listener_->OnViewClicked(this);
}

gfx::Size HoverHighlightView::CalculatePreferredSize(
    const views::SizeBounds& available_size) const {
  gfx::Size size = views::Button::CalculatePreferredSize(available_size);

  if (!expandable_ || size.height() < kTrayPopupItemMinHeight) {
    size.set_height(kTrayPopupItemMinHeight);
  }

  return size;
}

void HoverHighlightView::OnFocus() {
  ScrollRectToVisible(gfx::Rect(gfx::Point(), size()));
  views::Button::OnFocus();
}

void HoverHighlightView::AddSubRowContainer() {
  DCHECK(is_populated_);
  DCHECK(tri_view_);
  DCHECK(text_label_);
  DCHECK(!sub_row_);
  sub_row_ = new views::View();
  sub_row_->SetLayoutManager(std::make_unique<views::BoxLayout>(
      views::BoxLayout::Orientation::kHorizontal));
  tri_view_->AddView(TriView::Container::CENTER, sub_row_);
}

void HoverHighlightView::OnEnabledChanged() {
  views::Button::OnEnabledChanged();
  if (left_view_) {
    left_view_->SetEnabled(GetEnabled());
  }
  if (text_label_) {
    text_label_->SetEnabled(GetEnabled());
  }
  if (right_view_) {
    right_view_->SetEnabled(GetEnabled());
  }
}

void HoverHighlightView::SetAndUpdateAccessibleDefaultAction() {
  SetDefaultActionVerb(
      (right_view_ && right_view_->GetVisible() &&
       std::string(right_view_->GetClassName()).find("Button") !=
           std::string::npos)
          ? ax::mojom::DefaultActionVerb::kClick
          : ax::mojom::DefaultActionVerb::kPress);
  UpdateAccessibleDefaultActionVerb();
}

BEGIN_METADATA(HoverHighlightView)
END_METADATA

}  // namespace ash