// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ash/app_restore/arc_ghost_window_shell_surface.h"
#include "ash/frame/non_client_frame_view_ash.h"
#include "ash/wm/desks/desks_util.h"
#include "base/check_op.h"
#include "chrome/browser/ash/app_list/arc/arc_app_list_prefs.h"
#include "chrome/browser/ash/app_restore/arc_ghost_window_delegate.h"
#include "chrome/browser/ash/app_restore/arc_ghost_window_view.h"
#include "chrome/browser/ash/app_restore/arc_window_utils.h"
#include "chrome/browser/ash/arc/window_predictor/window_predictor_utils.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chromeos/constants/chromeos_features.h"
#include "chromeos/ui/base/window_state_type.h"
#include "chromeos/ui/frame/frame_utils.h"
#include "components/app_restore/app_restore_data.h"
#include "components/app_restore/window_properties.h"
#include "components/exo/buffer.h"
#include "ui/aura/env.h"
#include "ui/compositor/layer.h"
#include "ui/display/screen.h"
#include "ui/gfx/geometry/rounded_corners_f.h"
#include "ui/views/widget/widget.h"
#include "ui/views/window/caption_button_types.h"
#include "ui/wm/core/shadow_controller.h"
namespace ash::full_restore {
ArcGhostWindowShellSurface::ArcGhostWindowShellSurface(
std::unique_ptr<exo::Surface> surface,
int container,
const std::string& application_id)
: ClientControlledShellSurface(surface.get(),
/*can_minimize=*/true,
container,
/*default_scale_cancellation=*/true,
/*supports_floated_state=*/false) {
controller_surface_ = std::move(surface);
buffer_ = exo::Buffer::CreateBuffer(
gfx::Size(1, 1), gfx::BufferFormat::RGBA_8888, gfx::BufferUsage::GPU_READ,
"ArcGhostWindowShellSurface", gpu::kNullSurfaceHandle,
/*shutdown_event=*/nullptr);
SetApplicationId(application_id.c_str());
controller_surface_->Attach(buffer_.get());
controller_surface_->SetFrame(exo::SurfaceFrameType::NORMAL);
}
ArcGhostWindowShellSurface::~ArcGhostWindowShellSurface() {
controller_surface_.reset();
buffer_.reset();
}
// static
std::unique_ptr<ArcGhostWindowShellSurface> ArcGhostWindowShellSurface::Create(
const std::string& app_id,
arc::GhostWindowType type,
int window_id,
const gfx::Rect& bounds,
app_restore::AppRestoreData* restore_data,
base::RepeatingClosure close_callback) {
// ArcGhostWindowShellSurface need a valid display id, or it cannot be
// created.
int64_t display_id_value =
restore_data->display_id.value_or(display::kInvalidDisplayId);
const chromeos::WindowStateType window_state =
restore_data->window_info.window_state_type.value_or(
chromeos::WindowStateType::kDefault);
gfx::Rect local_bounds = bounds;
// If the window is maximize / minimized, the initial bounds will be
// unnecessary. Here set it as display size to ensure the content render is
// correct.
if (local_bounds.IsEmpty()) {
DCHECK(chromeos::IsMaximizedOrFullscreenWindowStateType(window_state) ||
chromeos::IsMinimizedWindowStateType(window_state));
display::Display disp;
display::Screen::GetScreen()->GetDisplayWithDisplayId(display_id_value,
&disp);
local_bounds = disp.work_area();
}
// TODO(sstan): Fallback to system default color or other topic color, if
// the task hasn't valid theme color.
uint32_t theme_color =
restore_data->status_bar_color.has_value() &&
IsValidThemeColor(restore_data->status_bar_color.value())
? restore_data->status_bar_color.value()
: SK_ColorWHITE;
// TODO(sstan): Handle the desk container from full_restore data.
int container = desks_util::GetActiveDeskContainerId();
auto surface = std::make_unique<exo::Surface>();
std::unique_ptr<ArcGhostWindowShellSurface> shell_surface(
new ArcGhostWindowShellSurface(std::move(surface), container,
WrapSessionAppIdFromWindowId(window_id)));
// TODO(sstan): Add set_surface_destroyed_callback.
shell_surface->set_delegate(std::make_unique<ArcGhostWindowDelegate>(
shell_surface.get(), window_id, app_id, display_id_value, local_bounds,
window_state));
shell_surface->set_close_callback(std::move(close_callback));
shell_surface->SetAppId(app_id);
shell_surface->SetBounds(display_id_value, local_bounds);
const std::optional<app_restore::WindowInfo::ArcExtraInfo>& arc_info =
restore_data->window_info.arc_extra_info;
if (arc_info) {
if (arc_info->maximum_size.has_value()) {
shell_surface->SetMaximumSize(*arc_info->maximum_size);
}
if (arc_info->minimum_size.has_value()) {
shell_surface->SetMinimumSize(*arc_info->minimum_size);
}
}
if (restore_data->window_info.app_title.has_value()) {
shell_surface->SetTitle(*restore_data->window_info.app_title);
}
// Set frame buttons.
constexpr uint32_t kVisibleButtonMask =
1 << views::CAPTION_BUTTON_ICON_MINIMIZE |
1 << views::CAPTION_BUTTON_ICON_MAXIMIZE_RESTORE |
1 << views::CAPTION_BUTTON_ICON_CLOSE |
1 << views::CAPTION_BUTTON_ICON_BACK;
shell_surface->SetFrameButtons(kVisibleButtonMask, kVisibleButtonMask);
shell_surface->OnSetFrameColors(theme_color, theme_color);
std::optional<gfx::RoundedCornersF> overlay_corners_radii;
if (chromeos::features::IsRoundedWindowsEnabled()) {
DCHECK_NE(window_state, chromeos::WindowStateType::kPip);
const int window_corner_radius =
chromeos::ShouldWindowStateHaveRoundedCorners(window_state)
? chromeos::features::RoundedWindowsRadius()
: 0;
gfx::RoundedCornersF window_radii(window_corner_radius);
shell_surface->SetWindowCornersRadii(window_radii);
// Ghost surface shadow radii must match the window radii.
shell_surface->SetShadowCornersRadii(window_radii);
// Ghost surface is an overlay widget, so its corners must be rounded. The
// bottom two corners of the ghost window overlay overlap with the window,
// so we need to round them.
overlay_corners_radii =
gfx::RoundedCornersF(0, 0, window_corner_radius, window_corner_radius);
}
shell_surface->controller_surface()->Commit();
shell_surface->InitContentOverlay(app_id, theme_color, type,
std::move(overlay_corners_radii));
// Relayout overlay.
shell_surface->GetWidget()->LayoutRootViewIfNecessary();
// Change the window state at the last operation, since we need create the
// window entity first.
if (chromeos::IsMaximizedOrFullscreenWindowStateType(window_state)) {
shell_surface->SetMaximized();
shell_surface->controller_surface()->Commit();
} else if (chromeos::IsMinimizedWindowStateType(window_state)) {
shell_surface->SetMinimized();
shell_surface->controller_surface()->Commit();
} else {
// Reset the same bounds, to make sure white background can be located in
// correct place. Without this operation, the white background will not
// at the same location with window bounds.
shell_surface->SetBounds(display_id_value, local_bounds);
}
return shell_surface;
}
void ArcGhostWindowShellSurface::OverrideInitParams(
views::Widget::InitParams* params) {
ClientControlledShellSurface::OverrideInitParams(params);
SetShellAppId(¶ms->init_properties_container, app_id_);
}
exo::Surface* ArcGhostWindowShellSurface::controller_surface() {
return controller_surface_.get();
}
void ArcGhostWindowShellSurface::InitContentOverlay(
const std::string& app_id,
uint32_t theme_color,
arc::GhostWindowType type,
std::optional<gfx::RoundedCornersF>&& corners_radii) {
std::string app_name;
// TODO(sstan): Move this part out of shell surface.
// In test env, ArcAppListPrefs or App maybe null.
auto* pref = ArcAppListPrefs::Get(ProfileManager::GetPrimaryUserProfile());
if (pref) {
auto app_info = pref->GetApp(app_id);
if (app_info)
app_name = app_info->name;
}
auto view = std::make_unique<ArcGhostWindowView>(this, app_name);
view_observer_ = view.get();
view->LoadIcon(app_id);
view->SetThemeColor(theme_color);
view->SetGhostWindowViewType(type);
exo::ShellSurfaceBase::OverlayParams overlay_params(std::move(view));
overlay_params.translucent = true;
overlay_params.overlaps_frame = false;
overlay_params.corners_radii = std::move(corners_radii);
AddOverlay(std::move(overlay_params));
}
void ArcGhostWindowShellSurface::SetAppId(
const std::optional<std::string>& id) {
app_id_ = id;
if (GetWidget() && GetWidget()->GetNativeWindow()) {
SetShellAppId(GetWidget()->GetNativeWindow(), app_id_);
}
}
void ArcGhostWindowShellSurface::SetShellAppId(
ui::PropertyHandler* property_handler,
const std::optional<std::string>& id) {
if (id)
property_handler->SetProperty(app_restore::kAppIdKey, *id);
else
property_handler->ClearProperty(app_restore::kAppIdKey);
}
void ArcGhostWindowShellSurface::SetWindowType(
arc::GhostWindowType window_type) {
DCHECK(view_observer_);
view_observer_->SetGhostWindowViewType(window_type);
}
} // namespace ash::full_restore