| // Copyright 2018 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 "ash/wm/overview/overview_utils.h" |
| |
| #include <utility> |
| |
| #include "ash/home_screen/home_launcher_gesture_handler.h" |
| #include "ash/home_screen/home_screen_controller.h" |
| #include "ash/public/cpp/ash_features.h" |
| #include "ash/public/cpp/shell_window_ids.h" |
| #include "ash/shell.h" |
| #include "ash/wm/overview/cleanup_animation_observer.h" |
| #include "ash/wm/overview/overview_controller.h" |
| #include "ash/wm/overview/scoped_overview_animation_settings.h" |
| #include "ash/wm/overview/start_animation_observer.h" |
| #include "ash/wm/splitview/split_view_controller.h" |
| #include "ash/wm/splitview/split_view_utils.h" |
| #include "ash/wm/window_state.h" |
| #include "ash/wm/window_transient_descendant_iterator.h" |
| #include "base/no_destructor.h" |
| #include "third_party/skia/include/pathops/SkPathOps.h" |
| #include "ui/aura/window.h" |
| #include "ui/gfx/canvas.h" |
| #include "ui/gfx/scoped_canvas.h" |
| #include "ui/gfx/transform_util.h" |
| #include "ui/views/background.h" |
| #include "ui/views/view.h" |
| #include "ui/views/widget/widget.h" |
| #include "ui/wm/core/coordinate_conversion.h" |
| #include "ui/wm/core/window_animations.h" |
| |
| namespace ash { |
| |
| namespace { |
| |
| // The transform applied to an overview item when animating to or from the home |
| // launcher. |
| const gfx::Transform& GetShiftTransform() { |
| static const base::NoDestructor<gfx::Transform> matrix(1, 0, 0, 1, 0, -100); |
| return *matrix; |
| } |
| |
| // BackgroundWith1PxBorder renders a solid background color, with a one pixel |
| // border with rounded corners. This accounts for the scaling of the canvas, so |
| // that the border is 1 pixel thick regardless of display scaling. |
| class BackgroundWith1PxBorder : public views::Background { |
| public: |
| BackgroundWith1PxBorder(SkColor background, |
| SkColor border_color, |
| int border_thickness, |
| int corner_radius) |
| : border_color_(border_color), |
| border_thickness_(border_thickness), |
| corner_radius_(corner_radius) { |
| SetNativeControlColor(background); |
| } |
| |
| // views::Background: |
| void Paint(gfx::Canvas* canvas, views::View* view) const override { |
| gfx::RectF border_rect_f(view->GetContentsBounds()); |
| |
| gfx::ScopedCanvas scoped_canvas(canvas); |
| const float scale = canvas->UndoDeviceScaleFactor(); |
| border_rect_f.Inset(border_thickness_, border_thickness_); |
| border_rect_f = gfx::ScaleRect(border_rect_f, scale); |
| |
| SkPath path; |
| const SkScalar scaled_corner_radius = |
| SkIntToScalar(gfx::ToCeiledInt(corner_radius_ * scale)); |
| path.addRoundRect(gfx::RectFToSkRect(border_rect_f), scaled_corner_radius, |
| scaled_corner_radius); |
| |
| cc::PaintFlags flags; |
| flags.setStyle(cc::PaintFlags::kStroke_Style); |
| flags.setStrokeWidth(1); |
| flags.setAntiAlias(true); |
| |
| SkPath stroke_path; |
| flags.getFillPath(path, &stroke_path); |
| |
| SkPath fill_path; |
| Op(path, stroke_path, kDifference_SkPathOp, &fill_path); |
| flags.setStyle(cc::PaintFlags::kFill_Style); |
| flags.setColor(get_color()); |
| canvas->sk_canvas()->drawPath(fill_path, flags); |
| |
| if (border_thickness_ > 0) { |
| flags.setColor(border_color_); |
| canvas->sk_canvas()->drawPath(stroke_path, flags); |
| } |
| } |
| |
| private: |
| // Color for the one pixel border. |
| const SkColor border_color_; |
| |
| // Thickness of border inset. |
| const int border_thickness_; |
| |
| // Corner radius of the inside edge of the roundrect border stroke. |
| const int corner_radius_; |
| |
| DISALLOW_COPY_AND_ASSIGN(BackgroundWith1PxBorder); |
| }; |
| |
| } // namespace |
| |
| bool CanCoverAvailableWorkspace(aura::Window* window) { |
| SplitViewController* split_view_controller = |
| Shell::Get()->split_view_controller(); |
| if (split_view_controller->IsSplitViewModeActive()) |
| return CanSnapInSplitview(window); |
| return wm::GetWindowState(window)->IsMaximizedOrFullscreenOrPinned(); |
| } |
| |
| void FadeInWidgetAndMaybeSlideOnEnter(views::Widget* widget, |
| OverviewAnimationType animation_type, |
| bool slide) { |
| aura::Window* window = widget->GetNativeWindow(); |
| if (window->layer()->GetTargetOpacity() == 1.f && !slide) |
| return; |
| |
| gfx::Transform original_transform = window->transform(); |
| if (slide) { |
| // Translate the window up before sliding down to |original_transform|. |
| gfx::Transform new_transform = original_transform; |
| new_transform.ConcatTransform(GetShiftTransform()); |
| if (window->layer()->GetTargetOpacity() == 1.f && |
| window->layer()->GetTargetTransform() == new_transform) { |
| return; |
| } |
| window->SetTransform(new_transform); |
| } |
| window->layer()->SetOpacity(0.0f); |
| ScopedOverviewAnimationSettings scoped_overview_animation_settings( |
| animation_type, window); |
| window->layer()->SetOpacity(1.0f); |
| if (slide) { |
| window->SetTransform(original_transform); |
| |
| auto start_observer = std::make_unique<StartAnimationObserver>(); |
| scoped_overview_animation_settings.AddObserver(start_observer.get()); |
| Shell::Get()->overview_controller()->AddStartAnimationObserver( |
| std::move(start_observer)); |
| } |
| } |
| |
| void FadeOutWidgetAndMaybeSlideOnExit(std::unique_ptr<views::Widget> widget, |
| OverviewAnimationType animation_type) { |
| // The overview controller may be nullptr on shutdown. |
| OverviewController* controller = Shell::Get()->overview_controller(); |
| if (!controller) { |
| widget->SetOpacity(0.f); |
| return; |
| } |
| |
| widget->SetOpacity(1.f); |
| // Fade out the widget. This animation continues past the lifetime of overview |
| // mode items. |
| ScopedOverviewAnimationSettings animation_settings(animation_type, |
| widget->GetNativeWindow()); |
| // CleanupAnimationObserver will delete itself (and the widget) when the |
| // opacity animation is complete. Ownership over the observer is passed to the |
| // overview controller which has longer lifetime so that animations can |
| // continue even after the overview mode is shut down. |
| views::Widget* widget_ptr = widget.get(); |
| auto observer = std::make_unique<CleanupAnimationObserver>(std::move(widget)); |
| animation_settings.AddObserver(observer.get()); |
| controller->AddDelayedAnimationObserver(std::move(observer)); |
| widget_ptr->SetOpacity(0.f); |
| |
| // Slide |widget| towards to top of screen if exit overview to home launcher. |
| if (animation_type == OVERVIEW_ANIMATION_EXIT_TO_HOME_LAUNCHER) { |
| gfx::Transform new_transform = widget_ptr->GetNativeWindow()->transform(); |
| new_transform.ConcatTransform(GetShiftTransform()); |
| widget_ptr->GetNativeWindow()->SetTransform(new_transform); |
| } |
| } |
| |
| std::unique_ptr<views::Widget> CreateBackgroundWidget(aura::Window* root_window, |
| ui::LayerType layer_type, |
| SkColor background_color, |
| int border_thickness, |
| int border_radius, |
| SkColor border_color, |
| float initial_opacity, |
| aura::Window* parent, |
| bool stack_on_top, |
| bool accept_events) { |
| std::unique_ptr<views::Widget> widget = std::make_unique<views::Widget>(); |
| views::Widget::InitParams params; |
| params.type = views::Widget::InitParams::TYPE_POPUP; |
| params.keep_on_top = false; |
| params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; |
| params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW; |
| params.layer_type = layer_type; |
| params.accept_events = accept_events; |
| widget->set_focus_on_creation(false); |
| // Parenting in kShellWindowId_WallpaperContainer allows proper layering of |
| // the shield and selection widgets. Since that container is created with |
| // USE_LOCAL_COORDINATES BoundsInScreenBehavior local bounds in |root_window_| |
| // need to be provided. |
| params.parent = |
| parent ? parent |
| : root_window->GetChildById(kShellWindowId_WallpaperContainer); |
| widget->Init(params); |
| aura::Window* widget_window = widget->GetNativeWindow(); |
| // Disable the "bounce in" animation when showing the window. |
| ::wm::SetWindowVisibilityAnimationTransition(widget_window, |
| ::wm::ANIMATE_NONE); |
| if (params.layer_type == ui::LAYER_SOLID_COLOR) { |
| widget_window->layer()->SetColor(background_color); |
| } else if (params.layer_type == ui::LAYER_TEXTURED) { |
| views::View* content_view = new views::View(); |
| content_view->SetBackground(std::make_unique<BackgroundWith1PxBorder>( |
| background_color, border_color, border_thickness, border_radius)); |
| widget->SetContentsView(content_view); |
| } |
| |
| if (stack_on_top) |
| widget_window->parent()->StackChildAtTop(widget_window); |
| else |
| widget_window->parent()->StackChildAtBottom(widget_window); |
| |
| widget->Show(); |
| widget_window->layer()->SetOpacity(initial_opacity); |
| return widget; |
| } |
| |
| gfx::RectF GetTransformedBounds(aura::Window* transformed_window, |
| int top_inset) { |
| gfx::RectF bounds; |
| for (auto* window : wm::GetTransientTreeIterator(transformed_window)) { |
| // Ignore other window types when computing bounding box of overview target |
| // item. |
| if (window != transformed_window && |
| window->type() != aura::client::WINDOW_TYPE_NORMAL) { |
| continue; |
| } |
| gfx::RectF window_bounds(window->GetTargetBounds()); |
| gfx::Transform new_transform = |
| TransformAboutPivot(gfx::ToRoundedPoint(window_bounds.origin()), |
| window->layer()->GetTargetTransform()); |
| new_transform.TransformRect(&window_bounds); |
| |
| // The preview title is shown above the preview window. Hide the window |
| // header for apps or browser windows with no tabs (web apps) to avoid |
| // showing both the window header and the preview title. |
| if (top_inset > 0) { |
| gfx::RectF header_bounds(window_bounds); |
| header_bounds.set_height(top_inset); |
| new_transform.TransformRect(&header_bounds); |
| window_bounds.Inset(0, header_bounds.height(), 0, 0); |
| } |
| ::wm::TranslateRectToScreen(window->parent(), &window_bounds); |
| bounds.Union(window_bounds); |
| } |
| return bounds; |
| } |
| |
| gfx::RectF GetTargetBoundsInScreen(aura::Window* window) { |
| gfx::RectF bounds; |
| for (auto* window_iter : wm::GetTransientTreeIterator(window)) { |
| // Ignore other window types when computing bounding box of overview target |
| // item. |
| if (window_iter != window && |
| window_iter->type() != aura::client::WINDOW_TYPE_NORMAL) { |
| continue; |
| } |
| gfx::RectF target_bounds(window_iter->GetTargetBounds()); |
| ::wm::TranslateRectToScreen(window_iter->parent(), &target_bounds); |
| bounds.Union(target_bounds); |
| } |
| return bounds; |
| } |
| |
| void SetTransform(aura::Window* window, const gfx::Transform& transform) { |
| gfx::PointF target_origin(GetTargetBoundsInScreen(window).origin()); |
| for (auto* window_iter : wm::GetTransientTreeIterator(window)) { |
| aura::Window* parent_window = window_iter->parent(); |
| gfx::RectF original_bounds(window_iter->GetTargetBounds()); |
| ::wm::TranslateRectToScreen(parent_window, &original_bounds); |
| gfx::Transform new_transform = |
| TransformAboutPivot(gfx::Point(target_origin.x() - original_bounds.x(), |
| target_origin.y() - original_bounds.y()), |
| transform); |
| window_iter->SetTransform(new_transform); |
| } |
| } |
| |
| bool IsSlidingOutOverviewFromShelf() { |
| if (!Shell::Get()->overview_controller()->IsSelecting()) |
| return false; |
| |
| if (Shell::Get() |
| ->home_screen_controller() |
| ->home_launcher_gesture_handler() |
| ->mode() == HomeLauncherGestureHandler::Mode::kSlideUpToShow) { |
| return true; |
| } |
| |
| return false; |
| } |
| |
| } // namespace ash |