| // Copyright 2017 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/ui/views/overlay/overlay_window_views.h" |
| |
| #include <memory> |
| #include <string> |
| |
| #include "base/bind.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/numerics/safe_conversions.h" |
| #include "base/time/time.h" |
| #include "base/timer/timer.h" |
| #include "build/build_config.h" |
| #include "build/chromeos_buildflags.h" |
| #include "chrome/app/vector_icons/vector_icons.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/ui/browser.h" |
| #include "chrome/browser/ui/browser_finder.h" |
| #include "chrome/browser/ui/views/overlay/back_to_tab_image_button.h" |
| #include "chrome/browser/ui/views/overlay/back_to_tab_label_button.h" |
| #include "chrome/browser/ui/views/overlay/close_image_button.h" |
| #include "chrome/browser/ui/views/overlay/hang_up_button.h" |
| #include "chrome/browser/ui/views/overlay/playback_image_button.h" |
| #include "chrome/browser/ui/views/overlay/resize_handle_button.h" |
| #include "chrome/browser/ui/views/overlay/skip_ad_label_button.h" |
| #include "chrome/browser/ui/views/overlay/toggle_camera_button.h" |
| #include "chrome/browser/ui/views/overlay/toggle_microphone_button.h" |
| #include "chrome/browser/ui/views/overlay/track_image_button.h" |
| #include "chrome/grit/generated_resources.h" |
| #include "components/url_formatter/url_formatter.h" |
| #include "components/vector_icons/vector_icons.h" |
| #include "content/public/browser/picture_in_picture_window_controller.h" |
| #include "content/public/browser/web_contents.h" |
| #include "media/base/media_switches.h" |
| #include "media/base/video_util.h" |
| #include "third_party/skia/include/core/SkColor.h" |
| #include "ui/base/hit_test.h" |
| #include "ui/base/l10n/l10n_util.h" |
| #include "ui/display/display.h" |
| #include "ui/display/screen.h" |
| #include "ui/gfx/color_palette.h" |
| #include "ui/gfx/geometry/resize_utils.h" |
| #include "ui/gfx/paint_vector_icon.h" |
| #include "ui/views/controls/button/image_button.h" |
| #include "ui/views/vector_icons.h" |
| #include "ui/views/widget/widget_delegate.h" |
| #include "ui/views/window/non_client_view.h" |
| |
| #if BUILDFLAG(IS_CHROMEOS_ASH) |
| #include "ash/public/cpp/ash_features.h" |
| #include "ash/public/cpp/window_properties.h" // nogncheck |
| #include "ui/aura/window.h" |
| #endif |
| |
| #if defined(OS_WIN) |
| #include "chrome/browser/shell_integration_win.h" |
| #include "ui/aura/window.h" |
| #include "ui/aura/window_tree_host.h" |
| #include "ui/base/win/shell.h" |
| #endif |
| |
| namespace { |
| |
| // Lower bound size of the window is a fixed value to allow for minimal sizes |
| // on UI affordances, such as buttons. |
| constexpr gfx::Size kMinWindowSize(260, 146); |
| |
| constexpr int kOverlayBorderThickness = 10; |
| |
| // The opacity of the controls scrim. |
| constexpr double kControlsScrimOpacity = 0.6; |
| |
| #if BUILDFLAG(IS_CHROMEOS_ASH) |
| // The opacity of the resize handle control. |
| constexpr double kResizeHandleOpacity = 0.38; |
| #endif |
| |
| // Size of a primary control. |
| constexpr gfx::Size kPrimaryControlSize(36, 36); |
| |
| // Margin from the bottom of the window for primary controls. |
| constexpr int kPrimaryControlBottomMargin = 8; |
| |
| // Size of a secondary control. |
| constexpr gfx::Size kSecondaryControlSize(20, 20); |
| |
| // Margin from the bottom of the window for secondary controls. |
| constexpr int kSecondaryControlBottomMargin = 16; |
| |
| // Margin between controls. |
| constexpr int kControlMargin = 32; |
| |
| // Returns the quadrant the OverlayWindowViews is primarily in on the current |
| // work area. |
| OverlayWindowViews::WindowQuadrant GetCurrentWindowQuadrant( |
| const gfx::Rect window_bounds, |
| content::PictureInPictureWindowController* controller) { |
| const gfx::Rect work_area = |
| display::Screen::GetScreen() |
| ->GetDisplayNearestWindow( |
| controller->GetWebContents()->GetTopLevelNativeWindow()) |
| .work_area(); |
| const gfx::Point window_center = window_bounds.CenterPoint(); |
| |
| // Check which quadrant the center of the window appears in. |
| const bool top = window_center.y() < work_area.height() / 2; |
| if (window_center.x() < work_area.width() / 2) { |
| return top ? OverlayWindowViews::WindowQuadrant::kTopLeft |
| : OverlayWindowViews::WindowQuadrant::kBottomLeft; |
| } |
| return top ? OverlayWindowViews::WindowQuadrant::kTopRight |
| : OverlayWindowViews::WindowQuadrant::kBottomRight; |
| } |
| |
| template <typename T> |
| T* AddChildView(std::vector<std::unique_ptr<views::View>>* views, |
| std::unique_ptr<T> child) { |
| views->push_back(std::move(child)); |
| return static_cast<T*>(views->back().get()); |
| } |
| |
| } // namespace |
| |
| // OverlayWindow implementation of NonClientFrameView. |
| class OverlayWindowFrameView : public views::NonClientFrameView { |
| public: |
| explicit OverlayWindowFrameView(views::Widget* widget) : widget_(widget) {} |
| ~OverlayWindowFrameView() override = default; |
| |
| // views::NonClientFrameView: |
| gfx::Rect GetBoundsForClientView() const override { return bounds(); } |
| gfx::Rect GetWindowBoundsForClientBounds( |
| const gfx::Rect& client_bounds) const override { |
| return bounds(); |
| } |
| int NonClientHitTest(const gfx::Point& point) override { |
| // Outside of the window bounds, do nothing. |
| if (!bounds().Contains(point)) |
| return HTNOWHERE; |
| |
| constexpr int kResizeAreaCornerSize = 16; |
| int window_component = GetHTComponentForFrame( |
| point, kOverlayBorderThickness, kOverlayBorderThickness, |
| kResizeAreaCornerSize, kResizeAreaCornerSize, |
| GetWidget()->widget_delegate()->CanResize()); |
| |
| // The media controls should take and handle user interaction. |
| OverlayWindowViews* window = static_cast<OverlayWindowViews*>(widget_); |
| if (window->AreControlsVisible() && |
| (window->GetBackToTabControlsBounds().Contains(point) || |
| window->GetSkipAdControlsBounds().Contains(point) || |
| window->GetCloseControlsBounds().Contains(point) || |
| window->GetPlayPauseControlsBounds().Contains(point) || |
| window->GetNextTrackControlsBounds().Contains(point) || |
| window->GetPreviousTrackControlsBounds().Contains(point) || |
| window->GetToggleMicrophoneButtonBounds().Contains(point) || |
| window->GetToggleCameraButtonBounds().Contains(point) || |
| window->GetHangUpButtonBounds().Contains(point))) { |
| return window_component; |
| } |
| |
| #if BUILDFLAG(IS_CHROMEOS_ASH) |
| // If the resize handle is clicked on, we want to force the hit test to |
| // force a resize drag. |
| if (window->AreControlsVisible() && |
| window->GetResizeHandleControlsBounds().Contains(point)) |
| return window->GetResizeHTComponent(); |
| #endif |
| |
| // Allows for dragging and resizing the window. |
| return (window_component == HTNOWHERE) ? HTCAPTION : window_component; |
| } |
| void GetWindowMask(const gfx::Size& size, SkPath* window_mask) override {} |
| void ResetWindowControls() override {} |
| void UpdateWindowIcon() override {} |
| void UpdateWindowTitle() override {} |
| void SizeConstraintsChanged() override {} |
| |
| // views::ViewTargeterDelegate: |
| bool DoesIntersectRect(const View* target, |
| const gfx::Rect& rect) const override { |
| DCHECK_EQ(target, this); |
| return false; |
| } |
| |
| private: |
| views::Widget* widget_; |
| |
| DISALLOW_COPY_AND_ASSIGN(OverlayWindowFrameView); |
| }; |
| |
| // OverlayWindow implementation of WidgetDelegate. |
| class OverlayWindowWidgetDelegate : public views::WidgetDelegate { |
| public: |
| OverlayWindowWidgetDelegate() { |
| SetCanResize(true); |
| SetModalType(ui::MODAL_TYPE_NONE); |
| // While not shown, the title is still used to identify the window in the |
| // window switcher. |
| SetShowTitle(false); |
| SetTitle(IDS_PICTURE_IN_PICTURE_TITLE_TEXT); |
| SetOwnedByWidget(true); |
| } |
| ~OverlayWindowWidgetDelegate() override = default; |
| |
| // views::WidgetDelegate: |
| std::unique_ptr<views::NonClientFrameView> CreateNonClientFrameView( |
| views::Widget* widget) override { |
| return std::make_unique<OverlayWindowFrameView>(widget); |
| } |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(OverlayWindowWidgetDelegate); |
| }; |
| |
| // static |
| std::unique_ptr<OverlayWindowViews> OverlayWindowViews::Create( |
| content::PictureInPictureWindowController* controller) { |
| // Can't use make_unique(), which doesn't have access to the private |
| // constructor. It's important that the constructor be private, because it |
| // doesn't initialize the object fully. |
| auto overlay_window = base::WrapUnique(new OverlayWindowViews(controller)); |
| |
| views::Widget::InitParams params(views::Widget::InitParams::TYPE_WINDOW); |
| params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; |
| // Just to have any non-empty bounds as required by Init(). The window is |
| // resized to fit the video that is embedded right afterwards, anyway. |
| params.bounds = gfx::Rect(overlay_window->GetMinimumSize()); |
| params.z_order = ui::ZOrderLevel::kFloatingWindow; |
| params.visible_on_all_workspaces = true; |
| params.remove_standard_frame = true; |
| params.name = "PictureInPictureWindow"; |
| params.layer_type = ui::LAYER_NOT_DRAWN; |
| params.delegate = new OverlayWindowWidgetDelegate(); |
| |
| overlay_window->Init(std::move(params)); |
| overlay_window->OnRootViewReady(); |
| |
| #if defined(OS_WIN) |
| std::wstring app_user_model_id; |
| Browser* browser = |
| chrome::FindBrowserWithWebContents(controller->GetWebContents()); |
| if (browser) { |
| const base::FilePath& profile_path = browser->profile()->GetPath(); |
| // Set the window app id to GetAppUserModelIdForApp if the original window |
| // is an app window, GetAppUserModelIdForBrowser if it's a browser window. |
| app_user_model_id = |
| browser->is_type_app() |
| ? shell_integration::win::GetAppUserModelIdForApp( |
| base::UTF8ToWide(browser->app_name()), profile_path) |
| : shell_integration::win::GetAppUserModelIdForBrowser(profile_path); |
| if (!app_user_model_id.empty()) { |
| ui::win::SetAppIdForWindow( |
| app_user_model_id, |
| overlay_window->GetNativeWindow()->GetHost()->GetAcceleratedWidget()); |
| } |
| } |
| #endif // defined(OS_WIN) |
| |
| return overlay_window; |
| } |
| |
| // static |
| std::unique_ptr<content::OverlayWindow> content::OverlayWindow::Create( |
| content::PictureInPictureWindowController* controller) { |
| return OverlayWindowViews::Create(controller); |
| } |
| |
| OverlayWindowViews::OverlayWindowViews( |
| content::PictureInPictureWindowController* controller) |
| : controller_(controller), |
| min_size_(kMinWindowSize), |
| hide_controls_timer_( |
| FROM_HERE, |
| base::TimeDelta::FromMilliseconds(2500), |
| base::BindRepeating(&OverlayWindowViews::UpdateControlsVisibility, |
| base::Unretained(this), |
| false /* is_visible */)) { |
| CalculateAndUpdateWindowBounds(); |
| SetUpViews(); |
| } |
| |
| OverlayWindowViews::~OverlayWindowViews() = default; |
| |
| gfx::Rect OverlayWindowViews::CalculateAndUpdateWindowBounds() { |
| gfx::Rect work_area = GetWorkAreaForWindow(); |
| |
| UpdateMaxSize(work_area); |
| |
| gfx::Size window_size = window_bounds_.size(); |
| if (!has_been_shown_) { |
| window_size = gfx::Size(work_area.width() / 5, work_area.height() / 5); |
| window_size.set_width(std::min( |
| max_size_.width(), std::max(min_size_.width(), window_size.width()))); |
| window_size.set_height( |
| std::min(max_size_.height(), |
| std::max(min_size_.height(), window_size.height()))); |
| } |
| |
| // Determine the window size by fitting |natural_size_| within |
| // |window_size|, keeping to |natural_size_|'s aspect ratio. |
| if (!window_size.IsEmpty() && !natural_size_.IsEmpty()) { |
| float aspect_ratio = (float)natural_size_.width() / natural_size_.height(); |
| |
| WindowQuadrant quadrant = |
| GetCurrentWindowQuadrant(GetBounds(), controller_); |
| gfx::ResizeEdge resize_edge; |
| switch (quadrant) { |
| case OverlayWindowViews::WindowQuadrant::kBottomRight: |
| resize_edge = gfx::ResizeEdge::kTopLeft; |
| break; |
| case OverlayWindowViews::WindowQuadrant::kBottomLeft: |
| resize_edge = gfx::ResizeEdge::kTopRight; |
| break; |
| case OverlayWindowViews::WindowQuadrant::kTopLeft: |
| resize_edge = gfx::ResizeEdge::kBottomRight; |
| break; |
| case OverlayWindowViews::WindowQuadrant::kTopRight: |
| resize_edge = gfx::ResizeEdge::kBottomLeft; |
| break; |
| } |
| |
| // Update the window size to adhere to the aspect ratio. |
| gfx::Size min_size = min_size_; |
| gfx::Size max_size = max_size_; |
| gfx::Rect window_rect(GetBounds().origin(), window_size); |
| gfx::SizeRectToAspectRatio(resize_edge, aspect_ratio, min_size, max_size, |
| &window_rect); |
| window_size.SetSize(window_rect.width(), window_rect.height()); |
| |
| UpdateLayerBoundsWithLetterboxing(window_size); |
| } |
| |
| // Use the previous window origin location, if exists. |
| gfx::Point origin = window_bounds_.origin(); |
| |
| int window_diff_width = work_area.right() - window_size.width(); |
| int window_diff_height = work_area.bottom() - window_size.height(); |
| |
| // Keep a margin distance of 2% the average of the two window size |
| // differences, keeping the margins consistent. |
| int buffer = (window_diff_width + window_diff_height) / 2 * 0.02; |
| |
| gfx::Point default_origin = |
| gfx::Point(window_diff_width - buffer, window_diff_height - buffer); |
| |
| if (has_been_shown_) { |
| // Make sure window is displayed entirely in the work area. |
| origin.SetToMin(default_origin); |
| } else { |
| origin = default_origin; |
| } |
| |
| window_bounds_ = gfx::Rect(origin, window_size); |
| return window_bounds_; |
| } |
| |
| void OverlayWindowViews::SetUpViews() { |
| // views::View that is displayed when video is hidden. ---------------------- |
| // Adding an extra pixel to width/height makes sure controls background cover |
| // entirely window when platform has fractional scale applied. |
| auto window_background_view = std::make_unique<views::View>(); |
| auto video_view = std::make_unique<views::View>(); |
| auto controls_scrim_view = std::make_unique<views::View>(); |
| auto controls_container_view = std::make_unique<views::View>(); |
| auto close_controls_view = |
| std::make_unique<views::CloseImageButton>(base::BindRepeating( |
| [](OverlayWindowViews* overlay) { |
| overlay->controller_->Close(/*should_pause_video=*/true); |
| overlay->RecordButtonPressed(OverlayWindowControl::kClose); |
| }, |
| base::Unretained(this))); |
| |
| std::unique_ptr<views::BackToTabImageButton> back_to_tab_image_button; |
| std::unique_ptr<BackToTabLabelButton> back_to_tab_label_button; |
| auto back_to_tab_callback = base::BindRepeating( |
| [](OverlayWindowViews* overlay) { |
| overlay->controller_->CloseAndFocusInitiator(); |
| overlay->RecordButtonPressed(OverlayWindowControl::kBackToTab); |
| }, |
| base::Unretained(this)); |
| if (base::FeatureList::IsEnabled(media::kMediaSessionWebRTC)) { |
| back_to_tab_label_button = |
| std::make_unique<BackToTabLabelButton>(std::move(back_to_tab_callback)); |
| } else { |
| back_to_tab_image_button = std::make_unique<views::BackToTabImageButton>( |
| std::move(back_to_tab_callback)); |
| } |
| |
| auto previous_track_controls_view = std::make_unique<views::TrackImageButton>( |
| base::BindRepeating( |
| [](OverlayWindowViews* overlay) { |
| overlay->controller_->PreviousTrack(); |
| overlay->RecordButtonPressed(OverlayWindowControl::kPreviousTrack); |
| }, |
| base::Unretained(this)), |
| vector_icons::kMediaPreviousTrackIcon, |
| l10n_util::GetStringUTF16( |
| IDS_PICTURE_IN_PICTURE_PREVIOUS_TRACK_CONTROL_ACCESSIBLE_TEXT)); |
| auto play_pause_controls_view = |
| std::make_unique<views::PlaybackImageButton>(base::BindRepeating( |
| [](OverlayWindowViews* overlay) { |
| overlay->TogglePlayPause(); |
| overlay->RecordButtonPressed(OverlayWindowControl::kPlayPause); |
| }, |
| base::Unretained(this))); |
| auto next_track_controls_view = std::make_unique<views::TrackImageButton>( |
| base::BindRepeating( |
| [](OverlayWindowViews* overlay) { |
| overlay->controller_->NextTrack(); |
| overlay->RecordButtonPressed(OverlayWindowControl::kNextTrack); |
| }, |
| base::Unretained(this)), |
| vector_icons::kMediaNextTrackIcon, |
| l10n_util::GetStringUTF16( |
| IDS_PICTURE_IN_PICTURE_NEXT_TRACK_CONTROL_ACCESSIBLE_TEXT)); |
| auto skip_ad_controls_view = |
| std::make_unique<views::SkipAdLabelButton>(base::BindRepeating( |
| [](OverlayWindowViews* overlay) { |
| overlay->controller_->SkipAd(); |
| overlay->RecordButtonPressed(OverlayWindowControl::kSkipAd); |
| }, |
| base::Unretained(this))); |
| auto toggle_microphone_button = |
| std::make_unique<ToggleMicrophoneButton>(base::BindRepeating( |
| [](OverlayWindowViews* overlay) { |
| overlay->controller_->ToggleMicrophone(); |
| overlay->RecordButtonPressed( |
| OverlayWindowControl::kToggleMicrophone); |
| }, |
| base::Unretained(this))); |
| auto toggle_camera_button = |
| std::make_unique<ToggleCameraButton>(base::BindRepeating( |
| [](OverlayWindowViews* overlay) { |
| overlay->controller_->ToggleCamera(); |
| overlay->RecordButtonPressed(OverlayWindowControl::kToggleCamera); |
| }, |
| base::Unretained(this))); |
| auto hang_up_button = std::make_unique<HangUpButton>(base::BindRepeating( |
| [](OverlayWindowViews* overlay) { |
| overlay->controller_->HangUp(); |
| overlay->RecordButtonPressed(OverlayWindowControl::kHangUp); |
| }, |
| base::Unretained(this))); |
| #if BUILDFLAG(IS_CHROMEOS_ASH) |
| auto resize_handle_view = std::make_unique<views::ResizeHandleButton>( |
| views::Button::PressedCallback()); |
| #endif |
| |
| window_background_view->SetPaintToLayer(ui::LAYER_SOLID_COLOR); |
| window_background_view->layer()->SetName("WindowBackgroundView"); |
| window_background_view->layer()->SetColor(SK_ColorBLACK); |
| |
| // view::View that holds the video. ----------------------------------------- |
| video_view->SetPaintToLayer(ui::LAYER_TEXTURED); |
| video_view->layer()->SetMasksToBounds(true); |
| video_view->layer()->SetFillsBoundsOpaquely(false); |
| video_view->layer()->SetName("VideoView"); |
| |
| // views::View that holds the scrim, which appears with the controls. ------- |
| controls_scrim_view->SetPaintToLayer(ui::LAYER_SOLID_COLOR); |
| controls_scrim_view->layer()->SetName("ControlsScrimView"); |
| controls_scrim_view->layer()->SetColor(gfx::kGoogleGrey900); |
| controls_scrim_view->layer()->SetOpacity(kControlsScrimOpacity); |
| |
| // views::View that is a parent of all the controls. Makes hiding and showing |
| // all the controls at once easier. |
| controls_container_view->SetPaintToLayer(ui::LAYER_NOT_DRAWN); |
| controls_container_view->layer()->SetFillsBoundsOpaquely(false); |
| controls_container_view->layer()->SetName("ControlsContainerView"); |
| |
| // views::View that closes the window. -------------------------------------- |
| close_controls_view->SetPaintToLayer(ui::LAYER_TEXTURED); |
| close_controls_view->layer()->SetFillsBoundsOpaquely(false); |
| close_controls_view->layer()->SetName("CloseControlsView"); |
| |
| // views::View that closes the window and focuses initiator tab. ------------ |
| if (back_to_tab_image_button) { |
| back_to_tab_image_button->SetPaintToLayer(ui::LAYER_TEXTURED); |
| back_to_tab_image_button->layer()->SetFillsBoundsOpaquely(false); |
| back_to_tab_image_button->layer()->SetName("BackToTabControlsView"); |
| } else { |
| DCHECK(back_to_tab_label_button); |
| back_to_tab_label_button->SetPaintToLayer(ui::LAYER_TEXTURED); |
| back_to_tab_label_button->layer()->SetFillsBoundsOpaquely(false); |
| back_to_tab_label_button->layer()->SetName("BackToTabControlsView"); |
| } |
| |
| // views::View that holds the previous-track image button. ------------------ |
| previous_track_controls_view->SetPaintToLayer(ui::LAYER_TEXTURED); |
| previous_track_controls_view->layer()->SetFillsBoundsOpaquely(false); |
| previous_track_controls_view->layer()->SetName("PreviousTrackControlsView"); |
| |
| // views::View that toggles play/pause/replay. ------------------------------ |
| play_pause_controls_view->SetPaintToLayer(ui::LAYER_TEXTURED); |
| play_pause_controls_view->layer()->SetFillsBoundsOpaquely(false); |
| play_pause_controls_view->layer()->SetName("PlayPauseControlsView"); |
| play_pause_controls_view->SetPlaybackState( |
| controller_->IsPlayerActive() ? kPlaying : kPaused); |
| |
| // views::View that holds the next-track image button. ---------------------- |
| next_track_controls_view->SetPaintToLayer(ui::LAYER_TEXTURED); |
| next_track_controls_view->layer()->SetFillsBoundsOpaquely(false); |
| next_track_controls_view->layer()->SetName("NextTrackControlsView"); |
| |
| // views::View that holds the skip-ad label button. ------------------------- |
| skip_ad_controls_view->SetPaintToLayer(ui::LAYER_TEXTURED); |
| skip_ad_controls_view->layer()->SetFillsBoundsOpaquely(true); |
| skip_ad_controls_view->layer()->SetName("SkipAdControlsView"); |
| |
| toggle_microphone_button->SetPaintToLayer(ui::LAYER_TEXTURED); |
| toggle_microphone_button->layer()->SetFillsBoundsOpaquely(false); |
| toggle_microphone_button->layer()->SetName("ToggleMicrophoneButton"); |
| |
| toggle_camera_button->SetPaintToLayer(ui::LAYER_TEXTURED); |
| toggle_camera_button->layer()->SetFillsBoundsOpaquely(false); |
| toggle_camera_button->layer()->SetName("ToggleCameraButton"); |
| |
| hang_up_button->SetPaintToLayer(ui::LAYER_TEXTURED); |
| hang_up_button->layer()->SetFillsBoundsOpaquely(false); |
| hang_up_button->layer()->SetName("HangUpButton"); |
| |
| #if BUILDFLAG(IS_CHROMEOS_ASH) |
| // views::View that shows the affordance that the window can be resized. ---- |
| resize_handle_view->SetPaintToLayer(ui::LAYER_TEXTURED); |
| resize_handle_view->layer()->SetFillsBoundsOpaquely(false); |
| resize_handle_view->layer()->SetName("ResizeHandleView"); |
| resize_handle_view->layer()->SetOpacity(kResizeHandleOpacity); |
| #endif |
| |
| // Set up view::Views hierarchy. -------------------------------------------- |
| window_background_view_ = |
| AddChildView(&view_holder_, std::move(window_background_view)); |
| video_view_ = AddChildView(&view_holder_, std::move(video_view)); |
| controls_scrim_view_ = |
| controls_container_view->AddChildView(std::move(controls_scrim_view)); |
| close_controls_view_ = |
| controls_container_view->AddChildView(std::move(close_controls_view)); |
| |
| if (back_to_tab_image_button) { |
| back_to_tab_image_button_ = controls_container_view->AddChildView( |
| std::move(back_to_tab_image_button)); |
| } else { |
| DCHECK(back_to_tab_label_button); |
| back_to_tab_label_button_ = controls_container_view->AddChildView( |
| std::move(back_to_tab_label_button)); |
| } |
| |
| previous_track_controls_view_ = controls_container_view->AddChildView( |
| std::move(previous_track_controls_view)); |
| play_pause_controls_view_ = controls_container_view->AddChildView( |
| std::move(play_pause_controls_view)); |
| next_track_controls_view_ = controls_container_view->AddChildView( |
| std::move(next_track_controls_view)); |
| skip_ad_controls_view_ = |
| controls_container_view->AddChildView(std::move(skip_ad_controls_view)); |
| toggle_microphone_button_ = controls_container_view->AddChildView( |
| std::move(toggle_microphone_button)); |
| toggle_camera_button_ = |
| controls_container_view->AddChildView(std::move(toggle_camera_button)); |
| hang_up_button_ = |
| controls_container_view->AddChildView(std::move(hang_up_button)); |
| #if BUILDFLAG(IS_CHROMEOS_ASH) |
| resize_handle_view_ = |
| controls_container_view->AddChildView(std::move(resize_handle_view)); |
| #endif |
| controls_container_view_ = |
| AddChildView(&view_holder_, std::move(controls_container_view)); |
| } |
| |
| void OverlayWindowViews::OnRootViewReady() { |
| #if BUILDFLAG(IS_CHROMEOS_ASH) |
| GetNativeWindow()->SetProperty(ash::kWindowPipTypeKey, true); |
| #endif // BUILDFLAG(IS_CHROMEOS_ASH) |
| |
| GetRootView()->SetPaintToLayer(ui::LAYER_TEXTURED); |
| GetRootView()->layer()->SetName("RootView"); |
| GetRootView()->layer()->SetMasksToBounds(true); |
| |
| views::View* const contents_view = GetContentsView(); |
| for (std::unique_ptr<views::View>& child : view_holder_) |
| contents_view->AddChildView(std::move(child)); |
| view_holder_.clear(); |
| |
| // Don't show the controls until the mouse hovers over the window. |
| UpdateControlsVisibility(false); |
| } |
| |
| void OverlayWindowViews::UpdateLayerBoundsWithLetterboxing( |
| gfx::Size window_size) { |
| // This is the case when the window is initially created or the video surface |
| // id has not been embedded. |
| if (window_bounds_.size().IsEmpty() || natural_size_.IsEmpty()) |
| return; |
| |
| gfx::Rect letterbox_region = media::ComputeLetterboxRegion( |
| gfx::Rect(gfx::Point(0, 0), window_size), natural_size_); |
| if (letterbox_region.IsEmpty()) |
| return; |
| |
| // To avoid black stripes in the window when integer window dimensions don't |
| // correspond to the video aspect ratio exactly (e.g. 854x480 for 16:9 |
| // video) force the letterbox region size to be equal to the window size. |
| const float aspect_ratio = |
| static_cast<float>(natural_size_.width()) / natural_size_.height(); |
| if (aspect_ratio > 1 && window_size.height() == letterbox_region.height()) { |
| const int height_from_width = |
| base::ClampRound(window_size.width() / aspect_ratio); |
| if (height_from_width == window_size.height()) |
| letterbox_region.set_width(window_size.width()); |
| } else if (aspect_ratio <= 1 && |
| window_size.width() == letterbox_region.width()) { |
| const int width_from_height = |
| base::ClampRound(window_size.height() * aspect_ratio); |
| if (width_from_height == window_size.width()) |
| letterbox_region.set_height(window_size.height()); |
| } |
| |
| gfx::Size letterbox_size = letterbox_region.size(); |
| gfx::Point origin = |
| gfx::Point((window_size.width() - letterbox_size.width()) / 2, |
| (window_size.height() - letterbox_size.height()) / 2); |
| |
| video_bounds_.set_origin(origin); |
| video_bounds_.set_size(letterbox_region.size()); |
| |
| // Update the layout of the controls. |
| UpdateControlsBounds(); |
| |
| // Update the surface layer bounds to scale with window size changes. |
| window_background_view_->SetBoundsRect( |
| gfx::Rect(gfx::Point(0, 0), GetBounds().size())); |
| video_view_->SetBoundsRect(video_bounds_); |
| if (video_view_->layer()->has_external_content()) |
| video_view_->layer()->SetSurfaceSize(video_bounds_.size()); |
| |
| // Notify the controller that the bounds have changed. |
| controller_->UpdateLayerBounds(); |
| } |
| |
| void OverlayWindowViews::UpdateControlsVisibility(bool is_visible) { |
| controls_container_view_->SetVisible( |
| force_controls_visible_.value_or(is_visible)); |
| } |
| |
| void OverlayWindowViews::UpdateControlsBounds() { |
| // If controls are hidden, let's update controls bounds immediately. |
| // Otherwise, wait a bit before updating controls bounds to avoid too many |
| // changes happening too quickly. |
| if (!AreControlsVisible()) { |
| OnUpdateControlsBounds(); |
| return; |
| } |
| |
| update_controls_bounds_timer_ = std::make_unique<base::OneShotTimer>(); |
| update_controls_bounds_timer_->Start( |
| FROM_HERE, base::TimeDelta::FromSeconds(1), |
| base::BindOnce(&OverlayWindowViews::OnUpdateControlsBounds, |
| base::Unretained(this))); |
| } |
| |
| void OverlayWindowViews::OnUpdateControlsBounds() { |
| controls_container_view_->SetSize(GetBounds().size()); |
| |
| // Adding an extra pixel to width/height makes sure the scrim covers the |
| // entire window when the platform has fractional scaling applied. |
| gfx::Rect larger_window_bounds = gfx::Rect(GetBounds().size()); |
| larger_window_bounds.Inset(-1, -1); |
| controls_scrim_view_->SetBoundsRect(larger_window_bounds); |
| |
| WindowQuadrant quadrant = GetCurrentWindowQuadrant(GetBounds(), controller_); |
| close_controls_view_->SetPosition(GetBounds().size(), quadrant); |
| |
| if (back_to_tab_label_button_) |
| back_to_tab_label_button_->SetWindowSize(GetBounds().size()); |
| |
| #if BUILDFLAG(IS_CHROMEOS_ASH) |
| resize_handle_view_->SetPosition(GetBounds().size(), quadrant); |
| #endif |
| |
| skip_ad_controls_view_->SetPosition(GetBounds().size()); |
| |
| // Following controls order matters: |
| // #1 Back to tab |
| // #2 Previous track |
| // #3 Play/Pause |
| // #4 Next track |
| // #5 Toggle microphone |
| // #6 Toggle camera |
| // #7 Hang up |
| std::vector<views::ImageButton*> visible_controls_views; |
| if (back_to_tab_image_button_) |
| visible_controls_views.push_back(back_to_tab_image_button_); |
| if (show_previous_track_button_) |
| visible_controls_views.push_back(previous_track_controls_view_); |
| if (show_play_pause_button_) |
| visible_controls_views.push_back(play_pause_controls_view_); |
| if (show_next_track_button_) |
| visible_controls_views.push_back(next_track_controls_view_); |
| if (show_toggle_microphone_button_) |
| visible_controls_views.push_back(toggle_microphone_button_); |
| if (show_toggle_camera_button_) |
| visible_controls_views.push_back(toggle_camera_button_); |
| if (show_hang_up_button_) |
| visible_controls_views.push_back(hang_up_button_); |
| |
| if (visible_controls_views.size() > 4) |
| visible_controls_views.resize(4); |
| |
| int mid_window_x = GetBounds().size().width() / 2; |
| int primary_control_y = GetBounds().size().height() - |
| kPrimaryControlSize.height() - |
| kPrimaryControlBottomMargin; |
| int secondary_control_y = GetBounds().size().height() - |
| kSecondaryControlSize.height() - |
| kSecondaryControlBottomMargin; |
| |
| switch (visible_controls_views.size()) { |
| case 0: |
| DCHECK(back_to_tab_label_button_); |
| break; |
| case 1: { |
| /* | --- --- [ ] --- --- | */ |
| visible_controls_views[0]->SetSize(kSecondaryControlSize); |
| visible_controls_views[0]->SetPosition( |
| gfx::Point(mid_window_x - kSecondaryControlSize.width() / 2, |
| secondary_control_y)); |
| break; |
| } |
| case 2: { |
| /* | ----- [ ] [ ] ----- | */ |
| visible_controls_views[0]->SetSize(kSecondaryControlSize); |
| visible_controls_views[0]->SetPosition(gfx::Point( |
| mid_window_x - kControlMargin / 2 - kSecondaryControlSize.width(), |
| secondary_control_y)); |
| |
| visible_controls_views[1]->SetSize(kSecondaryControlSize); |
| visible_controls_views[1]->SetPosition( |
| gfx::Point(mid_window_x + kControlMargin / 2, secondary_control_y)); |
| break; |
| } |
| case 3: { |
| /* | --- [ ] [ ] [ ] --- | */ |
| // Middle control is primary only if it's play/pause control. |
| if (visible_controls_views[1] == play_pause_controls_view_) { |
| visible_controls_views[0]->SetSize(kSecondaryControlSize); |
| visible_controls_views[0]->SetPosition( |
| gfx::Point(mid_window_x - kPrimaryControlSize.width() / 2 - |
| kControlMargin - kSecondaryControlSize.width(), |
| secondary_control_y)); |
| |
| visible_controls_views[1]->SetSize(kPrimaryControlSize); |
| visible_controls_views[1]->SetPosition(gfx::Point( |
| mid_window_x - kPrimaryControlSize.width() / 2, primary_control_y)); |
| |
| visible_controls_views[2]->SetSize(kSecondaryControlSize); |
| visible_controls_views[2]->SetPosition(gfx::Point( |
| mid_window_x + kPrimaryControlSize.width() / 2 + kControlMargin, |
| secondary_control_y)); |
| } else { |
| visible_controls_views[0]->SetSize(kSecondaryControlSize); |
| visible_controls_views[0]->SetPosition( |
| gfx::Point(mid_window_x - kSecondaryControlSize.width() / 2 - |
| kControlMargin - kSecondaryControlSize.width(), |
| secondary_control_y)); |
| |
| visible_controls_views[1]->SetSize(kSecondaryControlSize); |
| visible_controls_views[1]->SetPosition( |
| gfx::Point(mid_window_x - kSecondaryControlSize.width() / 2, |
| secondary_control_y)); |
| |
| visible_controls_views[2]->SetSize(kSecondaryControlSize); |
| visible_controls_views[2]->SetPosition(gfx::Point( |
| mid_window_x + kSecondaryControlSize.width() / 2 + kControlMargin, |
| secondary_control_y)); |
| } |
| break; |
| } |
| case 4: { |
| /* | - [ ] [ ] [ ] [ ] - | */ |
| visible_controls_views[0]->SetSize(kSecondaryControlSize); |
| visible_controls_views[0]->SetPosition( |
| gfx::Point(mid_window_x - kControlMargin * 3 / 2 - |
| kSecondaryControlSize.width() * 2, |
| secondary_control_y)); |
| |
| visible_controls_views[1]->SetSize(kSecondaryControlSize); |
| visible_controls_views[1]->SetPosition(gfx::Point( |
| mid_window_x - kControlMargin / 2 - kSecondaryControlSize.width(), |
| secondary_control_y)); |
| |
| visible_controls_views[2]->SetSize(kSecondaryControlSize); |
| visible_controls_views[2]->SetPosition( |
| gfx::Point(mid_window_x + kControlMargin / 2, secondary_control_y)); |
| |
| visible_controls_views[3]->SetSize(kSecondaryControlSize); |
| visible_controls_views[3]->SetPosition(gfx::Point( |
| mid_window_x + kControlMargin * 3 / 2 + kSecondaryControlSize.width(), |
| secondary_control_y)); |
| break; |
| } |
| default: |
| NOTREACHED(); |
| } |
| |
| // This will actually update the visibility of a control that was just added |
| // or removed, see SetPlayPauseButtonVisibility(), etc. |
| previous_track_controls_view_->SetVisible(show_previous_track_button_); |
| play_pause_controls_view_->SetVisible(show_play_pause_button_); |
| next_track_controls_view_->SetVisible(show_next_track_button_); |
| skip_ad_controls_view_->SetVisible(show_skip_ad_button_); |
| toggle_microphone_button_->SetVisible(show_toggle_microphone_button_); |
| toggle_camera_button_->SetVisible(show_toggle_camera_button_); |
| hang_up_button_->SetVisible(show_hang_up_button_); |
| } |
| |
| gfx::Rect OverlayWindowViews::CalculateControlsBounds(int x, |
| const gfx::Size& size) { |
| return gfx::Rect( |
| gfx::Point(x, (GetBounds().size().height() - size.height()) / 2), size); |
| } |
| |
| bool OverlayWindowViews::IsActive() { |
| return views::Widget::IsActive(); |
| } |
| |
| bool OverlayWindowViews::IsActive() const { |
| return views::Widget::IsActive(); |
| } |
| |
| void OverlayWindowViews::Close() { |
| views::Widget::Close(); |
| |
| if (auto* frame_sink_id = GetCurrentFrameSinkId()) |
| GetCompositor()->RemoveChildFrameSink(*frame_sink_id); |
| } |
| |
| void OverlayWindowViews::ShowInactive() { |
| if (back_to_tab_label_button_) { |
| back_to_tab_label_button_->SetText(url_formatter::FormatUrl( |
| controller_->GetWebContents()->GetLastCommittedURL(), |
| url_formatter::kFormatUrlOmitDefaults | |
| url_formatter::kFormatUrlOmitHTTPS | |
| url_formatter::kFormatUrlOmitTrivialSubdomains | |
| url_formatter::kFormatUrlTrimAfterHost, |
| net::UnescapeRule::SPACES, nullptr, nullptr, nullptr)); |
| } |
| |
| views::Widget::ShowInactive(); |
| views::Widget::SetVisibleOnAllWorkspaces(true); |
| #if BUILDFLAG(IS_CHROMEOS_ASH) |
| // For rounded corners. |
| if (ash::features::IsPipRoundedCornersEnabled()) { |
| decorator_ = std::make_unique<ash::RoundedCornerDecorator>( |
| GetNativeWindow(), GetNativeWindow(), GetRootView()->layer(), |
| ash::kPipRoundedCornerRadius); |
| } |
| #endif |
| |
| // If this is not the first time the window is shown, this will be a no-op. |
| has_been_shown_ = true; |
| } |
| |
| void OverlayWindowViews::Hide() { |
| views::Widget::Hide(); |
| } |
| |
| bool OverlayWindowViews::IsVisible() { |
| return views::Widget::IsVisible(); |
| } |
| |
| bool OverlayWindowViews::IsVisible() const { |
| return views::Widget::IsVisible(); |
| } |
| |
| bool OverlayWindowViews::IsAlwaysOnTop() { |
| return true; |
| } |
| |
| gfx::Rect OverlayWindowViews::GetBounds() { |
| return views::Widget::GetRestoredBounds(); |
| } |
| |
| void OverlayWindowViews::UpdateVideoSize(const gfx::Size& natural_size) { |
| DCHECK(!natural_size.IsEmpty()); |
| natural_size_ = natural_size; |
| SetAspectRatio(gfx::SizeF(natural_size_)); |
| |
| // Update the views::Widget bounds to adhere to sizing spec. This will also |
| // update the layout of the controls. |
| SetBounds(CalculateAndUpdateWindowBounds()); |
| } |
| |
| void OverlayWindowViews::SetPlaybackState(PlaybackState playback_state) { |
| playback_state_for_testing_ = playback_state; |
| play_pause_controls_view_->SetPlaybackState(playback_state); |
| } |
| |
| void OverlayWindowViews::SetPlayPauseButtonVisibility(bool is_visible) { |
| if (show_play_pause_button_ == is_visible) |
| return; |
| |
| show_play_pause_button_ = is_visible; |
| UpdateControlsBounds(); |
| } |
| |
| void OverlayWindowViews::SetSkipAdButtonVisibility(bool is_visible) { |
| if (show_skip_ad_button_ == is_visible) |
| return; |
| |
| show_skip_ad_button_ = is_visible; |
| UpdateControlsBounds(); |
| } |
| |
| void OverlayWindowViews::SetNextTrackButtonVisibility(bool is_visible) { |
| if (show_next_track_button_ == is_visible) |
| return; |
| |
| show_next_track_button_ = is_visible; |
| UpdateControlsBounds(); |
| } |
| |
| void OverlayWindowViews::SetPreviousTrackButtonVisibility(bool is_visible) { |
| if (show_previous_track_button_ == is_visible) |
| return; |
| |
| show_previous_track_button_ = is_visible; |
| UpdateControlsBounds(); |
| } |
| |
| void OverlayWindowViews::SetMicrophoneMuted(bool muted) { |
| toggle_microphone_button_->SetMutedState(muted); |
| } |
| |
| void OverlayWindowViews::SetCameraState(bool turned_on) { |
| toggle_camera_button_->SetCameraState(turned_on); |
| } |
| |
| void OverlayWindowViews::SetToggleMicrophoneButtonVisibility(bool is_visible) { |
| if (show_toggle_microphone_button_ == is_visible) |
| return; |
| |
| show_toggle_microphone_button_ = is_visible; |
| UpdateControlsBounds(); |
| } |
| |
| void OverlayWindowViews::SetToggleCameraButtonVisibility(bool is_visible) { |
| if (show_toggle_camera_button_ == is_visible) |
| return; |
| |
| show_toggle_camera_button_ = is_visible; |
| UpdateControlsBounds(); |
| } |
| |
| void OverlayWindowViews::SetHangUpButtonVisibility(bool is_visible) { |
| if (show_hang_up_button_ == is_visible) |
| return; |
| |
| show_hang_up_button_ = is_visible; |
| UpdateControlsBounds(); |
| } |
| |
| void OverlayWindowViews::SetSurfaceId(const viz::SurfaceId& surface_id) { |
| // TODO(https://crbug.com/925346): We also want to unregister the page that |
| // used to embed the video as its parent. |
| if (!GetCurrentFrameSinkId()) { |
| GetCompositor()->AddChildFrameSink(surface_id.frame_sink_id()); |
| } else if (*GetCurrentFrameSinkId() != surface_id.frame_sink_id()) { |
| GetCompositor()->RemoveChildFrameSink(*GetCurrentFrameSinkId()); |
| GetCompositor()->AddChildFrameSink(surface_id.frame_sink_id()); |
| } |
| |
| video_view_->layer()->SetShowSurface( |
| surface_id, GetBounds().size(), SK_ColorBLACK, |
| cc::DeadlinePolicy::UseDefaultDeadline(), |
| true /* stretch_content_to_fill_bounds */); |
| } |
| |
| void OverlayWindowViews::OnNativeBlur() { |
| // Controls should be hidden when there is no more focus on the window. This |
| // is used for tabbing and touch interactions. For mouse interactions, the |
| // window cannot be blurred before the ui::ET_MOUSE_EXITED event is handled. |
| UpdateControlsVisibility(false); |
| |
| views::Widget::OnNativeBlur(); |
| } |
| |
| void OverlayWindowViews::OnNativeWidgetDestroyed() { |
| controller_->OnWindowDestroyed(); |
| } |
| |
| gfx::Size OverlayWindowViews::GetMinimumSize() const { |
| return min_size_; |
| } |
| |
| gfx::Size OverlayWindowViews::GetMaximumSize() const { |
| return max_size_; |
| } |
| |
| void OverlayWindowViews::OnNativeWidgetMove() { |
| // Hide the controls when the window is moving. The controls will reappear |
| // when the user interacts with the window again. |
| UpdateControlsVisibility(false); |
| |
| // Update the existing |window_bounds_| when the window moves. This allows |
| // the window to reappear with the same origin point when a new video is |
| // shown. |
| window_bounds_ = GetBounds(); |
| |
| // Update the maximum size of the widget in case we have moved to another |
| // window. |
| UpdateMaxSize(GetWorkAreaForWindow()); |
| |
| #if BUILDFLAG(IS_CHROMEOS_ASH) |
| // Update the positioning of some icons when the window is moved. |
| WindowQuadrant quadrant = GetCurrentWindowQuadrant(GetBounds(), controller_); |
| close_controls_view_->SetPosition(GetBounds().size(), quadrant); |
| resize_handle_view_->SetPosition(GetBounds().size(), quadrant); |
| #endif |
| } |
| |
| void OverlayWindowViews::OnNativeWidgetSizeChanged(const gfx::Size& new_size) { |
| // Hide the controls when the window is being resized. The controls will |
| // reappear when the user interacts with the window again. |
| UpdateControlsVisibility(false); |
| |
| // Update the view layers to scale to |new_size|. |
| UpdateLayerBoundsWithLetterboxing(new_size); |
| |
| views::Widget::OnNativeWidgetSizeChanged(new_size); |
| } |
| |
| void OverlayWindowViews::OnNativeWidgetWorkspaceChanged() { |
| // TODO(apacible): Update sizes and maybe resize the current |
| // Picture-in-Picture window. Currently, switching between workspaces on linux |
| // does not trigger this function. http://crbug.com/819673 |
| } |
| |
| void OverlayWindowViews::OnKeyEvent(ui::KeyEvent* event) { |
| // Every time a user uses a keyboard to interact on the window, restart the |
| // timer to automatically hide the controls. |
| hide_controls_timer_.Reset(); |
| |
| // Any keystroke will make the controls visible, if not already. The Tab key |
| // needs to be handled separately. |
| // If the controls are already visible, this is a no-op. |
| if (event->type() == ui::ET_KEY_PRESSED || |
| event->key_code() == ui::VKEY_TAB) { |
| UpdateControlsVisibility(true); |
| } |
| |
| // If there is no focus affordance on the buttons, only handle space key to |
| // for TogglePlayPause(). |
| views::View* focused_view = GetFocusManager()->GetFocusedView(); |
| if (!focused_view && event->type() == ui::ET_KEY_PRESSED && |
| event->key_code() == ui::VKEY_SPACE) { |
| TogglePlayPause(); |
| event->SetHandled(); |
| } |
| |
| // On Windows, the Alt+F4 keyboard combination closes the window. Only handle |
| // closure on key press so Close() is not called a second time when the key |
| // is released. |
| #if defined(OS_WIN) |
| if (event->type() == ui::ET_KEY_PRESSED && event->IsAltDown() && |
| event->key_code() == ui::VKEY_F4) { |
| controller_->Close(true /* should_pause_video */); |
| event->SetHandled(); |
| } |
| #endif // OS_WIN |
| |
| views::Widget::OnKeyEvent(event); |
| } |
| |
| void OverlayWindowViews::OnMouseEvent(ui::MouseEvent* event) { |
| switch (event->type()) { |
| // Only show the media controls when the mouse is hovering over the window. |
| case ui::ET_MOUSE_MOVED: |
| case ui::ET_MOUSE_ENTERED: |
| UpdateControlsVisibility(true); |
| break; |
| |
| case ui::ET_MOUSE_EXITED: |
| // On Windows, ui::ET_MOUSE_EXITED is triggered when hovering over the |
| // media controls because of the HitTest. This check ensures the controls |
| // are visible if the mouse is still over the window. |
| if (!video_bounds_.Contains(event->location())) |
| UpdateControlsVisibility(false); |
| break; |
| |
| default: |
| break; |
| } |
| |
| // If the user interacts with the window using a mouse, stop the timer to |
| // automatically hide the controls. |
| hide_controls_timer_.Reset(); |
| |
| views::Widget::OnMouseEvent(event); |
| } |
| |
| void OverlayWindowViews::OnGestureEvent(ui::GestureEvent* event) { |
| if (event->type() != ui::ET_GESTURE_TAP) |
| return; |
| |
| // Every time a user taps on the window, restart the timer to automatically |
| // hide the controls. |
| hide_controls_timer_.Reset(); |
| |
| // If the controls were not shown, make them visible. All controls related |
| // layers are expected to have the same visibility. |
| // TODO(apacible): This placeholder logic should be updated with touchscreen |
| // specific investigation. https://crbug/854373 |
| if (!AreControlsVisible()) { |
| UpdateControlsVisibility(true); |
| return; |
| } |
| |
| if (GetBackToTabControlsBounds().Contains(event->location())) { |
| controller_->CloseAndFocusInitiator(); |
| RecordTapGesture(OverlayWindowControl::kBackToTab); |
| event->SetHandled(); |
| } else if (GetSkipAdControlsBounds().Contains(event->location())) { |
| controller_->SkipAd(); |
| RecordTapGesture(OverlayWindowControl::kSkipAd); |
| event->SetHandled(); |
| } else if (GetCloseControlsBounds().Contains(event->location())) { |
| controller_->Close(true /* should_pause_video */); |
| RecordTapGesture(OverlayWindowControl::kClose); |
| event->SetHandled(); |
| } else if (GetPlayPauseControlsBounds().Contains(event->location())) { |
| TogglePlayPause(); |
| RecordTapGesture(OverlayWindowControl::kPlayPause); |
| event->SetHandled(); |
| } else if (GetNextTrackControlsBounds().Contains(event->location())) { |
| controller_->NextTrack(); |
| RecordTapGesture(OverlayWindowControl::kNextTrack); |
| event->SetHandled(); |
| } else if (GetPreviousTrackControlsBounds().Contains(event->location())) { |
| controller_->PreviousTrack(); |
| RecordTapGesture(OverlayWindowControl::kPreviousTrack); |
| event->SetHandled(); |
| } else if (GetToggleMicrophoneButtonBounds().Contains(event->location())) { |
| controller_->ToggleMicrophone(); |
| RecordTapGesture(OverlayWindowControl::kToggleMicrophone); |
| event->SetHandled(); |
| } else if (GetToggleCameraButtonBounds().Contains(event->location())) { |
| controller_->ToggleCamera(); |
| RecordTapGesture(OverlayWindowControl::kToggleCamera); |
| event->SetHandled(); |
| } else if (GetHangUpButtonBounds().Contains(event->location())) { |
| controller_->HangUp(); |
| RecordTapGesture(OverlayWindowControl::kHangUp); |
| event->SetHandled(); |
| } |
| } |
| |
| void OverlayWindowViews::RecordTapGesture(OverlayWindowControl window_control) { |
| UMA_HISTOGRAM_ENUMERATION("PictureInPictureWindow.TapGesture", |
| window_control); |
| } |
| |
| void OverlayWindowViews::RecordButtonPressed( |
| OverlayWindowControl window_control) { |
| UMA_HISTOGRAM_ENUMERATION("PictureInPictureWindow.ButtonPressed", |
| window_control); |
| } |
| |
| gfx::Rect OverlayWindowViews::GetBackToTabControlsBounds() { |
| if (back_to_tab_image_button_) |
| return back_to_tab_image_button_->GetMirroredBounds(); |
| |
| DCHECK(back_to_tab_label_button_); |
| return back_to_tab_label_button_->GetMirroredBounds(); |
| } |
| |
| gfx::Rect OverlayWindowViews::GetSkipAdControlsBounds() { |
| return skip_ad_controls_view_->GetMirroredBounds(); |
| } |
| |
| gfx::Rect OverlayWindowViews::GetCloseControlsBounds() { |
| return close_controls_view_->GetMirroredBounds(); |
| } |
| |
| #if BUILDFLAG(IS_CHROMEOS_ASH) |
| gfx::Rect OverlayWindowViews::GetResizeHandleControlsBounds() { |
| return resize_handle_view_->GetMirroredBounds(); |
| } |
| #endif |
| |
| gfx::Rect OverlayWindowViews::GetPlayPauseControlsBounds() { |
| return play_pause_controls_view_->GetMirroredBounds(); |
| } |
| |
| gfx::Rect OverlayWindowViews::GetNextTrackControlsBounds() { |
| return next_track_controls_view_->GetMirroredBounds(); |
| } |
| |
| gfx::Rect OverlayWindowViews::GetPreviousTrackControlsBounds() { |
| return previous_track_controls_view_->GetMirroredBounds(); |
| } |
| |
| gfx::Rect OverlayWindowViews::GetToggleMicrophoneButtonBounds() { |
| return toggle_microphone_button_->GetMirroredBounds(); |
| } |
| |
| gfx::Rect OverlayWindowViews::GetToggleCameraButtonBounds() { |
| return toggle_camera_button_->GetMirroredBounds(); |
| } |
| |
| gfx::Rect OverlayWindowViews::GetHangUpButtonBounds() { |
| return hang_up_button_->GetMirroredBounds(); |
| } |
| |
| #if BUILDFLAG(IS_CHROMEOS_ASH) |
| int OverlayWindowViews::GetResizeHTComponent() const { |
| return resize_handle_view_->GetHTComponent(); |
| } |
| #endif |
| |
| bool OverlayWindowViews::AreControlsVisible() const { |
| return controls_container_view_->GetVisible(); |
| } |
| |
| void OverlayWindowViews::ForceControlsVisibleForTesting(bool visible) { |
| force_controls_visible_ = visible; |
| UpdateControlsVisibility(visible); |
| } |
| |
| bool OverlayWindowViews::IsLayoutPendingForTesting() const { |
| return update_controls_bounds_timer_ && |
| update_controls_bounds_timer_->IsRunning(); |
| } |
| |
| gfx::Rect OverlayWindowViews::GetWorkAreaForWindow() const { |
| return display::Screen::GetScreen() |
| ->GetDisplayNearestWindow( |
| native_widget() && IsVisible() |
| ? GetNativeWindow() |
| : controller_->GetWebContents()->GetTopLevelNativeWindow()) |
| .work_area(); |
| } |
| |
| void OverlayWindowViews::UpdateMaxSize(const gfx::Rect& work_area) { |
| // An empty |work_area| is not valid, but it is sometimes reported as a |
| // transient value. |
| if (work_area.IsEmpty()) |
| return; |
| |
| max_size_ = gfx::Size(work_area.width() / 2, work_area.height() / 2); |
| |
| if (!native_widget()) |
| return; |
| |
| // native_widget() is required for OnSizeConstraintsChanged. |
| OnSizeConstraintsChanged(); |
| |
| if (window_bounds_.width() <= max_size_.width() && |
| window_bounds_.height() <= max_size_.height()) { |
| return; |
| } |
| |
| SetSize(max_size_); |
| } |
| |
| void OverlayWindowViews::TogglePlayPause() { |
| // Retrieve expected active state based on what command was sent in |
| // TogglePlayPause() since the IPC message may not have been propagated |
| // the media player yet. |
| bool is_active = controller_->TogglePlayPause(); |
| play_pause_controls_view_->SetPlaybackState(is_active ? kPlaying : kPaused); |
| } |
| |
| views::PlaybackImageButton* |
| OverlayWindowViews::play_pause_controls_view_for_testing() const { |
| return play_pause_controls_view_; |
| } |
| |
| views::TrackImageButton* |
| OverlayWindowViews::next_track_controls_view_for_testing() const { |
| return next_track_controls_view_; |
| } |
| |
| views::TrackImageButton* |
| OverlayWindowViews::previous_track_controls_view_for_testing() const { |
| return previous_track_controls_view_; |
| } |
| |
| views::SkipAdLabelButton* |
| OverlayWindowViews::skip_ad_controls_view_for_testing() const { |
| return skip_ad_controls_view_; |
| } |
| |
| ToggleMicrophoneButton* |
| OverlayWindowViews::toggle_microphone_button_for_testing() const { |
| return toggle_microphone_button_; |
| } |
| |
| ToggleCameraButton* OverlayWindowViews::toggle_camera_button_for_testing() |
| const { |
| return toggle_camera_button_; |
| } |
| |
| HangUpButton* OverlayWindowViews::hang_up_button_for_testing() const { |
| return hang_up_button_; |
| } |
| |
| BackToTabLabelButton* OverlayWindowViews::back_to_tab_label_button_for_testing() |
| const { |
| return back_to_tab_label_button_; |
| } |
| |
| gfx::Point OverlayWindowViews::close_image_position_for_testing() const { |
| return close_controls_view_->origin(); |
| } |
| |
| #if BUILDFLAG(IS_CHROMEOS_ASH) |
| gfx::Point OverlayWindowViews::resize_handle_position_for_testing() const { |
| return resize_handle_view_->origin(); |
| } |
| #endif |
| |
| OverlayWindowViews::PlaybackState |
| OverlayWindowViews::playback_state_for_testing() const { |
| return playback_state_for_testing_; |
| } |
| |
| ui::Layer* OverlayWindowViews::video_layer_for_testing() const { |
| return video_view_->layer(); |
| } |
| |
| cc::Layer* OverlayWindowViews::GetLayerForTesting() { |
| return GetRootView()->layer()->cc_layer_for_testing(); |
| } |
| |
| const viz::FrameSinkId* OverlayWindowViews::GetCurrentFrameSinkId() const { |
| if (auto* surface = video_view_->layer()->GetSurfaceId()) |
| return &surface->frame_sink_id(); |
| |
| return nullptr; |
| } |