| // 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 |