chromium/chromeos/ui/frame/non_client_frame_view_base.cc

// 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/non_client_frame_view_base.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 {

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

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

void NonClientFrameViewBase::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 NonClientFrameViewBase::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(NonClientFrameViewBase, OverlayView)
END_METADATA

NonClientFrameViewBase::NonClientFrameViewBase(views::Widget* frame)
    : frame_(frame) {
  DCHECK(frame_);

  auto header_view = std::make_unique<HeaderView>(frame_, 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.
  frame->non_client_view()->SetOverlayView(overlay_view.release());

  UpdateDefaultFrameColors();
}

NonClientFrameViewBase::~NonClientFrameViewBase() = default;

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

int NonClientFrameViewBase::NonClientTopBorderHeight() const {
  const aura::Window* frame_window = frame_->GetNativeWindow();
  const WindowStateType window_state_type =
      frame_window->GetProperty(kWindowStateTypeKey);
  const bool is_in_tablet_mode = display::Screen::GetScreen()->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 NonClientFrameViewAshImmersiveHelper on
  // Lacros so that we can remove InTabletMode() && IsMaximized() condition.
  if (frame_->IsFullscreen() || !GetFrameEnabled() ||
      header_view_->in_immersive_mode() ||
      (is_in_tablet_mode && !should_have_frame_in_tablet)) {
    return 0;
  }
  return header_view_->GetPreferredHeight();
}

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

gfx::Rect NonClientFrameViewBase::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 NonClientFrameViewBase::NonClientHitTest(const gfx::Point& point) {
  return FrameBorderNonClientHitTest(this, point);
}

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

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

void NonClientFrameViewBase::UpdateWindowIcon() {}

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

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

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

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

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

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

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

gfx::Size NonClientFrameViewBase::GetMaximumSize() const {
  gfx::Size max_client_size(frame_->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 NonClientFrameViewBase::OnThemeChanged() {
  NonClientFrameView::OnThemeChanged();
  UpdateDefaultFrameColors();
}

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

  auto* color_provider = frame_->GetColorProvider();

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

bool NonClientFrameViewBase::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, frame_->client_view(),
                            &rect_in_client_view_coords_f);
  gfx::Rect rect_in_client_view_coords =
      gfx::ToEnclosingRect(rect_in_client_view_coords_f);
  return frame_->client_view()->HitTestRect(rect_in_client_view_coords);
}

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

void NonClientFrameViewBase::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(NonClientFrameViewBase)
END_METADATA

}  // namespace chromeos