blob: c01390ed2891631b6acd7939af81091a1d1373ac [file] [log] [blame]
// Copyright 2023 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/rounded_display/rounded_display_provider.h"
#include <algorithm>
#include <memory>
#include <utility>
#include <vector>
#include "ash/display/window_tree_host_manager.h"
#include "ash/rounded_display/rounded_display_gutter.h"
#include "ash/rounded_display/rounded_display_gutter_factory.h"
#include "ash/rounded_display/rounded_display_host.h"
#include "ash/screen_util.h"
#include "ash/shell.h"
#include "base/check.h"
#include "base/functional/bind.h"
#include "base/notreached.h"
#include "ui/display/display.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/rounded_corners_f.h"
#include "ui/gfx/geometry/size.h"
namespace ash {
namespace {
using Gutters = std::vector<RoundedDisplayGutter*>;
bool IsRadiiHorizontallyUniform(const gfx::RoundedCornersF& radii) {
return radii.upper_left() == radii.upper_right() &&
radii.lower_left() == radii.lower_right();
}
bool IsRadiiVerticallyUniform(const gfx::RoundedCornersF& radii) {
return radii.upper_left() == radii.lower_left() &&
radii.upper_right() == radii.lower_right();
}
bool IsRadiiValid(const gfx::RoundedCornersF radii) {
return !radii.IsEmpty() &&
(IsRadiiHorizontallyUniform(radii) || IsRadiiVerticallyUniform(radii));
}
// Returns the display's panel size in pixels.
gfx::Size GetPanelSizeInPixels(const display::Display& display) {
gfx::Size display_size_in_pixels = display.GetSizeInPixel();
if (display.panel_rotation() == display::Display::ROTATE_90 ||
display.panel_rotation() == display::Display::ROTATE_270) {
display_size_in_pixels.Transpose();
}
return display_size_in_pixels;
}
const display::Display& GetDisplay(int64_t display_id) {
return Shell::Get()->display_manager()->GetDisplayForId(display_id);
}
aura::Window* GetRootWindow(int64_t display_id) {
return Shell::GetRootWindowForDisplayId(display_id);
}
} // namespace
// static
std::unique_ptr<RoundedDisplayProvider> RoundedDisplayProvider::Create(
int64_t display_id) {
auto gutter_factory = std::make_unique<RoundedDisplayGutterFactory>();
return std::make_unique<RoundedDisplayProvider>(display_id,
std::move(gutter_factory));
}
RoundedDisplayProvider::RoundedDisplayProvider(
int64_t display_id,
std::unique_ptr<RoundedDisplayGutterFactory> gutter_factory)
: display_id_(display_id), gutter_factory_(std::move(gutter_factory)) {}
RoundedDisplayProvider::~RoundedDisplayProvider() {
if (host_) {
aura::Window* root_window = GetRootWindow(display_id_);
DCHECK(root_window) << "The provider needs to be destroyed first before "
"the root window is destroyed";
// `host_window_` needs to outlive the `host_`.
DCHECK(root_window->Contains(host_window_.get()));
root_window->RemoveChild(host_window_.get());
}
}
void RoundedDisplayProvider::Init(const gfx::RoundedCornersF& panel_radii,
Strategy strategy) {
if (host_) {
NOTREACHED() << "Provider is already initialized";
}
DCHECK(IsRadiiValid(panel_radii));
current_panel_radii_ = panel_radii;
strategy_ = strategy;
const display::Display& display = GetDisplay(display_id_);
CreateGutters(display, panel_radii);
InitializeHost();
}
void RoundedDisplayProvider::InitializeHost() {
host_ = std::make_unique<RoundedDisplayHost>(
base::BindRepeating(&RoundedDisplayProvider::GetGuttersInDrawOrder,
weak_ptr_factory_.GetWeakPtr()));
// TODO(zoraiznaeem): Change the default color to transparent when we fail to
// identify surface.
host_window_ = std::make_unique<aura::Window>(/*delegate=*/nullptr);
host_window_->set_owned_by_parent(false);
host_window_->Init(ui::LAYER_SOLID_COLOR);
host_window_->SetName("RoundedDisplayHost");
host_window_->SetEventTargetingPolicy(aura::EventTargetingPolicy::kNone);
host_window_->SetTransparent(true);
host_window_->Show();
aura::Window* root_window = GetRootWindow(display_id_);
root_window->AddChild(host_window_.get());
host_->Init(host_window_.get());
}
void RoundedDisplayProvider::UpdateHostParent() {
DCHECK(host_) << "Call Init() before calling UpdateHostParent";
aura::Window* new_display_root = GetRootWindow(display_id_);
aura::Window* current_display_root = host_window_->GetRootWindow();
if (new_display_root == current_display_root) {
return;
}
current_display_root->RemoveChild(host_window_.get());
new_display_root->AddChild(host_window_.get());
}
bool RoundedDisplayProvider::UpdateRoundedDisplaySurface() {
DCHECK(host_) << "Call Init() before calling UpdateRoundedDisplay";
const display::Display& display = GetDisplay(display_id_);
if (!ShouldSubmitNewCompositorFrame(display)) {
return false;
}
// We need to adjust the bounds of host_window to account for the 1px offset
// introduced for certain device scale factor values due to conversion between
// dip and pixel values.
host_window_->SetBounds(screen_util::SnapBoundsToDisplayEdge(
gfx::Rect(display.bounds().size()), GetRootWindow(display_id_)));
gfx::Rect content_rect(host_window_->bounds());
gfx::Rect damage_rect;
// Submit a compositor frame to update the surface. Textures will be reused,
// unless we decide to create new gutters, otherwise the positions of the
// existing textures will be updated.
host_->UpdateSurface(content_rect, damage_rect, /*synchronous_draw=*/true);
current_device_scale_factor_ = display.device_scale_factor();
current_logical_rotation_ = display.rotation();
return true;
}
bool RoundedDisplayProvider::ShouldSubmitNewCompositorFrame(
const display::Display& display) const {
return display.device_scale_factor() != current_device_scale_factor_ ||
display.rotation() != current_logical_rotation_;
}
void RoundedDisplayProvider::GetGuttersInDrawOrder(Gutters& gutters) const {
for (const auto& gutter : overlay_gutters_) {
gutters.push_back(gutter.get());
}
}
bool RoundedDisplayProvider::CreateGutters(
const display::Display& display,
const gfx::RoundedCornersF& panel_radii) {
gfx::Size panel_size = GetPanelSizeInPixels(display);
// Scanout direction is left to right wrt to the panel. Therefore horizontal
// gutters are in the direction of scanout and vertical gutters are in the
// other direction.
bool create_vertical_gutters = strategy_ != Strategy::kScanout;
overlay_gutters_ = gutter_factory_->CreateOverlayGutters(
panel_size, panel_radii, create_vertical_gutters);
return true;
}
} // namespace ash