910e62b5创建于 1月15日历史提交
// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chromeos/ui/frame/frame_view_chromeos.h"

#include <memory>

#include "chromeos/ui/base/window_properties.h"
#include "chromeos/ui/base/window_state_type.h"
#include "chromeos/ui/frame/default_frame_header.h"
#include "chromeos/ui/frame/frame_utils.h"
#include "chromeos/ui/frame/header_view.h"
#include "ui/aura/client/aura_constants.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/color/color_id.h"
#include "ui/display/screen.h"
#include "ui/display/tablet_state.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/size.h"
#include "ui/views/view.h"
#include "ui/views/view_targeter.h"
#include "ui/views/widget/widget.h"

namespace chromeos {

FrameViewChromeOS::OverlayView::OverlayView(HeaderView* header_view)
    : header_view_(header_view) {
  SetEventTargeter(std::make_unique<views::ViewTargeter>(this));
}

FrameViewChromeOS::OverlayView::~OverlayView() = default;

void FrameViewChromeOS::OverlayView::Layout(PassKey) {
  // Layout |header_view_| because layout affects the result of
  // GetPreferredOnScreenHeight().
  header_view_->DeprecatedLayoutImmediately();

  int onscreen_height = header_view_->GetPreferredOnScreenHeight();
  int height = header_view_->GetPreferredHeight();
  if (onscreen_height == 0 || !GetVisible()) {
    header_view_->SetVisible(false);
    // Make sure the correct width is set even when immersive is enabled, but
    // never revealed yet.
    header_view_->SetBounds(0, 0, width(), height);
  } else {
    header_view_->SetBounds(0, onscreen_height - height, width(), height);
    header_view_->SetVisible(true);
  }
}

bool FrameViewChromeOS::OverlayView::DoesIntersectRect(
    const views::View* target,
    const gfx::Rect& rect) const {
  DCHECK_EQ(target, this);
  // Grab events in the header view. Return false for other events so that they
  // can be handled by the client view.
  return header_view_->HitTestRect(rect);
}

BEGIN_METADATA(FrameViewChromeOS, OverlayView)
END_METADATA

FrameViewChromeOS::FrameViewChromeOS(views::Widget* widget) : widget_(widget) {
  DCHECK(widget_);

  auto header_view = std::make_unique<HeaderView>(widget_, this);
  header_view->Init();

  auto overlay_view = std::make_unique<OverlayView>(header_view.get());
  header_view_ = overlay_view->AddChildView(std::move(header_view));
  overlay_view_ = overlay_view.get();

  // |header_view_| is set as the non client view's overlay view so that it can
  // overlay the web contents in immersive fullscreen.
  // TODO(pkasting): Consider having something like NonClientViewAsh, which
  // would avoid the need to expose an "overlay view" concept on the
  // cross-platform class, and might allow for simpler creation/ownership/
  // plumbing.
  widget_->non_client_view()->SetOverlayView(overlay_view.release());

  UpdateDefaultFrameColors();
}

FrameViewChromeOS::~FrameViewChromeOS() = default;

HeaderView* FrameViewChromeOS::GetHeaderView() {
  return header_view_;
}

int FrameViewChromeOS::NonClientTopBorderHeight() const {
  const aura::Window* frame_window = widget_->GetNativeWindow();
  const WindowStateType window_state_type =
      frame_window->GetProperty(kWindowStateTypeKey);
  const bool is_in_tablet_mode = display::Screen::Get()->InTabletMode();
  const bool should_have_frame_in_tablet =
      window_state_type == chromeos::WindowStateType::kFloated ||
      window_state_type == chromeos::WindowStateType::kNormal;
  // The frame should not occupy the window area when it's in fullscreen,
  // not visible, disabled, in immersive mode or in tablet mode.
  // TODO(crbug.com/40879470): Support FrameViewAshImmersiveHelper on
  // Lacros so that we can remove InTabletMode() && IsMaximized() condition.
  if (widget_->IsFullscreen() || !GetFrameEnabled() ||
      header_view_->in_immersive_mode() ||
      (is_in_tablet_mode && !should_have_frame_in_tablet)) {
    return 0;
  }
  return header_view_->GetPreferredHeight();
}

gfx::Rect FrameViewChromeOS::GetBoundsForClientView() const {
  gfx::Rect client_bounds = bounds();
  client_bounds.Inset(gfx::Insets::TLBR(NonClientTopBorderHeight(), 0, 0, 0));
  return client_bounds;
}

gfx::Rect FrameViewChromeOS::GetWindowBoundsForClientBounds(
    const gfx::Rect& client_bounds) const {
  gfx::Rect window_bounds = client_bounds;
  window_bounds.Inset(gfx::Insets::TLBR(-NonClientTopBorderHeight(), 0, 0, 0));
  return window_bounds;
}

int FrameViewChromeOS::NonClientHitTest(const gfx::Point& point) {
  return FrameBorderNonClientHitTest(this, point);
}

void FrameViewChromeOS::GetWindowMask(const gfx::Size& size,
                                      SkPath* window_mask) {
  // No window masks in Aura.
}

void FrameViewChromeOS::ResetWindowControls() {
  header_view_->ResetWindowControls();
}

void FrameViewChromeOS::UpdateWindowTitle() {
  header_view_->SchedulePaintForTitle();
}

void FrameViewChromeOS::SizeConstraintsChanged() {
  header_view_->UpdateCaptionButtons();
}

views::View::Views FrameViewChromeOS::GetChildrenInZOrder() {
  return header_view_->GetFrameHeader()->GetAdjustedChildrenInZOrder(this);
}

gfx::Size FrameViewChromeOS::CalculatePreferredSize(
    const views::SizeBounds& available_size) const {
  gfx::Size pref = widget_->client_view()->GetPreferredSize(available_size);
  gfx::Rect bounds(0, 0, pref.width(), pref.height());
  return widget_->non_client_view()
      ->GetWindowBoundsForClientBounds(bounds)
      .size();
}

void FrameViewChromeOS::Layout(PassKey) {
  LayoutSuperclass<views::FrameView>(this);
  if (!GetFrameEnabled())
    return;
  aura::Window* frame_window = widget_->GetNativeWindow();
  frame_window->SetProperty(aura::client::kTopViewInset,
                            NonClientTopBorderHeight());
}

gfx::Size FrameViewChromeOS::GetMinimumSize() const {
  if (!GetFrameEnabled())
    return gfx::Size();

  gfx::Size min_client_view_size(widget_->client_view()->GetMinimumSize());
  return gfx::Size(
      std::max(header_view_->GetMinimumWidth(), min_client_view_size.width()),
      NonClientTopBorderHeight() + min_client_view_size.height());
}

gfx::Size FrameViewChromeOS::GetMaximumSize() const {
  gfx::Size max_client_size(widget_->client_view()->GetMaximumSize());
  int width = 0;
  int height = 0;

  if (max_client_size.width() > 0)
    width = std::max(header_view_->GetMinimumWidth(), max_client_size.width());
  if (max_client_size.height() > 0)
    height = NonClientTopBorderHeight() + max_client_size.height();

  return gfx::Size(width, height);
}

void FrameViewChromeOS::OnThemeChanged() {
  FrameView::OnThemeChanged();
  UpdateDefaultFrameColors();
}

void FrameViewChromeOS::UpdateDefaultFrameColors() {
  aura::Window* frame_window = widget_->GetNativeWindow();
  if (!frame_window->GetProperty(kTrackDefaultFrameColors))
    return;

  auto* color_provider = widget_->GetColorProvider();

  frame_window->SetProperty(kFrameActiveColorKey,
                            color_provider->GetColor(ui::kColorFrameActive));
  frame_window->SetProperty(kFrameInactiveColorKey,
                            color_provider->GetColor(ui::kColorFrameInactive));
}

bool FrameViewChromeOS::DoesIntersectRect(const views::View* target,
                                          const gfx::Rect& rect) const {
  DCHECK_EQ(target, this);

  // Give the OverlayView the first chance to handle events.
  if (frame_enabled_ && overlay_view_->HitTestRect(rect))
    return false;

  // Handle the event if it's within the bounds of the ClientView.
  gfx::RectF rect_in_client_view_coords_f(rect);
  View::ConvertRectToTarget(this, widget_->client_view(),
                            &rect_in_client_view_coords_f);
  gfx::Rect rect_in_client_view_coords =
      gfx::ToEnclosingRect(rect_in_client_view_coords_f);
  return widget_->client_view()->HitTestRect(rect_in_client_view_coords);
}

void FrameViewChromeOS::PaintAsActiveChanged() {
  header_view_->GetFrameHeader()->SetPaintAsActive(ShouldPaintAsActive());
  widget_->non_client_view()->DeprecatedLayoutImmediately();
}

void FrameViewChromeOS::OnDisplayTabletStateChanged(
    display::TabletState state) {
  switch (state) {
    case display::TabletState::kEnteringTabletMode:
    case display::TabletState::kExitingTabletMode:
      break;
    case display::TabletState::kInTabletMode:
    case display::TabletState::kInClamshellMode:
      // Without this, Layout is not guaranteed to be called when the tablet
      // state changes. Layout must be called so that the header view can hide
      // or unhide as appropriate.
      InvalidateLayout();
      break;
  }
}

BEGIN_METADATA(FrameViewChromeOS)
END_METADATA

}  // namespace chromeos