// Copyright 2020 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/app_list/views/search_result_page_anchored_dialog.h"
#include <utility>
#include "ash/constants/ash_features.h"
#include "ui/compositor/layer.h"
#include "ui/gfx/geometry/point.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/size.h"
#include "ui/gfx/geometry/transform.h"
#include "ui/views/view.h"
#include "ui/views/window/dialog_delegate.h"
#include "ui/views/window/non_client_view.h"
#include "ui/wm/core/coordinate_conversion.h"
namespace ash {
namespace {
// The intended offset the dialog should have from the top of the anchor view
// bounds.
constexpr int kDialogVerticalMargin = 32;
} // namespace
SearchResultPageAnchoredDialog::SearchResultPageAnchoredDialog(
std::unique_ptr<views::WidgetDelegate> dialog,
views::View* host_view,
base::OnceClosure callback)
: host_view_(host_view), callback_(std::move(callback)) {
DCHECK(!dialog->GetWidget());
views::Widget* const parent = host_view_->GetWidget();
widget_ = new views::Widget();
views::Widget::InitParams params(
views::Widget::InitParams::NATIVE_WIDGET_OWNS_WIDGET,
views::Widget::InitParams::TYPE_WINDOW_FRAMELESS);
params.layer_type = ui::LAYER_NOT_DRAWN;
params.parent = parent->GetNativeWindow();
params.delegate = dialog.release();
widget_->Init(std::move(params));
// The |dialog| ownership is passed to the window hierarchy.
widget_observations_.AddObservation(widget_.get());
widget_observations_.AddObservation(parent);
view_observations_.AddObservation(host_view_.get());
view_observations_.AddObservation(widget_->GetContentsView());
}
SearchResultPageAnchoredDialog::~SearchResultPageAnchoredDialog() {
view_observations_.RemoveAllObservations();
widget_observations_.RemoveAllObservations();
if (widget_)
widget_->Close();
}
void SearchResultPageAnchoredDialog::UpdateBounds() {
if (!widget_)
return;
gfx::Point anchor_point_in_screen(host_view_->width() / 2, 0);
views::View::ConvertPointToScreen(host_view_, &anchor_point_in_screen);
const int offset_for_frame_insets =
widget_->non_client_view() && widget_->non_client_view()->frame_view()
? widget_->non_client_view()->frame_view()->GetInsets().top()
: 0;
const int vertical_offset = kDialogVerticalMargin - offset_for_frame_insets;
gfx::Size dialog_size = widget_->GetContentsView()->GetPreferredSize();
widget_->SetBounds(
gfx::Rect(gfx::Point(anchor_point_in_screen.x() - dialog_size.width() / 2,
anchor_point_in_screen.y() + vertical_offset),
dialog_size));
}
float SearchResultPageAnchoredDialog::AdjustVerticalTransformOffset(
float default_offset) {
// In addition to the host view (in host view coordinates), the
// widget has to consider the parent (app list view) widget transform to
// correctly follow the anchor view animation.
const float parent_offset =
host_view_->GetWidget()->GetLayer()->transform().To2dTranslation().y();
return default_offset + parent_offset;
}
void SearchResultPageAnchoredDialog::OnWidgetDestroying(views::Widget* widget) {
widget_ = nullptr;
widget_observations_.RemoveAllObservations();
if (callback_)
std::move(callback_).Run();
}
void SearchResultPageAnchoredDialog::OnWidgetBoundsChanged(
views::Widget* widget,
const gfx::Rect& new_bounds) {
// Reposition the dialog widget if the host widget bounds change (if the
// bounds size remained the same, the host widget bounds change may not cause
// app list layout, and thus the anchor bounds in the host view coordinates
// may not change).
if (widget == host_view_->GetWidget())
UpdateBounds();
}
void SearchResultPageAnchoredDialog::OnViewBoundsChanged(
views::View* observed_view) {
UpdateBounds();
}
void SearchResultPageAnchoredDialog::OnViewPreferredSizeChanged(
views::View* observed_view) {
UpdateBounds();
}
} // namespace ash