chromium/ash/shelf/window_preview.cc

// Copyright 2018 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/shelf/window_preview.h"

#include "ash/public/cpp/shelf_config.h"
#include "ash/resources/vector_icons/vector_icons.h"
#include "ash/wm/window_preview_view.h"
#include "ash/wm/window_util.h"
#include "base/functional/bind.h"
#include "ui/aura/window.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/color/color_id.h"
#include "ui/color/color_provider.h"
#include "ui/gfx/color_palette.h"
#include "ui/gfx/paint_vector_icon.h"
#include "ui/views/background.h"
#include "ui/views/controls/button/image_button.h"
#include "ui/views/controls/label.h"

namespace ash {

// The margins around window titles.
constexpr int kTitleLineHeight = 20;
constexpr int kTitleMarginTop = 10;
constexpr int kTitleMarginBottom = 10;
constexpr int kTitleMarginRight = 16;

// The width and height of close buttons.
constexpr int kCloseButtonSize = 36;
constexpr int kCloseButtonImageSize = 24;
constexpr int kCloseButtonSideBleed = 8;
constexpr SkColor kCloseButtonColor = SK_ColorWHITE;

constexpr SkColor kPreviewContainerBgColor =
    SkColorSetA(gfx::kGoogleGrey100, 0x24);
constexpr int kPreviewBorderRadius = 4;

WindowPreview::WindowPreview(aura::Window* window, Delegate* delegate)
    : delegate_(delegate) {
  preview_view_ = new WindowPreviewView(window);
  preview_container_view_ = new views::View();
  preview_container_view_->SetBackground(views::CreateRoundedRectBackground(
      kPreviewContainerBgColor, kPreviewBorderRadius));
  title_ = new views::Label(window->GetTitle());
  close_button_ = new views::ImageButton(base::BindRepeating(
      &WindowPreview::CloseButtonPressed, base::Unretained(this)));
  close_button_->SetFocusBehavior(FocusBehavior::NEVER);

  AddChildView(preview_container_view_.get());
  AddChildView(preview_view_.get());
  AddChildView(title_.get());
  AddChildView(close_button_.get());
}

WindowPreview::~WindowPreview() = default;

gfx::Size WindowPreview::CalculatePreferredSize(
    const views::SizeBounds& available_size) const {
  // The preview itself will always be strictly contained within its container,
  // so only the container's size matters to calculate the preferred size.
  const gfx::Size container_size = GetPreviewContainerSize();
  const int title_height_with_padding =
      kTitleLineHeight + kTitleMarginTop + kTitleMarginBottom;
  return gfx::Size(container_size.width(),
                   container_size.height() + title_height_with_padding);
}

void WindowPreview::Layout(PassKey) {
  gfx::Rect content_rect = GetContentsBounds();

  gfx::Size title_size = title_->CalculatePreferredSize({});
  int title_height_with_padding =
      kTitleLineHeight + kTitleMarginTop + kTitleMarginBottom;
  int title_width =
      std::min(title_size.width(),
               content_rect.width() - kCloseButtonSize - kTitleMarginRight);
  title_->SetBoundsRect(gfx::Rect(content_rect.x(),
                                  content_rect.y() + kTitleMarginTop,
                                  title_width, kTitleLineHeight));

  close_button_->SetBoundsRect(
      gfx::Rect(content_rect.right() - kCloseButtonSize + kCloseButtonSideBleed,
                content_rect.y(), kCloseButtonSize, kCloseButtonSize));

  const gfx::Size container_size = GetPreviewContainerSize();
  gfx::Size mirror_size = preview_view_->CalculatePreferredSize({});
  float preview_ratio = static_cast<float>(mirror_size.width()) /
                        static_cast<float>(mirror_size.height());

  int preview_height = ShelfConfig::Get()->shelf_tooltip_preview_height();
  int preview_width = preview_height * preview_ratio;
  if (preview_ratio > ShelfConfig::Get()->shelf_tooltip_preview_max_ratio()) {
    // Very wide window.
    preview_width = ShelfConfig::Get()->shelf_tooltip_preview_max_width();
    preview_height =
        ShelfConfig::Get()->shelf_tooltip_preview_max_width() / preview_ratio;
  }

  // Center the actual preview over the container, horizontally and vertically.
  gfx::Point preview_offset_from_container(
      (container_size.width() - preview_width) / 2,
      (container_size.height() - preview_height) / 2);

  const int preview_container_top =
      content_rect.y() + title_height_with_padding;
  preview_container_view_->SetBoundsRect(
      gfx::Rect(content_rect.x(), preview_container_top, container_size.width(),
                container_size.height()));
  preview_view_->SetBoundsRect(
      gfx::Rect(content_rect.x() + preview_offset_from_container.x(),
                preview_container_top + preview_offset_from_container.y(),
                preview_width, preview_height));
}

bool WindowPreview::OnMousePressed(const ui::MouseEvent& event) {
  if (!preview_view_->bounds().Contains(event.location()))
    return false;

  aura::Window* target = preview_view_->window();
  if (target) {
    // The window might have been closed in the mean time.
    // TODO: Use WindowObserver to listen to when previewed windows are
    // being closed and remove this condition.
    wm::ActivateWindow(target);

    // This will have the effect of deleting this view.
    delegate_->OnPreviewActivated(this);
  }
  return true;
}

void WindowPreview::OnThemeChanged() {
  views::View::OnThemeChanged();
  const auto* color_provider = GetColorProvider();
  SkColor background_color =
      color_provider->GetColor(ui::kColorTooltipBackground);
  title_->SetEnabledColor(
      color_provider->GetColor(ui::kColorTooltipForeground));
  title_->SetBackgroundColor(background_color);

  // The background is not opaque, so we can't do subpixel rendering.
  title_->SetSubpixelRenderingEnabled(false);

  close_button_->SetImageModel(
      views::Button::STATE_NORMAL,
      ui::ImageModel::FromVectorIcon(kOverviewWindowCloseIcon,
                                     kCloseButtonColor));
  close_button_->SetImageHorizontalAlignment(views::ImageButton::ALIGN_CENTER);
  close_button_->SetImageVerticalAlignment(views::ImageButton::ALIGN_MIDDLE);
  close_button_->SetMinimumImageSize(
      gfx::Size(kCloseButtonImageSize, kCloseButtonImageSize));
}

gfx::Size WindowPreview::GetPreviewContainerSize() const {
  return gfx::Size(
      std::min(delegate_->GetMaxPreviewRatio() *
                   ShelfConfig::Get()->shelf_tooltip_preview_height(),
               static_cast<float>(
                   ShelfConfig::Get()->shelf_tooltip_preview_max_width())),
      ShelfConfig::Get()->shelf_tooltip_preview_height());
}

void WindowPreview::CloseButtonPressed() {
  // The window might have been closed in the mean time.
  // TODO: Use WindowObserver to listen to when previewed windows are
  // being closed and remove this condition.
  aura::Window* target = preview_view_->window();
  if (!target)
    return;
  window_util::CloseWidgetForWindow(target);

  // This will have the effect of deleting this view.
  delegate_->OnPreviewDismissed(this);
}

BEGIN_METADATA(WindowPreview)
END_METADATA

}  // namespace ash