| // Copyright 2019 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/desks/desk_mini_view.h" |
| |
| #include <algorithm> |
| |
| #include "ash/wm/desks/close_desk_button.h" |
| #include "ash/wm/desks/desk.h" |
| #include "ash/wm/desks/desk_preview_view.h" |
| #include "ash/wm/desks/desks_bar_view.h" |
| #include "ash/wm/desks/desks_controller.h" |
| #include "ui/aura/window.h" |
| #include "ui/views/widget/widget.h" |
| |
| namespace ash { |
| |
| namespace { |
| |
| constexpr int kLabelPreviewSpacing = 8; |
| |
| constexpr int kCloseButtonMargin = 4; |
| |
| constexpr gfx::Size kCloseButtonSize{24, 24}; |
| |
| constexpr SkColor kActiveColor = SkColorSetARGB(0xEE, 0xFF, 0xFF, 0xFF); |
| |
| constexpr SkColor kInactiveColor = SkColorSetARGB(0x50, 0xFF, 0xFF, 0xFF); |
| |
| constexpr SkColor kDraggedOverColor = SkColorSetARGB(0xFF, 0x5B, 0xBC, 0xFF); |
| |
| std::unique_ptr<DeskPreviewView> CreateDeskPreviewView( |
| DeskMiniView* mini_view) { |
| auto desk_preview_view = std::make_unique<DeskPreviewView>(mini_view); |
| desk_preview_view->set_owned_by_client(); |
| return desk_preview_view; |
| } |
| |
| // The desk preview bounds are proportional to the bounds of the display on |
| // which it resides, but always has a fixed height `kDeskPreviewHeight`. |
| gfx::Rect GetDeskPreviewBounds(aura::Window* root_window) { |
| const auto root_size = root_window->GetBoundsInRootWindow().size(); |
| const int preview_height = DeskPreviewView::GetHeight(); |
| return gfx::Rect(preview_height * root_size.width() / root_size.height(), |
| preview_height); |
| } |
| |
| } // namespace |
| |
| // ----------------------------------------------------------------------------- |
| // DeskMiniView |
| |
| DeskMiniView::DeskMiniView(DesksBarView* owner_bar, |
| aura::Window* root_window, |
| Desk* desk, |
| const base::string16& title) |
| : views::Button(owner_bar), |
| owner_bar_(owner_bar), |
| root_window_(root_window), |
| desk_(desk), |
| desk_preview_(CreateDeskPreviewView(this)), |
| label_(new views::Label(title)), |
| close_desk_button_(new CloseDeskButton(this)) { |
| desk_->AddObserver(this); |
| |
| SetPaintToLayer(); |
| layer()->SetFillsBoundsOpaquely(false); |
| |
| label_->SetAutoColorReadabilityEnabled(false); |
| label_->SetSubpixelRenderingEnabled(false); |
| label_->set_can_process_events_within_subtree(false); |
| label_->SetEnabledColor(SK_ColorWHITE); |
| label_->SetLineHeight(10); |
| |
| close_desk_button_->SetVisible(false); |
| |
| // TODO(afakhry): Tooltips and accessible names. |
| |
| AddChildView(desk_preview_.get()); |
| AddChildView(label_); |
| AddChildView(close_desk_button_); |
| |
| SetFocusPainter(nullptr); |
| SetInkDropMode(InkDropMode::OFF); |
| |
| UpdateBorderColor(); |
| |
| SchedulePaint(); |
| } |
| |
| DeskMiniView::~DeskMiniView() { |
| // In tests, where animations are disabled, the mini_view maybe destroyed |
| // before the desk. |
| if (desk_) |
| desk_->RemoveObserver(this); |
| } |
| |
| void DeskMiniView::SetTitle(const base::string16& title) { |
| label_->SetText(title); |
| } |
| |
| aura::Window* DeskMiniView::GetDeskContainer() const { |
| DCHECK(desk_); |
| return desk_->GetDeskContainerForRoot(root_window_); |
| } |
| |
| void DeskMiniView::OnHoverStateMayHaveChanged() { |
| // TODO(afakhry): In tablet mode, discuss showing the close button on long |
| // press. |
| // Don't show the close button when hovered while the dragged window is on |
| // the DesksBarView. |
| close_desk_button_->SetVisible(DesksController::Get()->CanRemoveDesks() && |
| !owner_bar_->dragged_item_over_bar() && |
| IsMouseHovered()); |
| } |
| |
| void DeskMiniView::UpdateBorderColor() { |
| DCHECK(desk_); |
| if (owner_bar_->dragged_item_over_bar() && |
| IsPointOnMiniView(owner_bar_->last_dragged_item_screen_location())) { |
| desk_preview_->SetBorderColor(kDraggedOverColor); |
| } else { |
| desk_preview_->SetBorderColor(desk_->is_active() ? kActiveColor |
| : kInactiveColor); |
| } |
| } |
| |
| const char* DeskMiniView::GetClassName() const { |
| return "DeskMiniView"; |
| } |
| |
| void DeskMiniView::Layout() { |
| auto* root_window = GetWidget()->GetNativeWindow()->GetRootWindow(); |
| DCHECK(root_window); |
| |
| const gfx::Rect preview_bounds = GetDeskPreviewBounds(root_window); |
| desk_preview_->SetBoundsRect(preview_bounds); |
| |
| const gfx::Size label_size = label_->GetPreferredSize(); |
| const gfx::Rect label_bounds{ |
| (preview_bounds.width() - label_size.width()) / 2, |
| preview_bounds.bottom() + kLabelPreviewSpacing, label_size.width(), |
| label_size.height()}; |
| label_->SetBoundsRect(label_bounds); |
| |
| close_desk_button_->SetBounds( |
| preview_bounds.right() - kCloseButtonSize.width() - kCloseButtonMargin, |
| kCloseButtonMargin, kCloseButtonSize.width(), kCloseButtonSize.height()); |
| |
| Button::Layout(); |
| |
| SchedulePaint(); |
| } |
| |
| gfx::Size DeskMiniView::CalculatePreferredSize() const { |
| auto* root_window = GetWidget()->GetNativeWindow()->GetRootWindow(); |
| DCHECK(root_window); |
| |
| const gfx::Size label_size = label_->GetPreferredSize(); |
| const gfx::Rect preview_bounds = GetDeskPreviewBounds(root_window); |
| |
| return gfx::Size{ |
| std::max(preview_bounds.width(), label_size.width()), |
| preview_bounds.height() + kLabelPreviewSpacing + label_size.height()}; |
| } |
| |
| void DeskMiniView::ButtonPressed(views::Button* sender, |
| const ui::Event& event) { |
| DCHECK(desk_); |
| if (sender != close_desk_button_) |
| return; |
| |
| // Hide the close button so it can no longer be pressed. |
| close_desk_button_->SetVisible(false); |
| |
| // This mini_view can no longer be pressed. |
| listener_ = nullptr; |
| |
| auto* controller = DesksController::Get(); |
| DCHECK(controller->CanRemoveDesks()); |
| controller->RemoveDesk(desk_); |
| } |
| |
| void DeskMiniView::OnContentChanged() { |
| desk_preview_->RecreateDeskContentsMirrorLayers(); |
| } |
| |
| void DeskMiniView::OnDeskDestroyed(const Desk* desk) { |
| // Note that the mini_view outlives the desk (which will be removed after all |
| // DeskController's observers have been notified of its removal) because of |
| // the animation. |
| // Note that we can't make it the other way around (i.e. make the desk outlive |
| // the mini_view). The desk's existence (or lack thereof) is more important |
| // than the existence of the mini_view, since it determines whether we can |
| // create new desks or remove existing ones. This determines whether the close |
| // button will show on hover, and whether the new_desk_button is enabled. We |
| // shouldn't allow that state to be wrong while the mini_views perform the |
| // desk removal animation. |
| // TODO(afakhry): Consider detaching the layer and destroying the mini_view |
| // directly. |
| |
| DCHECK_EQ(desk_, desk); |
| desk_ = nullptr; |
| |
| // No need to remove `this` as an observer; it's done automatically. |
| } |
| |
| bool DeskMiniView::IsPointOnMiniView(const gfx::Point& screen_location) const { |
| gfx::Point point_in_view = screen_location; |
| ConvertPointFromScreen(this, &point_in_view); |
| return HitTestPoint(point_in_view); |
| } |
| |
| } // namespace ash |