| // Copyright 2013 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/window_selector_item.h" |
| |
| #include <algorithm> |
| #include <vector> |
| |
| #include "ash/public/cpp/shell_window_ids.h" |
| #include "ash/public/cpp/window_properties.h" |
| #include "ash/resources/vector_icons/vector_icons.h" |
| #include "ash/shell.h" |
| #include "ash/strings/grit/ash_strings.h" |
| #include "ash/wm/overview/overview_animation_type.h" |
| #include "ash/wm/overview/overview_utils.h" |
| #include "ash/wm/overview/overview_window_drag_controller.h" |
| #include "ash/wm/overview/rounded_rect_view.h" |
| #include "ash/wm/overview/scoped_overview_animation_settings.h" |
| #include "ash/wm/overview/scoped_transform_overview_window.h" |
| #include "ash/wm/overview/window_grid.h" |
| #include "ash/wm/overview/window_selector_controller.h" |
| #include "ash/wm/splitview/split_view_constants.h" |
| #include "ash/wm/splitview/split_view_utils.h" |
| #include "ash/wm/tablet_mode/tablet_mode_controller.h" |
| #include "ash/wm/window_state.h" |
| #include "base/auto_reset.h" |
| #include "base/metrics/user_metrics.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/time/time.h" |
| #include "ui/aura/client/aura_constants.h" |
| #include "ui/base/l10n/l10n_util.h" |
| #include "ui/compositor/layer_animation_sequence.h" |
| #include "ui/compositor/scoped_animation_duration_scale_mode.h" |
| #include "ui/compositor/scoped_layer_animation_settings.h" |
| #include "ui/compositor_extra/shadow.h" |
| #include "ui/gfx/animation/slide_animation.h" |
| #include "ui/gfx/canvas.h" |
| #include "ui/gfx/color_utils.h" |
| #include "ui/gfx/geometry/safe_integer_conversions.h" |
| #include "ui/gfx/image/image_skia_operations.h" |
| #include "ui/gfx/paint_vector_icon.h" |
| #include "ui/strings/grit/ui_strings.h" |
| #include "ui/views/animation/flood_fill_ink_drop_ripple.h" |
| #include "ui/views/animation/ink_drop_impl.h" |
| #include "ui/views/animation/ink_drop_mask.h" |
| #include "ui/views/background.h" |
| #include "ui/views/controls/image_view.h" |
| #include "ui/views/layout/box_layout.h" |
| #include "ui/views/window/non_client_view.h" |
| #include "ui/wm/core/coordinate_conversion.h" |
| #include "ui/wm/core/shadow_types.h" |
| |
| namespace ash { |
| |
| namespace { |
| |
| // In the conceptual overview table, the window margin is the space reserved |
| // around the window within the cell. This margin does not overlap so the |
| // closest distance between adjacent windows will be twice this amount. |
| constexpr int kWindowMargin = 5; |
| |
| // Cover the transformed window including the gaps between the windows with a |
| // transparent shield to block the input events from reaching the transformed |
| // window while in overview. |
| constexpr int kWindowSelectorMargin = kWindowMargin * 2; |
| |
| // Foreground label color. |
| constexpr SkColor kLabelColor = SK_ColorWHITE; |
| |
| // Horizontal padding for the label, on both sides. |
| constexpr int kHorizontalLabelPaddingDp = 12; |
| |
| // Height of an item header. |
| constexpr int kHeaderHeightDp = 40; |
| |
| // Opacity for dimmed items. |
| constexpr float kDimmedItemOpacity = 0.3f; |
| |
| // Opacity for fading out during closing a window. |
| constexpr float kClosingItemOpacity = 0.8f; |
| |
| // Opacity for the item header. |
| constexpr float kHeaderOpacity = 0.1f; |
| |
| // Duration of the header and close button fade in/out when a drag is |
| // started/finished on a window selector item; |
| constexpr int kDragAnimationMs = 167; |
| |
| // Before closing a window animate both the window and the caption to shrink by |
| // this fraction of size. |
| constexpr float kPreCloseScale = 0.02f; |
| |
| // The size in dp of the window icon shown on the overview window next to the |
| // title. |
| constexpr gfx::Size kIconSize{24, 24}; |
| |
| constexpr int kCloseButtonInkDropInsetDp = 2; |
| |
| // Close button color. |
| constexpr SkColor kCloseButtonColor = SK_ColorWHITE; |
| |
| // The colors of the close button ripple. |
| constexpr SkColor kCloseButtonInkDropRippleColor = |
| SkColorSetA(kCloseButtonColor, 0x0F); |
| constexpr SkColor kCloseButtonInkDropRippleHighlightColor = |
| SkColorSetA(kCloseButtonColor, 0x14); |
| |
| // The font delta of the overview window title. |
| constexpr int kLabelFontDelta = 2; |
| |
| constexpr int kShadowElevation = 16; |
| |
| // Values of the backdrop. |
| constexpr int kBackdropRoundingDp = 4; |
| constexpr SkColor kBackdropColor = SkColorSetARGB(0x24, 0xFF, 0xFF, 0xFF); |
| |
| // The amount of translation an item animates by when it is closed by using |
| // swipe to close. |
| constexpr int kSwipeToCloseCloseTranslationDp = 96; |
| |
| // Before dragging an overview window, the window will be scaled up |
| // |kPreDragScale| to indicate its selection. |
| constexpr float kDragWindowScale = 0.04f; |
| |
| std::unique_ptr<views::Widget> CreateBackdropWidget(aura::Window* parent) { |
| auto widget = CreateBackgroundWidget( |
| /*root_window=*/nullptr, ui::LAYER_TEXTURED, kBackdropColor, |
| /*border_thickness=*/0, kBackdropRoundingDp, kBackdropColor, |
| /*initial_opacity=*/1.f, parent, |
| /*stack_on_top=*/false); |
| return widget; |
| } |
| |
| // A Button that has a listener and listens to mouse / gesture events on the |
| // visible part of an overview window. Note that the drag events are only |
| // handled in maximized mode. |
| class ShieldButton : public views::Button { |
| public: |
| ShieldButton(views::ButtonListener* listener, const base::string16& name) |
| : views::Button(listener) { |
| SetAccessibleName(name); |
| } |
| ~ShieldButton() override = default; |
| |
| // When WindowSelectorItem (which is a ButtonListener) is destroyed, its |
| // |item_widget_| is allowed to stay around to complete any animations. |
| // Resetting the listener in all views that are targeted by events is |
| // necessary to prevent a crash when a user clicks on the fading out widget |
| // after the WindowSelectorItem has been destroyed. |
| void ResetListener() { listener_ = nullptr; } |
| |
| // views::Button: |
| bool OnMousePressed(const ui::MouseEvent& event) override { |
| if (listener()) { |
| gfx::Point location(event.location()); |
| views::View::ConvertPointToScreen(this, &location); |
| listener()->HandlePressEvent(location); |
| return true; |
| } |
| return views::Button::OnMousePressed(event); |
| } |
| |
| void OnMouseReleased(const ui::MouseEvent& event) override { |
| if (listener()) { |
| gfx::Point location(event.location()); |
| views::View::ConvertPointToScreen(this, &location); |
| listener()->HandleReleaseEvent(location); |
| return; |
| } |
| views::Button::OnMouseReleased(event); |
| } |
| |
| bool OnMouseDragged(const ui::MouseEvent& event) override { |
| if (listener()) { |
| gfx::Point location(event.location()); |
| views::View::ConvertPointToScreen(this, &location); |
| listener()->HandleDragEvent(location); |
| return true; |
| } |
| return views::Button::OnMouseDragged(event); |
| } |
| |
| void OnGestureEvent(ui::GestureEvent* event) override { |
| if (IsSlidingOutOverviewFromShelf()) { |
| event->SetHandled(); |
| return; |
| } |
| |
| if (listener()) { |
| gfx::Point location(event->location()); |
| views::View::ConvertPointToScreen(this, &location); |
| switch (event->type()) { |
| case ui::ET_GESTURE_TAP_DOWN: |
| listener()->HandlePressEvent(location); |
| break; |
| case ui::ET_GESTURE_SCROLL_UPDATE: |
| listener()->HandleDragEvent(location); |
| break; |
| case ui::ET_SCROLL_FLING_START: |
| listener()->HandleFlingStartEvent(location, |
| event->details().velocity_x(), |
| event->details().velocity_y()); |
| break; |
| case ui::ET_GESTURE_SCROLL_END: |
| listener()->HandleReleaseEvent(location); |
| break; |
| case ui::ET_GESTURE_LONG_PRESS: |
| listener()->HandleLongPressEvent(location); |
| break; |
| case ui::ET_GESTURE_TAP: |
| listener()->ActivateDraggedWindow(); |
| break; |
| case ui::ET_GESTURE_END: |
| listener()->ResetDraggedWindowGesture(); |
| break; |
| default: |
| break; |
| } |
| event->SetHandled(); |
| return; |
| } |
| views::Button::OnGestureEvent(event); |
| } |
| |
| WindowSelectorItem* listener() { |
| return static_cast<WindowSelectorItem*>(listener_); |
| } |
| |
| protected: |
| // views::View: |
| const char* GetClassName() const override { return "ShieldButton"; } |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(ShieldButton); |
| }; |
| |
| } // namespace |
| |
| WindowSelectorItem::OverviewCloseButton::OverviewCloseButton( |
| views::ButtonListener* listener) |
| : views::ImageButton(listener) { |
| SetInkDropMode(InkDropMode::ON_NO_GESTURE_HANDLER); |
| |
| SetImage(views::Button::STATE_NORMAL, |
| gfx::CreateVectorIcon(kOverviewWindowCloseIcon, kCloseButtonColor)); |
| SetImageAlignment(views::ImageButton::ALIGN_CENTER, |
| views::ImageButton::ALIGN_MIDDLE); |
| SetMinimumImageSize(gfx::Size(kHeaderHeightDp, kHeaderHeightDp)); |
| SetAccessibleName(l10n_util::GetStringUTF16(IDS_APP_ACCNAME_CLOSE)); |
| SetTooltipText(l10n_util::GetStringUTF16(IDS_APP_ACCNAME_CLOSE)); |
| } |
| |
| WindowSelectorItem::OverviewCloseButton::~OverviewCloseButton() = default; |
| |
| std::unique_ptr<views::InkDrop> |
| WindowSelectorItem::OverviewCloseButton::CreateInkDrop() { |
| std::unique_ptr<views::InkDropImpl> ink_drop = |
| std::make_unique<views::InkDropImpl>(this, size()); |
| ink_drop->SetAutoHighlightMode( |
| views::InkDropImpl::AutoHighlightMode::SHOW_ON_RIPPLE); |
| ink_drop->SetShowHighlightOnHover(true); |
| return ink_drop; |
| } |
| |
| std::unique_ptr<views::InkDropRipple> |
| WindowSelectorItem::OverviewCloseButton::CreateInkDropRipple() const { |
| return std::make_unique<views::FloodFillInkDropRipple>( |
| size(), gfx::Insets(), GetInkDropCenterBasedOnLastEvent(), |
| kCloseButtonInkDropRippleColor, /*visible_opacity=*/1.f); |
| } |
| |
| std::unique_ptr<views::InkDropHighlight> |
| WindowSelectorItem::OverviewCloseButton::CreateInkDropHighlight() const { |
| return std::make_unique<views::InkDropHighlight>( |
| gfx::PointF(GetLocalBounds().CenterPoint()), |
| std::make_unique<views::CircleLayerDelegate>( |
| kCloseButtonInkDropRippleHighlightColor, GetInkDropRadius())); |
| } |
| |
| std::unique_ptr<views::InkDropMask> |
| WindowSelectorItem::OverviewCloseButton::CreateInkDropMask() const { |
| return std::make_unique<views::CircleInkDropMask>( |
| size(), GetLocalBounds().CenterPoint(), GetInkDropRadius()); |
| } |
| |
| int WindowSelectorItem::OverviewCloseButton::GetInkDropRadius() const { |
| return std::min(size().width(), size().height()) / 2 - |
| kCloseButtonInkDropInsetDp; |
| } |
| |
| // A Container View that has a ShieldButton to listen to events. The |
| // ShieldButton covers most of the View except for the transparent gap between |
| // the windows and is visually transparent. The text label does not receive |
| // events, however the close button is higher in Z-order than its parent and |
| // receives events forwarding them to the same |listener| (i.e. |
| // WindowSelectorItem::ButtonPressed()). |
| class WindowSelectorItem::CaptionContainerView : public views::View { |
| public: |
| enum class HeaderVisibility { |
| kInvisible, |
| kCloseButtonInvisibleOnly, |
| kVisible, |
| }; |
| |
| CaptionContainerView(ButtonListener* listener, |
| views::ImageView* image_view, |
| views::Label* title_label, |
| views::Label* cannot_snap_label, |
| views::ImageButton* close_button) |
| : listener_button_(new ShieldButton(listener, title_label->text())), |
| image_view_(image_view), |
| title_label_(title_label), |
| cannot_snap_label_(cannot_snap_label), |
| close_button_(close_button) { |
| AddChildView(listener_button_); |
| |
| header_view_ = new views::View(); |
| views::BoxLayout* layout = |
| header_view_->SetLayoutManager(std::make_unique<views::BoxLayout>( |
| views::BoxLayout::kHorizontal, gfx::Insets(), |
| kHorizontalLabelPaddingDp)); |
| if (image_view_) |
| header_view_->AddChildView(image_view_); |
| header_view_->AddChildView(title_label_); |
| AddChildWithLayer(header_view_, close_button_); |
| AddChildWithLayer(this, header_view_); |
| layout->SetFlexForView(title_label_, 1); |
| } |
| |
| ~CaptionContainerView() override { |
| // If the cannot snap container was never created, delete cannot_snap_label_ |
| // manually. |
| if (!cannot_snap_container_) |
| delete cannot_snap_label_; |
| } |
| |
| RoundedRectView* GetCannotSnapContainer() { |
| if (!cannot_snap_container_) { |
| // Use |cannot_snap_container_| to specify the padding surrounding |
| // |cannot_snap_label_| and to give the label rounded corners. |
| cannot_snap_container_ = new RoundedRectView( |
| kSplitviewLabelRoundRectRadiusDp, kSplitviewLabelBackgroundColor); |
| cannot_snap_container_->SetLayoutManager( |
| std::make_unique<views::BoxLayout>( |
| views::BoxLayout::kVertical, |
| gfx::Insets(kSplitviewLabelVerticalInsetDp, |
| kSplitviewLabelHorizontalInsetDp))); |
| cannot_snap_container_->AddChildView(cannot_snap_label_); |
| cannot_snap_container_->set_can_process_events_within_subtree(false); |
| AddChildWithLayer(this, cannot_snap_container_); |
| cannot_snap_container_->layer()->SetOpacity(0.f); |
| Layout(); |
| } |
| return cannot_snap_container_; |
| } |
| |
| ShieldButton* listener_button() { return listener_button_; } |
| |
| gfx::Rect backdrop_bounds() const { return backdrop_bounds_; } |
| |
| void SetHeaderVisibility(HeaderVisibility visibility) { |
| DCHECK(close_button_->layer()); |
| DCHECK(header_view_->layer()); |
| |
| // Make the close button invisible if the rest of the header is to be shown. |
| // If the rest of the header is to be hidden, make the close button visible |
| // as |header_view_|'s opacity will be 0.f, hiding the close button. Modify |
| // |close_button_|'s opacity instead of visibilty so the flex from its |
| // sibling views do not mess up its layout. |
| close_button_->layer()->SetOpacity( |
| visibility == HeaderVisibility::kCloseButtonInvisibleOnly ? 0.f : 1.f); |
| const bool visible = visibility != HeaderVisibility::kInvisible; |
| AnimateLayerOpacity(header_view_->layer(), visible); |
| } |
| |
| void SetCannotSnapLabelVisibility(bool visible) { |
| if (!cannot_snap_container_ && !visible) |
| return; |
| |
| DoSplitviewOpacityAnimation( |
| GetCannotSnapContainer()->layer(), |
| visible ? SPLITVIEW_ANIMATION_SELECTOR_ITEM_FADE_IN |
| : SPLITVIEW_ANIMATION_SELECTOR_ITEM_FADE_OUT); |
| } |
| |
| views::View* header_view() { return header_view_; } |
| |
| protected: |
| // views::View: |
| void Layout() override { |
| // |listener_button_| serves as a shield to prevent input events from |
| // reaching the transformed window in overview. |
| gfx::Rect bounds(GetLocalBounds()); |
| bounds.Inset(kWindowSelectorMargin, kWindowSelectorMargin); |
| listener_button_->SetBoundsRect(bounds); |
| |
| // Position the cannot snap label. |
| gfx::Size label_size = cannot_snap_label_->CalculatePreferredSize(); |
| label_size.set_width( |
| std::min(label_size.width() + 2 * kSplitviewLabelHorizontalInsetDp, |
| bounds.width() - 2 * kSplitviewLabelHorizontalInsetDp)); |
| label_size.set_height( |
| std::max(label_size.height(), kSplitviewLabelPreferredHeightDp)); |
| |
| const int visible_height = close_button_->GetPreferredSize().height(); |
| backdrop_bounds_ = bounds; |
| backdrop_bounds_.Inset(0, visible_height, 0, 0); |
| |
| if (cannot_snap_container_) { |
| // Position the cannot snap label in the middle of the item, minus the |
| // title. |
| gfx::Rect cannot_snap_bounds = GetLocalBounds(); |
| cannot_snap_bounds.Inset(0, visible_height, 0, 0); |
| cannot_snap_bounds.ClampToCenteredSize(label_size); |
| cannot_snap_container_->SetBoundsRect(cannot_snap_bounds); |
| } |
| |
| // Position the header at the top. The left should be indented to match |
| // the transformed window, but not the right because the close button hit |
| // radius should extend past the transformed window's rightmost bounds. |
| gfx::Rect header_bounds = GetLocalBounds(); |
| header_bounds.Inset(kWindowSelectorMargin, kWindowSelectorMargin, 0, 0); |
| header_bounds.set_height(visible_height); |
| header_view_->SetBoundsRect(header_bounds); |
| } |
| |
| const char* GetClassName() const override { return "CaptionContainerView"; } |
| |
| private: |
| // Animates |layer| from 0 -> 1 opacity if |visible| and 1 -> 0 opacity |
| // otherwise. The tween type differs for |visible| and if |visible| is true |
| // there is a slight delay before the animation begins. Does not animate if |
| // opacity matches |visible|. |
| void AnimateLayerOpacity(ui::Layer* layer, bool visible) { |
| float target_opacity = visible ? 1.f : 0.f; |
| if (layer->GetTargetOpacity() == target_opacity) |
| return; |
| |
| layer->SetOpacity(1.f - target_opacity); |
| { |
| ui::LayerAnimator* animator = layer->GetAnimator(); |
| ui::ScopedLayerAnimationSettings settings(animator); |
| settings.SetPreemptionStrategy( |
| ui::LayerAnimator::REPLACE_QUEUED_ANIMATIONS); |
| if (visible) { |
| animator->SchedulePauseForProperties( |
| base::TimeDelta::FromMilliseconds(kDragAnimationMs), |
| ui::LayerAnimationElement::OPACITY); |
| } |
| settings.SetTransitionDuration( |
| base::TimeDelta::FromMilliseconds(kDragAnimationMs)); |
| settings.SetTweenType(visible ? gfx::Tween::LINEAR_OUT_SLOW_IN |
| : gfx::Tween::FAST_OUT_LINEAR_IN); |
| layer->SetOpacity(target_opacity); |
| } |
| } |
| |
| // Helper function to add a child view to a parent view and make it paint to |
| // layer. |
| static void AddChildWithLayer(views::View* parent, views::View* child) { |
| child->SetPaintToLayer(); |
| child->layer()->SetFillsBoundsOpaquely(false); |
| parent->AddChildView(child); |
| }; |
| |
| ShieldButton* listener_button_; |
| views::ImageView* image_view_; |
| views::Label* title_label_; |
| views::Label* cannot_snap_label_; |
| RoundedRectView* cannot_snap_container_ = nullptr; |
| views::ImageButton* close_button_; |
| // View which contains the icon, title and close button. |
| views::View* header_view_ = nullptr; |
| gfx::Rect backdrop_bounds_; |
| |
| DISALLOW_COPY_AND_ASSIGN(CaptionContainerView); |
| }; |
| |
| WindowSelectorItem::WindowSelectorItem(aura::Window* window, |
| WindowSelector* window_selector, |
| WindowGrid* window_grid) |
| : root_window_(window->GetRootWindow()), |
| transform_window_(this, window), |
| close_button_(new OverviewCloseButton(this)), |
| window_selector_(window_selector), |
| window_grid_(window_grid) { |
| CreateWindowLabel(window->GetTitle()); |
| GetWindow()->AddObserver(this); |
| if (GetWindowDimensionsType() != |
| ScopedTransformOverviewWindow::GridWindowFillMode::kNormal) { |
| backdrop_widget_ = CreateBackdropWidget(window->parent()); |
| } |
| GetWindow()->SetProperty(ash::kIsShowingInOverviewKey, true); |
| } |
| |
| WindowSelectorItem::~WindowSelectorItem() { |
| GetWindow()->RemoveObserver(this); |
| GetWindow()->ClearProperty(ash::kIsShowingInOverviewKey); |
| } |
| |
| aura::Window* WindowSelectorItem::GetWindow() { |
| return transform_window_.window(); |
| } |
| |
| aura::Window* WindowSelectorItem::GetWindowForStacking() { |
| // If the window is minimized, stack |widget_window| above the minimized |
| // window, otherwise the minimized window will cover |widget_window|. The |
| // minimized is created with the same parent as the original window, just |
| // like |item_widget_|, so there is no need to reparent. |
| return transform_window_.minimized_widget() |
| ? transform_window_.minimized_widget()->GetNativeWindow() |
| : GetWindow(); |
| } |
| |
| bool WindowSelectorItem::Contains(const aura::Window* target) const { |
| return transform_window_.Contains(target); |
| } |
| |
| void WindowSelectorItem::RestoreWindow(bool reset_transform) { |
| caption_container_view_->listener_button()->ResetListener(); |
| close_button_->ResetListener(); |
| transform_window_.RestoreWindow( |
| reset_transform, |
| window_selector_->enter_exit_overview_type() == |
| WindowSelector::EnterExitOverviewType::kWindowsMinimized); |
| } |
| |
| void WindowSelectorItem::EnsureVisible() { |
| transform_window_.EnsureVisible(); |
| } |
| |
| void WindowSelectorItem::Shutdown() { |
| if (transform_window_.GetTopInset()) { |
| // Activating a window (even when it is the window that was active before |
| // overview) results in stacking it at the top. Maintain the label window |
| // stacking position above the item to make the header transformation more |
| // gradual upon exiting the overview mode. |
| aura::Window* widget_window = item_widget_->GetNativeWindow(); |
| |
| // |widget_window| was originally created in the same container as the |
| // |transform_window_| but when closing overview the |transform_window_| |
| // could have been reparented if a drag was active. Only change stacking |
| // if the windows still belong to the same container. |
| if (widget_window->parent() == transform_window_.window()->parent()) { |
| widget_window->parent()->StackChildAbove(widget_window, |
| transform_window_.window()); |
| } |
| } |
| |
| // On swiping from the shelf, the caller handles the animation via calls to |
| // UpdateYAndOpacity, so do not additional fade out or slide animation to the |
| // window. |
| if (window_selector_->enter_exit_overview_type() == |
| WindowSelector::EnterExitOverviewType::kSwipeFromShelf) { |
| return; |
| } |
| |
| // Fade out the item widget. This animation continues past the lifetime |
| // of |this|. |
| const bool slide = window_selector_->enter_exit_overview_type() == |
| WindowSelector::EnterExitOverviewType::kWindowsMinimized; |
| FadeOutWidgetAndMaybeSlideOnExit( |
| std::move(item_widget_), |
| slide ? OVERVIEW_ANIMATION_EXIT_TO_HOME_LAUNCHER |
| : OVERVIEW_ANIMATION_EXIT_OVERVIEW_MODE_FADE_OUT, |
| slide); |
| } |
| |
| void WindowSelectorItem::PrepareForOverview() { |
| transform_window_.PrepareForOverview(); |
| RestackItemWidget(); |
| UpdateHeaderLayout(HeaderFadeInMode::kEnter, OVERVIEW_ANIMATION_NONE); |
| } |
| |
| void WindowSelectorItem::SlideWindowIn() { |
| // |transform_window_|'s |minimized_widget| is non null because this only gets |
| // called if we see the home launcher on enter (all windows are minimized). |
| DCHECK(item_widget_); |
| DCHECK(transform_window_.minimized_widget()); |
| |
| FadeInWidgetAndMaybeSlideOnEnter(item_widget_.get(), |
| OVERVIEW_ANIMATION_ENTER_FROM_HOME_LAUNCHER, |
| /*slide=*/true); |
| FadeInWidgetAndMaybeSlideOnEnter(transform_window_.minimized_widget(), |
| OVERVIEW_ANIMATION_ENTER_FROM_HOME_LAUNCHER, |
| /*slide=*/true); |
| } |
| |
| void WindowSelectorItem::UpdateYPositionAndOpacity( |
| int new_grid_y, |
| float opacity, |
| WindowSelector::UpdateAnimationSettingsCallback callback) { |
| // Animate the window selector widget and the window itself. |
| // TODO(sammiequon): Investigate if we can combine with |
| // FadeInWidgetAndMaybeSlideOnEnter and animate the transient children too. |
| // Also when animating we should remove shadow and rounded corners. |
| std::vector<ui::Layer*> animation_layers = { |
| GetWindowForStacking()->layer(), |
| item_widget_->GetNativeWindow()->layer()}; |
| for (auto* layer : animation_layers) { |
| layer->GetAnimator()->StopAnimating(); |
| std::unique_ptr<ui::ScopedLayerAnimationSettings> settings; |
| if (!callback.is_null()) { |
| settings = std::make_unique<ui::ScopedLayerAnimationSettings>( |
| layer->GetAnimator()); |
| callback.Run(settings.get(), /*observe=*/false); |
| } |
| layer->SetOpacity(opacity); |
| |
| // Alter the y-translation. Offset by the window location relative to the |
| // grid. |
| const int offset = target_bounds_.y() + kHeaderHeightDp + kWindowMargin; |
| gfx::Transform transform = layer->transform(); |
| transform.matrix().setFloat(1, 3, static_cast<float>(offset + new_grid_y)); |
| layer->SetTransform(transform); |
| } |
| } |
| |
| float WindowSelectorItem::GetItemScale(const gfx::Size& size) { |
| gfx::Size inset_size(size.width(), size.height() - 2 * kWindowMargin); |
| return ScopedTransformOverviewWindow::GetItemScale( |
| GetTargetBoundsInScreen().size(), inset_size, |
| transform_window_.GetTopInset(), |
| close_button_->GetPreferredSize().height()); |
| } |
| |
| gfx::Rect WindowSelectorItem::GetTargetBoundsInScreen() const { |
| return ::ash::GetTargetBoundsInScreen(transform_window_.GetOverviewWindow()); |
| } |
| |
| gfx::Rect WindowSelectorItem::GetTransformedBounds() const { |
| return transform_window_.GetTransformedBounds(); |
| } |
| |
| void WindowSelectorItem::SetBounds(const gfx::Rect& target_bounds, |
| OverviewAnimationType animation_type) { |
| if (in_bounds_update_) |
| return; |
| |
| // Do not animate if the resulting bounds does not change. The original |
| // window may change bounds so we still need to call SetItemBounds to update |
| // the window transform. |
| OverviewAnimationType new_animation_type = animation_type; |
| if (target_bounds == target_bounds_) |
| new_animation_type = OVERVIEW_ANIMATION_NONE; |
| |
| base::AutoReset<bool> auto_reset_in_bounds_update(&in_bounds_update_, true); |
| // If |target_bounds_| is empty, this is the first update. Let |
| // UpdateHeaderLayout know, as we do not want |item_widget_| to be animated |
| // with the window. |
| HeaderFadeInMode mode = target_bounds_.IsEmpty() |
| ? HeaderFadeInMode::kFirstUpdate |
| : HeaderFadeInMode::kUpdate; |
| target_bounds_ = target_bounds; |
| |
| gfx::Rect inset_bounds(target_bounds); |
| inset_bounds.Inset(kWindowMargin, kWindowMargin); |
| |
| // Do not animate if entering when the window is minimized, as it will be |
| // faded in. We still want to animate if the position is changed after |
| // entering. |
| if (wm::GetWindowState(GetWindow())->IsMinimized() && |
| mode == HeaderFadeInMode::kFirstUpdate) { |
| new_animation_type = OVERVIEW_ANIMATION_NONE; |
| } |
| |
| SetItemBounds(inset_bounds, new_animation_type); |
| |
| // SetItemBounds is called before UpdateHeaderLayout so the header can |
| // properly use the updated windows bounds. |
| UpdateHeaderLayout(mode, new_animation_type); |
| |
| // Shadow is normally set after an animation is finished. In the case of no |
| // animations, manually set the shadow. Shadow relies on both the window |
| // transform and |item_widget_|'s new bounds so set it after SetItemBounds |
| // and UpdateHeaderLayout. Do not apply the shadow for drop target. |
| if (new_animation_type == OVERVIEW_ANIMATION_NONE) { |
| SetShadowBounds( |
| window_grid_->IsDropTargetWindow(GetWindow()) |
| ? base::nullopt |
| : base::make_optional(transform_window_.GetTransformedBounds())); |
| } |
| |
| UpdateBackdropBounds(); |
| } |
| |
| void WindowSelectorItem::SendAccessibleSelectionEvent() { |
| caption_container_view_->listener_button()->NotifyAccessibilityEvent( |
| ax::mojom::Event::kSelection, true); |
| } |
| |
| void WindowSelectorItem::AnimateAndCloseWindow(bool up) { |
| base::RecordAction(base::UserMetricsAction("WindowSelector_SwipeToClose")); |
| |
| animating_to_close_ = true; |
| window_selector_->PositionWindows(/*animate=*/true); |
| caption_container_view_->listener_button()->ResetListener(); |
| close_button_->ResetListener(); |
| |
| int translation_y = kSwipeToCloseCloseTranslationDp * (up ? -1 : 1); |
| gfx::Transform transform; |
| transform.Translate(gfx::Vector2d(0, translation_y)); |
| |
| auto animate_window = [this](aura::Window* window, |
| const gfx::Transform& transform, bool observe) { |
| ScopedOverviewAnimationSettings settings( |
| OVERVIEW_ANIMATION_CLOSE_SELECTOR_ITEM, window); |
| gfx::Transform original_transform = window->transform(); |
| original_transform.ConcatTransform(transform); |
| window->SetTransform(original_transform); |
| if (observe) |
| settings.AddObserver(this); |
| }; |
| |
| AnimateOpacity(0.0, OVERVIEW_ANIMATION_CLOSE_SELECTOR_ITEM); |
| animate_window(item_widget_->GetNativeWindow(), transform, false); |
| animate_window(GetWindowForStacking(), transform, true); |
| } |
| |
| void WindowSelectorItem::CloseWindow() { |
| gfx::Rect inset_bounds(target_bounds_); |
| inset_bounds.Inset(target_bounds_.width() * kPreCloseScale, |
| target_bounds_.height() * kPreCloseScale); |
| // Scale down both the window and label. |
| SetBounds(inset_bounds, OVERVIEW_ANIMATION_CLOSING_SELECTOR_ITEM); |
| // First animate opacity to an intermediate value concurrently with the |
| // scaling animation. |
| AnimateOpacity(kClosingItemOpacity, OVERVIEW_ANIMATION_CLOSING_SELECTOR_ITEM); |
| |
| // Fade out the window and the label, effectively hiding them. |
| AnimateOpacity(0.0, OVERVIEW_ANIMATION_CLOSE_SELECTOR_ITEM); |
| transform_window_.Close(); |
| } |
| |
| void WindowSelectorItem::OnMinimizedStateChanged() { |
| transform_window_.UpdateMirrorWindowForMinimizedState(); |
| } |
| |
| void WindowSelectorItem::UpdateCannotSnapWarningVisibility() { |
| // Windows which can snap will never show this warning. Or if the window is |
| // the drop target window, also do not show this warning. |
| if (Shell::Get()->split_view_controller()->CanSnap(GetWindow()) || |
| window_grid_->IsDropTargetWindow(GetWindow())) { |
| caption_container_view_->SetCannotSnapLabelVisibility(false); |
| return; |
| } |
| |
| const SplitViewController::State state = |
| Shell::Get()->split_view_controller()->state(); |
| const bool visible = state == SplitViewController::LEFT_SNAPPED || |
| state == SplitViewController::RIGHT_SNAPPED; |
| caption_container_view_->SetCannotSnapLabelVisibility(visible); |
| } |
| |
| void WindowSelectorItem::OnSelectorItemDragStarted(WindowSelectorItem* item) { |
| caption_container_view_->SetHeaderVisibility( |
| item == this |
| ? CaptionContainerView::HeaderVisibility::kInvisible |
| : CaptionContainerView::HeaderVisibility::kCloseButtonInvisibleOnly); |
| } |
| |
| void WindowSelectorItem::OnSelectorItemDragEnded() { |
| caption_container_view_->SetHeaderVisibility( |
| CaptionContainerView::HeaderVisibility::kVisible); |
| } |
| |
| ScopedTransformOverviewWindow::GridWindowFillMode |
| WindowSelectorItem::GetWindowDimensionsType() const { |
| return transform_window_.type(); |
| } |
| |
| void WindowSelectorItem::UpdateWindowDimensionsType() { |
| transform_window_.UpdateWindowDimensionsType(); |
| if (GetWindowDimensionsType() == |
| ScopedTransformOverviewWindow::GridWindowFillMode::kNormal) { |
| // Delete the backdrop widget, if it exists for normal windows. |
| if (backdrop_widget_) |
| backdrop_widget_.reset(); |
| } else { |
| // Create the backdrop widget if needed. |
| if (!backdrop_widget_) { |
| backdrop_widget_ = |
| CreateBackdropWidget(transform_window_.window()->parent()); |
| } |
| } |
| } |
| |
| void WindowSelectorItem::EnableBackdropIfNeeded() { |
| if (GetWindowDimensionsType() == |
| ScopedTransformOverviewWindow::GridWindowFillMode::kNormal) { |
| DisableBackdrop(); |
| return; |
| } |
| |
| UpdateBackdropBounds(); |
| } |
| |
| void WindowSelectorItem::DisableBackdrop() { |
| if (backdrop_widget_) |
| backdrop_widget_->Hide(); |
| } |
| |
| void WindowSelectorItem::UpdateBackdropBounds() { |
| if (!backdrop_widget_) |
| return; |
| |
| gfx::Rect backdrop_bounds = caption_container_view_->backdrop_bounds(); |
| ::wm::ConvertRectToScreen(item_widget_->GetNativeWindow(), &backdrop_bounds); |
| backdrop_widget_->SetBounds(backdrop_bounds); |
| backdrop_widget_->Show(); |
| } |
| |
| gfx::Rect WindowSelectorItem::GetBoundsOfSelectedItem() { |
| gfx::Rect original_bounds = target_bounds(); |
| ScaleUpSelectedItem(OVERVIEW_ANIMATION_NONE); |
| gfx::Rect selected_bounds = transform_window_.GetTransformedBounds(); |
| SetBounds(original_bounds, OVERVIEW_ANIMATION_NONE); |
| return selected_bounds; |
| } |
| |
| void WindowSelectorItem::ScaleUpSelectedItem( |
| OverviewAnimationType animation_type) { |
| gfx::Rect scaled_bounds(target_bounds()); |
| scaled_bounds.Inset(-scaled_bounds.width() * kDragWindowScale, |
| -scaled_bounds.height() * kDragWindowScale); |
| SetBounds(scaled_bounds, animation_type); |
| } |
| |
| void WindowSelectorItem::SetDimmed(bool dimmed) { |
| dimmed_ = dimmed; |
| SetOpacity(dimmed ? kDimmedItemOpacity : 1.0f); |
| } |
| |
| void WindowSelectorItem::RestackItemWidget() { |
| aura::Window* widget_window = item_widget_->GetNativeWindow(); |
| widget_window->parent()->StackChildAbove(widget_window, |
| GetWindowForStacking()); |
| } |
| |
| void WindowSelectorItem::ButtonPressed(views::Button* sender, |
| const ui::Event& event) { |
| if (IsSlidingOutOverviewFromShelf()) |
| return; |
| |
| if (sender == close_button_) { |
| base::RecordAction( |
| base::UserMetricsAction("WindowSelector_OverviewCloseButton")); |
| if (Shell::Get() |
| ->tablet_mode_controller() |
| ->IsTabletModeWindowManagerEnabled()) { |
| base::RecordAction( |
| base::UserMetricsAction("Tablet_WindowCloseFromOverviewButton")); |
| } |
| CloseWindow(); |
| return; |
| } |
| |
| CHECK(sender == caption_container_view_->listener_button()); |
| |
| // For other cases, the event is handled in OverviewWindowDragController. |
| if (!SplitViewController::ShouldAllowSplitView()) |
| window_selector_->SelectWindow(this); |
| } |
| |
| void WindowSelectorItem::OnWindowBoundsChanged( |
| aura::Window* window, |
| const gfx::Rect& old_bounds, |
| const gfx::Rect& new_bounds, |
| ui::PropertyChangeReason reason) { |
| if (reason == ui::PropertyChangeReason::NOT_FROM_ANIMATION) |
| transform_window_.ResizeMinimizedWidgetIfNeeded(); |
| } |
| |
| void WindowSelectorItem::OnWindowDestroying(aura::Window* window) { |
| window->RemoveObserver(this); |
| transform_window_.OnWindowDestroyed(); |
| } |
| |
| void WindowSelectorItem::OnWindowTitleChanged(aura::Window* window) { |
| // TODO(flackr): Maybe add the new title to a vector of titles so that we can |
| // filter any of the titles the window had while in the overview session. |
| label_view_->SetText(window->GetTitle()); |
| UpdateAccessibilityName(); |
| } |
| |
| void WindowSelectorItem::OnImplicitAnimationsCompleted() { |
| transform_window_.Close(); |
| } |
| |
| void WindowSelectorItem::HandlePressEvent( |
| const gfx::Point& location_in_screen) { |
| // We allow switching finger while dragging, but do not allow dragging two or more items. |
| if (window_selector_->window_drag_controller() && |
| window_selector_->window_drag_controller()->item()) { |
| return; |
| } |
| |
| StartDrag(); |
| window_selector_->InitiateDrag(this, location_in_screen); |
| } |
| |
| void WindowSelectorItem::HandleReleaseEvent( |
| const gfx::Point& location_in_screen) { |
| if (!IsDragItem()) |
| return; |
| window_grid_->SetSelectionWidgetVisibility(true); |
| window_selector_->CompleteDrag(this, location_in_screen); |
| } |
| |
| void WindowSelectorItem::HandleDragEvent(const gfx::Point& location_in_screen) { |
| if (!IsDragItem()) |
| return; |
| |
| window_selector_->Drag(this, location_in_screen); |
| } |
| |
| void WindowSelectorItem::HandleLongPressEvent( |
| const gfx::Point& location_in_screen) { |
| if (!SplitViewController::ShouldAllowSplitView()) |
| return; |
| |
| window_selector_->StartSplitViewDragMode(location_in_screen); |
| } |
| |
| void WindowSelectorItem::HandleFlingStartEvent( |
| const gfx::Point& location_in_screen, |
| float velocity_x, |
| float velocity_y) { |
| window_selector_->Fling(this, location_in_screen, velocity_x, velocity_y); |
| } |
| |
| void WindowSelectorItem::ActivateDraggedWindow() { |
| if (!IsDragItem()) |
| return; |
| |
| window_selector_->ActivateDraggedWindow(); |
| } |
| |
| void WindowSelectorItem::ResetDraggedWindowGesture() { |
| if (!IsDragItem()) |
| return; |
| |
| OnSelectorItemDragEnded(); |
| window_selector_->ResetDraggedWindowGesture(); |
| } |
| |
| bool WindowSelectorItem::IsDragItem() { |
| return window_selector_->window_drag_controller() && |
| window_selector_->window_drag_controller()->item() == this; |
| } |
| |
| void WindowSelectorItem::OnDragAnimationCompleted() { |
| // This is function is called whenever the grid repositions its windows, but |
| // we only need to restack the windows if an item was being dragged around |
| // and then released. |
| if (!should_restack_on_animation_end_) |
| return; |
| |
| should_restack_on_animation_end_ = false; |
| |
| // First stack this item's window below the snapped window if split view mode |
| // is active. |
| aura::Window* dragged_window = GetWindowForStacking(); |
| aura::Window* dragged_widget_window = item_widget_->GetNativeWindow(); |
| aura::Window* parent_window = dragged_widget_window->parent(); |
| if (Shell::Get()->IsSplitViewModeActive()) { |
| aura::Window* snapped_window = |
| Shell::Get()->split_view_controller()->GetDefaultSnappedWindow(); |
| if (snapped_window->parent() == parent_window && |
| dragged_window->parent() == parent_window) { |
| parent_window->StackChildBelow(dragged_widget_window, snapped_window); |
| parent_window->StackChildBelow(dragged_window, dragged_widget_window); |
| } |
| } |
| |
| // Then find the window which was stacked right above this selector item's |
| // window before dragging and stack this selector item's window below it. |
| const std::vector<std::unique_ptr<WindowSelectorItem>>& selector_items = |
| window_grid_->window_list(); |
| aura::Window* stacking_target = nullptr; |
| for (size_t index = 0; index < selector_items.size(); index++) { |
| if (index > 0) { |
| aura::Window* window = selector_items[index - 1].get()->GetWindow(); |
| if (window->parent() == parent_window && |
| dragged_window->parent() == parent_window) { |
| stacking_target = window; |
| } |
| } |
| if (selector_items[index].get() == this && stacking_target) { |
| parent_window->StackChildBelow(dragged_widget_window, stacking_target); |
| parent_window->StackChildBelow(dragged_window, dragged_widget_window); |
| break; |
| } |
| } |
| } |
| |
| void WindowSelectorItem::SetShadowBounds( |
| base::Optional<gfx::Rect> bounds_in_screen) { |
| // Shadow is normally turned off during animations and reapplied when they |
| // are finished. On destruction, |shadow_| is cleaned up before |
| // |transform_window_|, which may call this function, so early exit if |
| // |shadow_| is nullptr. |
| if (!shadow_) |
| return; |
| |
| if (bounds_in_screen == base::nullopt) { |
| shadow_->layer()->SetVisible(false); |
| return; |
| } |
| |
| shadow_->layer()->SetVisible(true); |
| gfx::Rect bounds_in_item = |
| gfx::Rect(item_widget_->GetNativeWindow()->GetTargetBounds().size()); |
| bounds_in_item.Inset(kWindowSelectorMargin, kWindowSelectorMargin); |
| bounds_in_item.Inset(0, close_button_->GetPreferredSize().height(), 0, 0); |
| bounds_in_item.ClampToCenteredSize(bounds_in_screen.value().size()); |
| |
| shadow_->SetContentBounds(bounds_in_item); |
| } |
| |
| void WindowSelectorItem::SetOpacity(float opacity) { |
| item_widget_->SetOpacity(opacity); |
| transform_window_.SetOpacity(opacity); |
| } |
| |
| float WindowSelectorItem::GetOpacity() { |
| return item_widget_->GetNativeWindow()->layer()->opacity(); |
| } |
| |
| OverviewAnimationType WindowSelectorItem::GetExitOverviewAnimationType() { |
| return should_animate_when_exiting_ |
| ? OVERVIEW_ANIMATION_LAY_OUT_SELECTOR_ITEMS_ON_EXIT |
| : OVERVIEW_ANIMATION_NONE; |
| } |
| |
| OverviewAnimationType WindowSelectorItem::GetExitTransformAnimationType() { |
| return should_animate_when_exiting_ ? OVERVIEW_ANIMATION_RESTORE_WINDOW |
| : OVERVIEW_ANIMATION_RESTORE_WINDOW_ZERO; |
| } |
| |
| float WindowSelectorItem::GetCloseButtonVisibilityForTesting() const { |
| return close_button_->layer()->opacity(); |
| } |
| |
| float WindowSelectorItem::GetTitlebarOpacityForTesting() const { |
| return caption_container_view_->header_view()->layer()->opacity(); |
| } |
| |
| gfx::Rect WindowSelectorItem::GetShadowBoundsForTesting() { |
| if (!shadow_ || !shadow_->layer()->visible()) |
| return gfx::Rect(); |
| |
| return shadow_->content_bounds(); |
| } |
| |
| void WindowSelectorItem::SetItemBounds(const gfx::Rect& target_bounds, |
| OverviewAnimationType animation_type) { |
| aura::Window* window = GetWindow(); |
| DCHECK(root_window_ == window->GetRootWindow()); |
| gfx::Rect screen_rect = GetTargetBoundsInScreen(); |
| |
| // Avoid division by zero by ensuring screen bounds is not empty. |
| gfx::Size screen_size(screen_rect.size()); |
| screen_size.SetToMax(gfx::Size(1, 1)); |
| screen_rect.set_size(screen_size); |
| |
| const int top_view_inset = transform_window_.GetTopInset(); |
| const int title_height = close_button_->GetPreferredSize().height(); |
| gfx::Rect selector_item_bounds = |
| transform_window_.ShrinkRectToFitPreservingAspectRatio( |
| screen_rect, target_bounds, top_view_inset, title_height); |
| // Do not set transform for drop target, set bounds instead. |
| if (window_grid_->IsDropTargetWindow(window)) { |
| window->layer()->SetBounds(selector_item_bounds); |
| transform_window_.GetOverviewWindow()->SetTransform(gfx::Transform()); |
| return; |
| } |
| |
| gfx::Transform transform = ScopedTransformOverviewWindow::GetTransformForRect( |
| screen_rect, selector_item_bounds); |
| ScopedTransformOverviewWindow::ScopedAnimationSettings animation_settings; |
| transform_window_.BeginScopedAnimation(animation_type, &animation_settings); |
| SetTransform(transform_window_.GetOverviewWindow(), transform); |
| } |
| |
| void WindowSelectorItem::CreateWindowLabel(const base::string16& title) { |
| views::Widget::InitParams params_label; |
| params_label.type = views::Widget::InitParams::TYPE_POPUP; |
| params_label.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; |
| params_label.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW; |
| params_label.visible_on_all_workspaces = true; |
| params_label.layer_type = ui::LAYER_NOT_DRAWN; |
| params_label.name = "OverviewModeLabel"; |
| params_label.activatable = |
| views::Widget::InitParams::Activatable::ACTIVATABLE_DEFAULT; |
| params_label.accept_events = true; |
| item_widget_ = std::make_unique<views::Widget>(); |
| params_label.parent = transform_window_.window()->parent(); |
| item_widget_->set_focus_on_creation(false); |
| item_widget_->Init(params_label); |
| aura::Window* widget_window = item_widget_->GetNativeWindow(); |
| // Stack the widget above the transform window so that it can block events. |
| widget_window->parent()->StackChildAbove(widget_window, |
| transform_window_.window()); |
| |
| // Create an image view the header icon. Tries to use the app icon, as it is |
| // higher resolution. If it does not exist, use the window icon. If neither |
| // exist, display nothing. |
| views::ImageView* image_view = nullptr; |
| gfx::ImageSkia* icon = GetWindow()->GetProperty(aura::client::kAppIconKey); |
| if (!icon || icon->size().IsEmpty()) |
| icon = GetWindow()->GetProperty(aura::client::kWindowIconKey); |
| if (icon && !icon->size().IsEmpty()) { |
| image_view = new views::ImageView(); |
| image_view->SetImage(gfx::ImageSkiaOperations::CreateResizedImage( |
| *icon, skia::ImageOperations::RESIZE_BEST, kIconSize)); |
| image_view->SetSize(kIconSize); |
| } |
| |
| label_view_ = new views::Label(title); |
| label_view_->SetHorizontalAlignment(gfx::ALIGN_LEFT); |
| label_view_->SetAutoColorReadabilityEnabled(false); |
| label_view_->SetEnabledColor(kLabelColor); |
| label_view_->SetSubpixelRenderingEnabled(false); |
| label_view_->SetFontList(gfx::FontList().Derive( |
| kLabelFontDelta, gfx::Font::NORMAL, gfx::Font::Weight::MEDIUM)); |
| |
| shadow_ = std::make_unique<ui::Shadow>(); |
| shadow_->Init(kShadowElevation); |
| item_widget_->GetLayer()->Add(shadow_->layer()); |
| |
| cannot_snap_label_view_ = new views::Label( |
| l10n_util::GetStringUTF16(IDS_ASH_SPLIT_VIEW_CANNOT_SNAP)); |
| cannot_snap_label_view_->SetHorizontalAlignment(gfx::ALIGN_CENTER); |
| cannot_snap_label_view_->SetAutoColorReadabilityEnabled(false); |
| cannot_snap_label_view_->SetEnabledColor(kSplitviewLabelEnabledColor); |
| cannot_snap_label_view_->SetBackgroundColor(kSplitviewLabelBackgroundColor); |
| |
| caption_container_view_ = new CaptionContainerView( |
| this, image_view, label_view_, cannot_snap_label_view_, close_button_); |
| UpdateCannotSnapWarningVisibility(); |
| |
| item_widget_->SetContentsView(caption_container_view_); |
| item_widget_->Show(); |
| item_widget_->SetOpacity(0); |
| item_widget_->GetLayer()->SetMasksToBounds(false); |
| FadeInWidgetAndMaybeSlideOnEnter( |
| item_widget_.get(), OVERVIEW_ANIMATION_ENTER_OVERVIEW_MODE_FADE_IN, |
| /*slide=*/false); |
| } |
| |
| void WindowSelectorItem::UpdateHeaderLayout( |
| HeaderFadeInMode mode, |
| OverviewAnimationType animation_type) { |
| gfx::Rect transformed_window_bounds = |
| transform_window_.window_selector_bounds().value_or( |
| transform_window_.GetTransformedBounds()); |
| ::wm::ConvertRectFromScreen(root_window_, &transformed_window_bounds); |
| |
| gfx::Rect label_rect(close_button_->GetPreferredSize()); |
| label_rect.set_width(transformed_window_bounds.width()); |
| // For tabbed windows the initial bounds of the caption are set such that it |
| // appears to be "growing" up from the window content area. |
| label_rect.set_y( |
| (mode != HeaderFadeInMode::kEnter || transform_window_.GetTopInset()) |
| ? -label_rect.height() |
| : 0); |
| |
| aura::Window* widget_window = item_widget_->GetNativeWindow(); |
| // For the first update, place the widget at its destination. |
| ScopedOverviewAnimationSettings animation_settings( |
| mode == HeaderFadeInMode::kFirstUpdate ? OVERVIEW_ANIMATION_NONE |
| : animation_type, |
| widget_window); |
| // |widget_window| covers both the transformed window and the header |
| // as well as the gap between the windows to prevent events from reaching |
| // the window including its sizing borders. |
| if (mode != HeaderFadeInMode::kEnter) { |
| label_rect.set_height(close_button_->GetPreferredSize().height() + |
| transformed_window_bounds.height()); |
| } |
| label_rect.Inset(-kWindowSelectorMargin, -kWindowSelectorMargin); |
| widget_window->SetBounds(label_rect); |
| gfx::Transform label_transform; |
| label_transform.Translate(transformed_window_bounds.x(), |
| transformed_window_bounds.y()); |
| widget_window->SetTransform(label_transform); |
| } |
| |
| void WindowSelectorItem::AnimateOpacity(float opacity, |
| OverviewAnimationType animation_type) { |
| DCHECK_GE(opacity, 0.f); |
| DCHECK_LE(opacity, 1.f); |
| ScopedTransformOverviewWindow::ScopedAnimationSettings animation_settings; |
| transform_window_.BeginScopedAnimation(animation_type, &animation_settings); |
| transform_window_.SetOpacity(opacity); |
| |
| const float header_opacity = selected_ ? 0.f : kHeaderOpacity * opacity; |
| aura::Window* widget_window = item_widget_->GetNativeWindow(); |
| ScopedOverviewAnimationSettings animation_settings_label(animation_type, |
| widget_window); |
| widget_window->layer()->SetOpacity(header_opacity); |
| } |
| |
| void WindowSelectorItem::UpdateAccessibilityName() { |
| caption_container_view_->listener_button()->SetAccessibleName( |
| GetWindow()->GetTitle()); |
| } |
| |
| aura::Window* WindowSelectorItem::GetOverviewWindowForMinimizedStateForTest() { |
| return transform_window_.GetOverviewWindowForMinimizedState(); |
| } |
| |
| void WindowSelectorItem::StartDrag() { |
| window_grid_->SetSelectionWidgetVisibility(false); |
| |
| // |transform_window_| handles hiding shadow and rounded edges mask while |
| // animating, and applies them after animation is complete. Prevent the |
| // shadow and rounded edges mask from showing up after dragging in the case |
| // the window is pressed while still animating. |
| transform_window_.CancelAnimationsListener(); |
| |
| aura::Window* widget_window = item_widget_->GetNativeWindow(); |
| aura::Window* window = GetWindowForStacking(); |
| if (widget_window && widget_window->parent() == window->parent()) { |
| // TODO(xdai): This might not work if there is an always on top window. |
| // See crbug.com/733760. |
| widget_window->parent()->StackChildAtTop(widget_window); |
| widget_window->parent()->StackChildBelow(window, widget_window); |
| } |
| } |
| |
| } // namespace ash |