chromium/ash/system/tray/status_area_overflow_button_tray.cc

// Copyright 2019 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/status_area_overflow_button_tray.h"

#include <memory>

#include "ash/constants/tray_background_view_catalog.h"
#include "ash/resources/vector_icons/vector_icons.h"
#include "ash/shelf/shelf.h"
#include "ash/strings/grit/ash_strings.h"
#include "ash/style/ash_color_id.h"
#include "ash/system/status_area_widget.h"
#include "ash/system/tray/tray_constants.h"
#include "ash/system/tray/tray_container.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/compositor/layer.h"
#include "ui/gfx/animation/slide_animation.h"
#include "ui/gfx/animation/tween.h"
#include "ui/gfx/vector_icon_utils.h"
#include "ui/views/border.h"
#include "ui/views/view.h"

namespace ash {

namespace {

constexpr int kAnimationDurationMs = 250;
constexpr int kTrayWidth = kStatusAreaOverflowButtonSize.width();
constexpr int kTrayHeight = kStatusAreaOverflowButtonSize.height();

}  // namespace

StatusAreaOverflowButtonTray::IconView::IconView()
    : slide_animation_(std::make_unique<gfx::SlideAnimation>(this)) {
  slide_animation_->Reset(1.0);
  slide_animation_->SetTweenType(gfx::Tween::EASE_OUT);
  slide_animation_->SetSlideDuration(base::Milliseconds(kAnimationDurationMs));

  SetPaintToLayer();
  layer()->SetFillsBoundsOpaquely(false);

  SetImage(ui::ImageModel::FromVectorIcon(
      kOverflowShelfRightIcon,
      static_cast<ui::ColorId>(kColorAshIconColorPrimary)));

  // The icon has the default size. Use the size to calculate the border so that
  // the icon is placed at the center of the ink drop.
  const int size = gfx::GetDefaultSizeOfVectorIcon(kOverflowShelfRightIcon);
  const int vertical_padding = (kTrayHeight - size) / 2;
  const int horizontal_padding = (kTrayWidth - size) / 2;
  SetBorder(views::CreateEmptyBorder(
      gfx::Insets::VH(vertical_padding, horizontal_padding)));

  UpdateRotation();
}

StatusAreaOverflowButtonTray::IconView::~IconView() {}

void StatusAreaOverflowButtonTray::IconView::ToggleState(State state) {
  slide_animation_->End();
  if (state == CLICK_TO_EXPAND)
    slide_animation_->Show();
  else if (state == CLICK_TO_COLLAPSE)
    slide_animation_->Hide();

  // TODO(b/260253147): Currently, the collapse/expand animation is not fully
  // spec'd, so skip it for now.
  slide_animation_->End();
}

void StatusAreaOverflowButtonTray::IconView::AnimationEnded(
    const gfx::Animation* animation) {
  UpdateRotation();
}

void StatusAreaOverflowButtonTray::IconView::AnimationProgressed(
    const gfx::Animation* animation) {
  UpdateRotation();
}

void StatusAreaOverflowButtonTray::IconView::AnimationCanceled(
    const gfx::Animation* animation) {
  UpdateRotation();
}

void StatusAreaOverflowButtonTray::IconView::UpdateRotation() {
  double progress = slide_animation_->GetCurrentValue();

  gfx::Transform transform;
  gfx::Vector2d center(kTrayWidth / 2.0, kTrayHeight / 2.0);
  transform.Translate(center);
  transform.RotateAboutZAxis(180.0 * progress);
  transform.Translate(gfx::Vector2d(-center.x(), -center.y()));

  SetTransform(transform);
}

BEGIN_METADATA(StatusAreaOverflowButtonTray, IconView)
END_METADATA

StatusAreaOverflowButtonTray::StatusAreaOverflowButtonTray(Shelf* shelf)
    : TrayBackgroundView(
          shelf,
          TrayBackgroundViewCatalogName::kStatusAreaOverflowButton),
      icon_(tray_container()->AddChildView(std::make_unique<IconView>())) {
  SetCallback(base::BindRepeating(
      &StatusAreaOverflowButtonTray::OnButtonPressed, base::Unretained(this)));

  set_use_bounce_in_animation(false);
  // https://b/293650341 `TrayBackgroundView` sets the layer opacity to 0.0 when
  // they're not visible so it can animate the opacity when the visibility
  // changes. Since this view bypasses that logic we need to work around this by
  // setting the opacity ourselves.
  layer()->SetOpacity(1.0);
}

StatusAreaOverflowButtonTray::~StatusAreaOverflowButtonTray() {}

void StatusAreaOverflowButtonTray::ClickedOutsideBubble(
    const ui::LocatedEvent& event) {}

std::u16string StatusAreaOverflowButtonTray::GetAccessibleNameForTray() {
  return l10n_util::GetStringUTF16(
      state_ == CLICK_TO_COLLAPSE ? IDS_ASH_STATUS_AREA_OVERFLOW_BUTTON_COLLAPSE
                                  : IDS_ASH_STATUS_AREA_OVERFLOW_BUTTON_EXPAND);
}

void StatusAreaOverflowButtonTray::HandleLocaleChange() {}

void StatusAreaOverflowButtonTray::HideBubbleWithView(
    const TrayBubbleView* bubble_view) {}

void StatusAreaOverflowButtonTray::HideBubble(
    const TrayBubbleView* bubble_view) {}

void StatusAreaOverflowButtonTray::Initialize() {
  TrayBackgroundView::Initialize();
  SetVisiblePreferred(false);
}

void StatusAreaOverflowButtonTray::SetVisiblePreferred(bool visible_preferred) {
  // The visibility of the overflow tray button is controlled by the
  // `StatusAreaWidget`, so we bypass all default visibility logic from
  // `TrayBackgroundView`.
  views::View::SetVisible(visible_preferred);
  TrackVisibilityUMA(visible_preferred);
}

void StatusAreaOverflowButtonTray::UpdateAfterStatusAreaCollapseChange() {
  // The visibility of the overflow tray button is controlled by the
  // `StatusAreaWidget`, so we bypass all default visibility logic from
  // `TrayBackgroundView`.
}

void StatusAreaOverflowButtonTray::OnButtonPressed(const ui::Event& event) {
  state_ = state_ == CLICK_TO_COLLAPSE ? CLICK_TO_EXPAND : CLICK_TO_COLLAPSE;
  icon_->ToggleState(state_);
  shelf()->GetStatusAreaWidget()->UpdateCollapseState();
}

void StatusAreaOverflowButtonTray::ResetStateToCollapsed() {
  state_ = CLICK_TO_EXPAND;
  icon_->ToggleState(state_);
}

BEGIN_METADATA(StatusAreaOverflowButtonTray);
END_METADATA

}  // namespace ash