blob: 344a15b95aa3dbf7b226001cc770b39773636df9 [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/shell.h"
#include "ash/strings/grit/ash_strings.h"
#include "ash/wm/desks/desk_mini_view.h"
#include "ash/wm/desks/desk_mini_view_animations.h"
#include "ash/wm/desks/new_desk_button.h"
#include "ash/wm/overview/overview_controller.h"
#include "base/stl_util.h"
#include "third_party/skia/include/core/SkColor.h"
#include "ui/aura/window.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/events/event_observer.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 kBarHeight = 104;
base::string16 GetMiniViewTitle(int mini_view_index) {
DCHECK_GE(mini_view_index, 0);
DCHECK_LT(mini_view_index, 4);
constexpr int kStringIds[] = {IDS_ASH_DESKS_DESK_1_MINI_VIEW_TITLE,
IDS_ASH_DESKS_DESK_2_MINI_VIEW_TITLE,
IDS_ASH_DESKS_DESK_3_MINI_VIEW_TITLE,
IDS_ASH_DESKS_DESK_4_MINI_VIEW_TITLE};
return l10n_util::GetStringUTF16(kStringIds[mini_view_index]);
}
} // 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})) {}
~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;
default:
NOTREACHED();
break;
}
}
private:
DesksBarView* owner_;
std::unique_ptr<views::EventMonitor> event_monitor_;
DISALLOW_COPY_AND_ASSIGN(DeskBarHoverObserver);
};
// -----------------------------------------------------------------------------
// DesksBarView:
DesksBarView::DesksBarView()
: backgroud_view_(new views::View),
new_desk_button_(new NewDeskButton(this)) {
SetPaintToLayer();
layer()->SetFillsBoundsOpaquely(false);
backgroud_view_->SetPaintToLayer(ui::LAYER_SOLID_COLOR);
backgroud_view_->layer()->SetFillsBoundsOpaquely(false);
backgroud_view_->layer()->SetColor(SkColorSetARGB(60, 0, 0, 0));
AddChildView(backgroud_view_);
AddChildView(new_desk_button_);
UpdateNewDeskButtonState();
DesksController::Get()->AddObserver(this);
}
DesksBarView::~DesksBarView() {
DesksController::Get()->RemoveObserver(this);
}
// static
int DesksBarView::GetBarHeight() {
return kBarHeight;
}
// 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_NO;
params.accept_events = true;
params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW;
// Use the wallpaper container similar to all background widgets created in
// overview mode.
params.parent = root->GetChildById(kShellWindowId_WallpaperContainer);
params.bounds = bounds;
params.name = "VirtualDesksWidget";
widget->Init(params);
::wm::SetWindowVisibilityAnimationTransition(widget->GetNativeWindow(),
::wm::ANIMATE_NONE);
return widget;
}
void DesksBarView::Init() {
UpdateNewMiniViews(/*animate=*/false);
hover_observer_ = std::make_unique<DeskBarHoverObserver>(
this, GetWidget()->GetNativeWindow());
}
void DesksBarView::OnHoverStateMayHaveChanged() {
for (auto& mini_view : mini_views_)
mini_view->OnHoverStateMayHaveChanged();
}
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() {
backgroud_view_->SetBoundsRect(bounds());
constexpr int kButtonRightMargin = 36;
constexpr int kIconAndTextHorizontalPadding = 16;
constexpr int kIconAndTextVerticalPadding = 8;
gfx::Size new_desk_button_size = new_desk_button_->GetPreferredSize();
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;
constexpr int kMiniViewsSpacing = 8;
const gfx::Size mini_view_size = mini_views_[0]->GetPreferredSize();
const int total_width =
mini_views_.size() * (mini_view_size.width() + kMiniViewsSpacing) -
kMiniViewsSpacing;
gfx::Rect mini_views_bounds = bounds();
mini_views_bounds.ClampToCenteredSize(
gfx::Size(total_width, mini_view_size.height()));
int x = mini_views_bounds.x();
const int y = mini_views_bounds.y();
for (auto& mini_view : mini_views_) {
mini_view->SetBoundsRect(gfx::Rect(gfx::Point(x, y), mini_view_size));
x += (mini_view_size.width() + kMiniViewsSpacing);
}
}
void DesksBarView::ButtonPressed(views::Button* sender,
const ui::Event& event) {
auto* controller = DesksController::Get();
if (sender == new_desk_button_) {
if (controller->CanCreateDesks()) {
controller->NewDesk();
UpdateNewDeskButtonState();
}
return;
}
for (auto& mini_view : mini_views_) {
if (mini_view.get() == sender) {
controller->ActivateDesk(mini_view->desk());
return;
}
}
}
void DesksBarView::OnDeskAdded(const Desk* desk) {
UpdateNewMiniViews(/*animate=*/true);
}
void DesksBarView::OnDeskRemoved(const Desk* desk) {
auto iter =
std::find_if(mini_views_.begin(), mini_views_.end(),
[desk](const std::unique_ptr<DeskMiniView>& mini_view) {
return desk == mini_view->desk();
});
DCHECK(iter != mini_views_.end());
const int begin_x = GetFirstMiniViewXOffset();
std::unique_ptr<DeskMiniView> removed_mini_view = std::move(*iter);
auto partition_iter = mini_views_.erase(iter);
Layout();
UpdateMiniViewsLabels();
UpdateNewDeskButtonState();
std::vector<DeskMiniView*> mini_views_before;
std::vector<DeskMiniView*> mini_views_after;
const auto transform_lambda =
[](const std::unique_ptr<DeskMiniView>& mini_view) {
return mini_view.get();
};
std::transform(mini_views_.begin(), partition_iter,
std::back_inserter(mini_views_before), transform_lambda);
std::transform(partition_iter, mini_views_.end(),
std::back_inserter(mini_views_after), transform_lambda);
PerformRemoveDeskMiniViewAnimation(std::move(removed_mini_view),
mini_views_before, mini_views_after,
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::OnDeskSwitchAnimationFinished() {}
void DesksBarView::UpdateNewDeskButtonState() {
new_desk_button_->SetEnabled(DesksController::Get()->CanCreateDesks());
}
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, -kBarHeight);
backgroud_view_->layer()->SetTransform(translate);
backgroud_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())) {
mini_views_.emplace_back(std::make_unique<DeskMiniView>(
this, root_window, desk.get(), GetMiniViewTitle(mini_views_.size())));
DeskMiniView* mini_view = mini_views_.back().get();
mini_view->set_owned_by_client();
new_mini_views.emplace_back(mini_view);
AddChildView(mini_view);
}
}
Layout();
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.get();
}
return nullptr;
}
void DesksBarView::UpdateMiniViewsLabels() {
// TODO(afakhry): Don't do this for user-modified desk labels.
size_t i = 0;
for (auto& mini_view : mini_views_)
mini_view->SetTitle(GetMiniViewTitle(i++));
}
int DesksBarView::GetFirstMiniViewXOffset() const {
return mini_views_.empty() ? bounds().CenterPoint().x()
: mini_views_[0]->bounds().x();
}
} // namespace ash