chromium/ash/frame/wide_frame_view.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/frame/wide_frame_view.h"
#include <memory>

#include "ash/frame/non_client_frame_view_ash.h"
#include "ash/public/cpp/window_properties.h"
#include "ash/screen_util.h"
#include "ash/shell.h"
#include "ash/wm/overview/overview_controller.h"
#include "ash/wm/window_state.h"
#include "ash/wm/wm_event.h"
#include "base/functional/bind.h"
#include "base/memory/raw_ptr.h"
#include "base/metrics/user_metrics.h"
#include "chromeos/ui/frame/caption_buttons/frame_caption_button_container_view.h"
#include "chromeos/ui/frame/default_frame_header.h"
#include "chromeos/ui/frame/header_view.h"
#include "chromeos/ui/frame/immersive/immersive_fullscreen_controller.h"
#include "ui/aura/window.h"
#include "ui/aura/window_targeter.h"
#include "ui/display/display.h"
#include "ui/display/screen.h"
#include "ui/views/widget/widget.h"
#include "ui/views/window/caption_button_layout_constants.h"

namespace ash {
namespace {

using ::chromeos::ImmersiveFullscreenController;

class WideFrameTargeter : public aura::WindowTargeter {
 public:
  explicit WideFrameTargeter(chromeos::HeaderView* header_view)
      : header_view_(header_view) {}

  WideFrameTargeter(const WideFrameTargeter&) = delete;
  WideFrameTargeter& operator=(const WideFrameTargeter&) = delete;

  ~WideFrameTargeter() override = default;

  // aura::WindowTargeter:
  bool GetHitTestRects(aura::Window* target,
                       gfx::Rect* hit_test_rect_mouse,
                       gfx::Rect* hit_test_rect_touch) const override {
    if (header_view_->in_immersive_mode() && !header_view_->is_revealed()) {
      aura::Window* source = header_view_->GetWidget()->GetNativeWindow();
      *hit_test_rect_mouse = source->bounds();
      aura::Window::ConvertRectToTarget(source, target->parent(),
                                        hit_test_rect_mouse);
      hit_test_rect_mouse->set_y(target->bounds().y());
      hit_test_rect_mouse->set_height(1);
      hit_test_rect_touch->SetRect(0, 0, 0, 0);
      return true;
    }
    return aura::WindowTargeter::GetHitTestRects(target, hit_test_rect_mouse,
                                                 hit_test_rect_touch);
  }

 private:
  raw_ptr<chromeos::HeaderView> header_view_;
};

}  // namespace

// static
gfx::Rect WideFrameView::GetFrameBounds(views::Widget* target) {
  static const int kFrameHeight =
      views::GetCaptionButtonLayoutSize(
          views::CaptionButtonLayoutSize::kNonBrowserCaption)
          .height();
  aura::Window* target_window = target->GetNativeWindow();
  gfx::Rect bounds =
      screen_util::GetIdealBoundsForMaximizedOrFullscreenOrPinnedState(
          target_window);

  bounds.set_height(kFrameHeight);
  return bounds;
}

void WideFrameView::Init(ImmersiveFullscreenController* controller) {
  DCHECK(target_);
  controller->Init(this, target_, header_view_);
}

void WideFrameView::SetCaptionButtonModel(
    std::unique_ptr<chromeos::CaptionButtonModel> model) {
  header_view_->caption_button_container()->SetModel(std::move(model));
  header_view_->UpdateCaptionButtons();
}

WideFrameView::WideFrameView(views::Widget* target)
    : target_(target),
      frame_context_menu_controller_(
          std::make_unique<FrameContextMenuController>(target_, this)) {
  // WideFrameView is owned by its client, not by Views.
  SetOwnedByWidget(false);

  aura::Window* target_window = target->GetNativeWindow();
  target_window->AddObserver(this);
  // Use the HeaderView itself as a frame view because WideFrameView is
  // is the frame only.
  header_view_ = AddChildView(
      std::make_unique<chromeos::HeaderView>(target, /*frame view=*/nullptr));
  header_view_->Init();
  GetTargetHeaderView()->SetShouldPaintHeader(false);
  header_view_->set_context_menu_controller(
      frame_context_menu_controller_.get());

  views::Widget::InitParams params(
      views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET,
      views::Widget::InitParams::TYPE_POPUP);
  params.delegate = this;
  params.bounds = GetFrameBounds(target);
  params.name = "WideFrameView";
  params.parent = target->GetNativeWindow();
  // Setup Opacity Control.
  // WideFrame should be used only when the rounded corner is not necessary.
  params.opacity = views::Widget::InitParams::WindowOpacity::kOpaque;
  auto widget = std::make_unique<views::Widget>();
  widget->Init(std::move(params));
  widget_ = std::move(widget);

  aura::Window* window = widget_->GetNativeWindow();
  // Overview normally clips the caption container which exists on the same
  // window. But this WideFrameView exists as a separate window, which we hide
  // in overview using the `kHideInOverviewKey` property. However, we still want
  // to show it in the desks mini_views.
  window->SetProperty(kHideInOverviewKey, true);
  window->SetProperty(kForceVisibleInMiniViewKey, true);
  window->SetEventTargeter(std::make_unique<WideFrameTargeter>(header_view()));
  set_owned_by_client();
  WindowState::Get(window)->set_allow_set_bounds_direct(true);

  paint_as_active_subscription_ =
      target_->RegisterPaintAsActiveChangedCallback(base::BindRepeating(
          &WideFrameView::PaintAsActiveChanged, base::Unretained(this)));
  PaintAsActiveChanged();
}

WideFrameView::~WideFrameView() {
  header_view_->set_context_menu_controller(nullptr);
  if (widget_)
    widget_->CloseNow();
  if (target_) {
    chromeos::HeaderView* target_header_view = GetTargetHeaderView();
    target_header_view->SetShouldPaintHeader(true);
    target_header_view->GetFrameHeader()->UpdateFrameHeaderKey();
    target_->GetNativeWindow()->RemoveObserver(this);
  }
}

void WideFrameView::Layout(PassKey) {
  int onscreen_height = header_view_->GetPreferredOnScreenHeight();
  if (onscreen_height == 0 || !GetVisible()) {
    header_view_->SetVisible(false);
  } else {
    const int height = header_view_->GetPreferredHeight();
    header_view_->SetBounds(0, onscreen_height - height, width(), height);
    header_view_->SetVisible(true);
  }
}

void WideFrameView::OnMouseEvent(ui::MouseEvent* event) {
  if (event->IsOnlyLeftMouseButton()) {
    if ((event->flags() & ui::EF_IS_DOUBLE_CLICK)) {
      base::RecordAction(
          base::UserMetricsAction("Caption_ClickTogglesMaximize"));
      const WMEvent wm_event(WM_EVENT_TOGGLE_MAXIMIZE_CAPTION);
      WindowState::Get(target_->GetNativeWindow())->OnWMEvent(&wm_event);
    }
    event->SetHandled();
  }
}

void WideFrameView::OnWindowDestroying(aura::Window* window) {
  window->RemoveObserver(this);
  target_ = nullptr;
}

void WideFrameView::OnDisplayMetricsChanged(const display::Display& display,
                                            uint32_t changed_metrics) {
  aura::Window* target_window = target_->GetNativeWindow();
  // The target window is detached from the tree when the window is being moved
  // to another desks, or another display, and it may trigger the display
  // metrics change if the target window is in fullscreen. Do not try to layout
  // in this case. The it'll be layout when the window is added back.
  // (crbug.com/1267128)
  if (!target_window->GetRootWindow())
    return;

  display::Screen* screen = display::Screen::GetScreen();
  if (screen->GetDisplayNearestWindow(target_->GetNativeWindow()).id() !=
          display.id() ||
      !widget_) {
    return;
  }
  DCHECK(target_);

  // Ignore if this is called when the targe window state is nor proper
  // state. This can happen when switching the state to other states, in which
  // case, the wide frame will be removed.
  if (!WindowState::Get(target_window)->IsMaximizedOrFullscreenOrPinned()) {
    return;
  }

  GetWidget()->SetBounds(GetFrameBounds(target_));
}

void WideFrameView::OnImmersiveRevealStarted() {
  header_view_->OnImmersiveRevealStarted();
}

void WideFrameView::OnImmersiveRevealEnded() {
  header_view_->OnImmersiveRevealEnded();
}

void WideFrameView::OnImmersiveFullscreenEntered() {
  header_view_->OnImmersiveFullscreenEntered();
  widget_->GetNativeWindow()->SetTransparent(true);
  if (target_)
    GetTargetHeaderView()->OnImmersiveFullscreenEntered();
}

void WideFrameView::OnImmersiveFullscreenExited() {
  header_view_->OnImmersiveFullscreenExited();
  widget_->GetNativeWindow()->SetTransparent(false);
  if (target_)
    GetTargetHeaderView()->OnImmersiveFullscreenExited();
  DeprecatedLayoutImmediately();
}

void WideFrameView::SetVisibleFraction(double visible_fraction) {
  header_view_->SetVisibleFraction(visible_fraction);
}

std::vector<gfx::Rect> WideFrameView::GetVisibleBoundsInScreen() const {
  return header_view_->GetVisibleBoundsInScreen();
}

bool WideFrameView::ShouldShowContextMenu(
    View* source,
    const gfx::Point& screen_coords_point) {
  gfx::Point point_in_header_coords(screen_coords_point);
  views::View::ConvertPointToTarget(this, header_view_,
                                    &point_in_header_coords);
  return header_view_->HitTestRect(
      gfx::Rect(point_in_header_coords, gfx::Size(1, 1)));
}

chromeos::HeaderView* WideFrameView::GetTargetHeaderView() {
  auto* frame_view = static_cast<NonClientFrameViewAsh*>(
      target_->non_client_view()->frame_view());
  return frame_view->GetHeaderView();
}

void WideFrameView::PaintAsActiveChanged() {
  header_view_->GetFrameHeader()->SetPaintAsActive(
      target_->ShouldPaintAsActive());
}

}  // namespace ash