blob: 5ac3a41a6db628da11cedf23b8281b8bf3241daf [file] [log] [blame]
// 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/desks_bar_view.h"
#include <algorithm>
#include <iterator>
#include <utility>
#include "ash/public/cpp/shell_window_ids.h"
#include "ash/public/cpp/window_properties.h"
#include "ash/shell.h"
#include "ash/style/ash_color_provider.h"
#include "ash/wm/desks/desk_mini_view.h"
#include "ash/wm/desks/desk_mini_view_animations.h"
#include "ash/wm/desks/desk_name_view.h"
#include "ash/wm/desks/desk_preview_view.h"
#include "ash/wm/desks/desks_util.h"
#include "ash/wm/desks/new_desk_button.h"
#include "ash/wm/overview/overview_controller.h"
#include "ash/wm/overview/overview_grid.h"
#include "ash/wm/overview/overview_highlight_controller.h"
#include "ash/wm/overview/overview_session.h"
#include "base/stl_util.h"
#include "third_party/skia/include/core/SkColor.h"
#include "ui/aura/window.h"
#include "ui/events/event_observer.h"
#include "ui/events/types/event_type.h"
#include "ui/gfx/color_palette.h"
#include "ui/gfx/geometry/insets.h"
#include "ui/views/event_monitor.h"
#include "ui/views/widget/widget.h"
#include "ui/wm/core/window_animations.h"
namespace ash {
namespace {
constexpr int kBarHeightInCompactLayout = 64;
constexpr int kUseCompactLayoutWidthThreshold = 600;
// In the non-compact layout, this is the height allocated for elements other
// than the desk preview (e.g. the DeskNameView, and the vertical paddings).
constexpr int kNonPreviewAllocatedHeight = 47;
// The local Y coordinate of the mini views in both non-compact and compact
// layouts respectively.
constexpr int kMiniViewsY = 16;
constexpr int kMiniViewsYCompact = 8;
// New desk button layout constants.
constexpr int kButtonRightMargin = 36;
constexpr int kIconAndTextHorizontalPadding = 16;
constexpr int kIconAndTextVerticalPadding = 8;
// Spacing between mini views.
constexpr int kMiniViewsSpacing = 12;
gfx::Rect GetGestureEventScreenRect(const ui::Event& event) {
DCHECK(event.IsGestureEvent());
return event.AsGestureEvent()->details().bounding_box();
}
OverviewHighlightController* GetHighlightController() {
auto* overview_controller = Shell::Get()->overview_controller();
DCHECK(overview_controller->InOverviewSession());
return overview_controller->overview_session()->highlight_controller();
}
} // namespace
// -----------------------------------------------------------------------------
// DeskBarHoverObserver:
class DeskBarHoverObserver : public ui::EventObserver {
public:
DeskBarHoverObserver(DesksBarView* owner, aura::Window* widget_window)
: owner_(owner),
event_monitor_(views::EventMonitor::CreateWindowMonitor(
this,
widget_window,
{ui::ET_MOUSE_PRESSED, ui::ET_MOUSE_DRAGGED, ui::ET_MOUSE_RELEASED,
ui::ET_MOUSE_MOVED, ui::ET_MOUSE_ENTERED, ui::ET_MOUSE_EXITED,
ui::ET_GESTURE_LONG_PRESS, ui::ET_GESTURE_LONG_TAP,
ui::ET_GESTURE_TAP, ui::ET_GESTURE_TAP_DOWN})) {}
~DeskBarHoverObserver() override = default;
// ui::EventObserver:
void OnEvent(const ui::Event& event) override {
switch (event.type()) {
case ui::ET_MOUSE_PRESSED:
case ui::ET_MOUSE_DRAGGED:
case ui::ET_MOUSE_RELEASED:
case ui::ET_MOUSE_MOVED:
case ui::ET_MOUSE_ENTERED:
case ui::ET_MOUSE_EXITED:
owner_->OnHoverStateMayHaveChanged();
break;
case ui::ET_GESTURE_LONG_PRESS:
case ui::ET_GESTURE_LONG_TAP:
owner_->OnGestureTap(GetGestureEventScreenRect(event),
/*is_long_gesture=*/true);
break;
case ui::ET_GESTURE_TAP:
case ui::ET_GESTURE_TAP_DOWN:
owner_->OnGestureTap(GetGestureEventScreenRect(event),
/*is_long_gesture=*/false);
break;
default:
NOTREACHED();
break;
}
}
private:
DesksBarView* owner_;
std::unique_ptr<views::EventMonitor> event_monitor_;
DISALLOW_COPY_AND_ASSIGN(DeskBarHoverObserver);
};
// -----------------------------------------------------------------------------
// DesksBarView:
DesksBarView::DesksBarView(OverviewGrid* overview_grid)
: background_view_(new views::View),
new_desk_button_(new NewDeskButton(this)),
overview_grid_(overview_grid) {
SetPaintToLayer();
layer()->SetFillsBoundsOpaquely(false);
background_view_->SetPaintToLayer(ui::LAYER_SOLID_COLOR);
background_view_->layer()->SetFillsBoundsOpaquely(false);
background_view_->layer()->SetColor(
AshColorProvider::Get()->GetBaseLayerColor(
AshColorProvider::BaseLayerType::kTransparent80,
AshColorProvider::AshColorMode::kDark));
AddChildView(background_view_);
AddChildView(new_desk_button_);
DesksController::Get()->AddObserver(this);
}
DesksBarView::~DesksBarView() {
DesksController::Get()->RemoveObserver(this);
}
// static
int DesksBarView::GetBarHeightForWidth(aura::Window* root,
const DesksBarView* desks_bar_view,
int width) {
if (width <= kUseCompactLayoutWidthThreshold ||
(desks_bar_view && width <= desks_bar_view->min_width_to_fit_contents_)) {
return kBarHeightInCompactLayout;
}
return DeskPreviewView::GetHeight(root, /*compact=*/false) +
kNonPreviewAllocatedHeight;
}
// static
std::unique_ptr<views::Widget> DesksBarView::CreateDesksWidget(
aura::Window* root,
const gfx::Rect& bounds) {
DCHECK(root);
DCHECK(root->IsRootWindow());
auto widget = std::make_unique<views::Widget>();
views::Widget::InitParams params(
views::Widget::InitParams::TYPE_WINDOW_FRAMELESS);
params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
params.activatable = views::Widget::InitParams::ACTIVATABLE_YES;
params.accept_events = true;
params.opacity = views::Widget::InitParams::WindowOpacity::kTranslucent;
// This widget will be parented to the currently-active desk container on
// |root|.
params.context = root;
params.bounds = bounds;
params.name = "VirtualDesksWidget";
// Even though this widget exists on the active desk container, it should not
// show up in the MRU list, and it should not be mirrored in the desks
// mini_views.
params.init_properties_container.SetProperty(kExcludeInMruKey, true);
params.init_properties_container.SetProperty(kHideInDeskMiniViewKey, true);
widget->Init(std::move(params));
auto* window = widget->GetNativeWindow();
window->set_id(kShellWindowId_DesksBarWindow);
::wm::SetWindowVisibilityAnimationTransition(window, ::wm::ANIMATE_NONE);
return widget;
}
void DesksBarView::Init() {
UpdateNewMiniViews(/*animate=*/false);
hover_observer_ = std::make_unique<DeskBarHoverObserver>(
this, GetWidget()->GetNativeWindow());
}
bool DesksBarView::IsDeskNameBeingModified() const {
if (!GetWidget()->IsActive())
return false;
for (auto* mini_view : mini_views_) {
if (mini_view->IsDeskNameBeingModified())
return true;
}
return false;
}
float DesksBarView::GetOnHoverWindowSizeScaleFactor() const {
return float{height()} / overview_grid_->root_window()->bounds().height();
}
void DesksBarView::OnHoverStateMayHaveChanged() {
for (auto* mini_view : mini_views_)
mini_view->OnHoverStateMayHaveChanged();
}
void DesksBarView::OnGestureTap(const gfx::Rect& screen_rect,
bool is_long_gesture) {
for (auto* mini_view : mini_views_)
mini_view->OnWidgetGestureTap(screen_rect, is_long_gesture);
}
void DesksBarView::SetDragDetails(const gfx::Point& screen_location,
bool dragged_item_over_bar) {
last_dragged_item_screen_location_ = screen_location;
const bool old_dragged_item_over_bar = dragged_item_over_bar_;
dragged_item_over_bar_ = dragged_item_over_bar;
if (!old_dragged_item_over_bar && !dragged_item_over_bar)
return;
for (auto* mini_view : mini_views_)
mini_view->UpdateBorderColor();
}
const char* DesksBarView::GetClassName() const {
return "DesksBarView";
}
void DesksBarView::Layout() {
background_view_->SetBoundsRect(bounds());
const bool compact = UsesCompactLayout();
new_desk_button_->SetLabelVisible(!compact);
gfx::Size new_desk_button_size = new_desk_button_->GetPreferredSize();
if (compact) {
new_desk_button_size.Enlarge(2 * kIconAndTextVerticalPadding,
2 * kIconAndTextVerticalPadding);
} else {
new_desk_button_size.Enlarge(2 * kIconAndTextHorizontalPadding,
2 * kIconAndTextVerticalPadding);
}
const gfx::Rect button_bounds{
bounds().right() - new_desk_button_size.width() - kButtonRightMargin,
(bounds().height() - new_desk_button_size.height()) / 2,
new_desk_button_size.width(), new_desk_button_size.height()};
new_desk_button_->SetBoundsRect(button_bounds);
if (mini_views_.empty())
return;
const gfx::Size mini_view_size = mini_views_[0]->GetPreferredSize();
const int total_width =
mini_views_.size() * (mini_view_size.width() + kMiniViewsSpacing) -
kMiniViewsSpacing;
int x = (width() - total_width) / 2;
const int y = compact ? kMiniViewsYCompact : kMiniViewsY;
for (auto* mini_view : mini_views_) {
mini_view->SetBoundsRect(gfx::Rect(gfx::Point(x, y), mini_view_size));
x += (mini_view_size.width() + kMiniViewsSpacing);
}
}
bool DesksBarView::OnMousePressed(const ui::MouseEvent& event) {
DeskNameView::CommitChanges(GetWidget());
return false;
}
void DesksBarView::OnGestureEvent(ui::GestureEvent* event) {
switch (event->type()) {
case ui::ET_GESTURE_LONG_PRESS:
case ui::ET_GESTURE_LONG_TAP:
case ui::ET_GESTURE_TAP:
case ui::ET_GESTURE_TAP_DOWN:
DeskNameView::CommitChanges(GetWidget());
break;
default:
break;
}
}
bool DesksBarView::UsesCompactLayout() const {
return width() <= kUseCompactLayoutWidthThreshold ||
width() <= min_width_to_fit_contents_;
}
void DesksBarView::ButtonPressed(views::Button* sender,
const ui::Event& event) {
if (sender == new_desk_button_)
new_desk_button_->OnButtonPressed();
}
void DesksBarView::OnDeskAdded(const Desk* desk) {
DeskNameView::CommitChanges(GetWidget());
UpdateNewMiniViews(/*animate=*/true);
}
void DesksBarView::OnDeskRemoved(const Desk* desk) {
DeskNameView::CommitChanges(GetWidget());
auto iter = std::find_if(
mini_views_.begin(), mini_views_.end(),
[desk](DeskMiniView* mini_view) { return desk == mini_view->desk(); });
DCHECK(iter != mini_views_.end());
// Let the highlight controller know the view is destroying before it is
// removed from the collection because it needs to know the index of the mini
// view, or the desk name view (if either is currently highlighted) relative
// to other traversable views.
auto* highlight_controller = GetHighlightController();
// The order here matters, we call it first on the desk_name_view since it
// comes later in the highlight order (See documentation of
// OnViewDestroyingOrDisabling()).
highlight_controller->OnViewDestroyingOrDisabling((*iter)->desk_name_view());
highlight_controller->OnViewDestroyingOrDisabling(*iter);
const int begin_x = GetFirstMiniViewXOffset();
// Remove the mini view from the list now. And remove it from its parent
// after the animation is done.
DeskMiniView* mini_view = *iter;
auto partition_iter = mini_views_.erase(iter);
UpdateMinimumWidthToFitContents();
overview_grid_->OnDesksChanged();
new_desk_button_->UpdateButtonState();
PerformRemoveDeskMiniViewAnimation(
mini_view,
std::vector<DeskMiniView*>(mini_views_.begin(), partition_iter),
std::vector<DeskMiniView*>(partition_iter, mini_views_.end()),
begin_x - GetFirstMiniViewXOffset());
}
void DesksBarView::OnDeskActivationChanged(const Desk* activated,
const Desk* deactivated) {
for (auto* mini_view : mini_views_) {
const Desk* desk = mini_view->desk();
if (desk == activated || desk == deactivated)
mini_view->UpdateBorderColor();
}
}
void DesksBarView::OnDeskSwitchAnimationLaunching() {}
void DesksBarView::OnDeskSwitchAnimationFinished() {}
void DesksBarView::UpdateNewMiniViews(bool animate) {
const auto& desks = DesksController::Get()->desks();
if (desks.size() < 2) {
// We do not show mini_views when we have a single desk.
DCHECK(mini_views_.empty());
// The bar background is initially translated off the screen.
gfx::Transform translate;
translate.Translate(0, -height());
background_view_->layer()->SetTransform(translate);
background_view_->layer()->SetOpacity(0);
return;
}
// This should not be called when a desk is removed.
DCHECK_LE(mini_views_.size(), desks.size());
const bool first_time_mini_views = mini_views_.empty();
const int begin_x = GetFirstMiniViewXOffset();
std::vector<DeskMiniView*> new_mini_views;
aura::Window* root_window = GetWidget()->GetNativeWindow()->GetRootWindow();
DCHECK(root_window);
for (const auto& desk : desks) {
if (!FindMiniViewForDesk(desk.get())) {
DeskMiniView* mini_view = AddChildView(
std::make_unique<DeskMiniView>(this, root_window, desk.get()));
mini_views_.push_back(mini_view);
new_mini_views.push_back(mini_view);
}
}
UpdateMinimumWidthToFitContents();
overview_grid_->OnDesksChanged();
if (!animate)
return;
PerformNewDeskMiniViewAnimation(this, new_mini_views,
begin_x - GetFirstMiniViewXOffset(),
first_time_mini_views);
}
DeskMiniView* DesksBarView::FindMiniViewForDesk(const Desk* desk) const {
for (auto* mini_view : mini_views_) {
if (mini_view->desk() == desk)
return mini_view;
}
return nullptr;
}
int DesksBarView::GetFirstMiniViewXOffset() const {
return mini_views_.empty() ? bounds().CenterPoint().x()
: mini_views_[0]->bounds().x();
}
void DesksBarView::UpdateMinimumWidthToFitContents() {
int button_width = new_desk_button_->GetMinSize(/*compact=*/false).width();
button_width += 2 * kIconAndTextHorizontalPadding;
button_width += kButtonRightMargin;
if (mini_views_.empty()) {
min_width_to_fit_contents_ = button_width;
return;
}
const int mini_view_width = mini_views_[0]->GetMinWidthForDefaultLayout();
const int total_mini_views_width =
mini_views_.size() * (mini_view_width + kMiniViewsSpacing) -
kMiniViewsSpacing;
min_width_to_fit_contents_ = total_mini_views_width + button_width * 2;
}
} // namespace ash