#include "ash/wm/desks/desk_bar_view_base.h"
#include <algorithm>
#include <vector>
#include "ash/ash_element_identifiers.h"
#include "ash/constants/ash_features.h"
#include "ash/keyboard/ui/keyboard_ui_controller.h"
#include "ash/public/cpp/saved_desk_delegate.h"
#include "ash/public/cpp/shelf_types.h"
#include "ash/public/cpp/shell_window_ids.h"
#include "ash/public/cpp/style/color_provider.h"
#include "ash/public/cpp/window_properties.h"
#include "ash/resources/vector_icons/vector_icons.h"
#include "ash/shelf/shelf.h"
#include "ash/shell.h"
#include "ash/strings/grit/ash_strings.h"
#include "ash/style/ash_color_id.h"
#include "ash/style/typography.h"
#include "ash/wm/desks/desk.h"
#include "ash/wm/desks/desk_action_button.h"
#include "ash/wm/desks/desk_action_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_constants.h"
#include "ash/wm/desks/desks_util.h"
#include "ash/wm/desks/templates/saved_desk_metrics_util.h"
#include "ash/wm/desks/templates/saved_desk_presenter.h"
#include "ash/wm/desks/templates/saved_desk_util.h"
#include "ash/wm/overview/overview_controller.h"
#include "ash/wm/overview/overview_grid.h"
#include "ash/wm/overview/overview_metrics.h"
#include "ash/wm/overview/overview_session.h"
#include "ash/wm/overview/overview_utils.h"
#include "ash/wm/window_positioning_utils.h"
#include "ash/wm/window_properties.h"
#include "ash/wm/work_area_insets.h"
#include "base/check.h"
#include "base/functional/bind.h"
#include "base/i18n/rtl.h"
#include "base/memory/raw_ptr.h"
#include "base/metrics/histogram_functions.h"
#include "base/notreached.h"
#include "base/task/sequenced_task_runner.h"
#include "base/trace_event/trace_event.h"
#include "base/uuid.h"
#include "chromeos/constants/chromeos_features.h"
#include "chromeos/utils/haptics_util.h"
#include "third_party/abseil-cpp/absl/cleanup/cleanup.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/base/mojom/menu_source_type.mojom.h"
#include "ui/compositor/layer.h"
#include "ui/display/screen.h"
#include "ui/events/devices/device_data_manager.h"
#include "ui/events/devices/haptic_touchpad_effects.h"
#include "ui/events/event_observer.h"
#include "ui/gfx/geometry/insets.h"
#include "ui/gfx/geometry/point.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/size.h"
#include "ui/gfx/scoped_animation_duration_scale_mode.h"
#include "ui/gfx/text_elider.h"
#include "ui/views/background.h"
#include "ui/views/event_monitor.h"
#include "ui/views/highlight_border.h"
#include "ui/views/view_class_properties.h"
#include "ui/wm/core/window_animations.h"
namespace ash {
namespace {
constexpr base::TimeDelta kAnimationDelayDuration = base::Milliseconds(100);
bool HasExternalKeyboard() {
for (const ui::InputDevice& device :
ui::DeviceDataManager::GetInstance()->GetKeyboardDevices()) {
if (device.type != ui::InputDeviceType::INPUT_DEVICE_INTERNAL) {
return true;
}
}
return false;
}
void InitScrollContentsAnimationSettings(
ui::ScopedLayerAnimationSettings& settings) {
settings.SetTransitionDuration(kDeskBarScrollDuration);
settings.SetTweenType(gfx::Tween::ACCEL_20_DECEL_60);
}
gfx::Rect GetGestureEventScreenRect(const ui::Event& event) {
CHECK(event.IsGestureEvent());
return event.AsGestureEvent()->details().bounding_box();
}
void MaybeSetupBackgroundView(DeskBarViewBase* bar_view) {
const bool type_is_desk_button =
bar_view->type() == DeskBarViewBase::Type::kDeskButton;
auto* view = type_is_desk_button ? bar_view->background_view() : bar_view;
view->SetPaintToLayer();
auto* layer = view->layer();
layer->SetFillsBoundsOpaquely(false);
if (!type_is_desk_button) {
return;
}
if (chromeos::features::IsSystemBlurEnabled()) {
layer->SetBackgroundBlur(ColorProvider::kBackgroundBlurSigma);
layer->SetBackdropFilterQuality(ColorProvider::kBackgroundBlurQuality);
}
const int corner_radius = type_is_desk_button
? kDeskBarCornerRadiusOverviewDeskButton
: kDeskBarCornerRadiusOverview;
view->SetBorder(std::make_unique<views::HighlightBorder>(
corner_radius, views::HighlightBorder::Type::kHighlightBorderNoShadow));
layer->SetRoundedCornerRadius(gfx::RoundedCornersF(corner_radius));
const ui::ColorId background_color_id =
chromeos::features::IsSystemBlurEnabled()
? static_cast<ui::ColorId>(kColorAshShieldAndBase80)
: cros_tokens::kCrosSysSystemOnBaseOpaque;
view->SetBackground(views::CreateSolidBackground(background_color_id));
}
}
class DeskBarScrollViewLayout : public views::LayoutManager {
public:
explicit DeskBarScrollViewLayout(DeskBarViewBase* bar_view)
: bar_view_(bar_view) {}
DeskBarScrollViewLayout(const DeskBarScrollViewLayout&) = delete;
DeskBarScrollViewLayout& operator=(const DeskBarScrollViewLayout&) = delete;
~DeskBarScrollViewLayout() override = default;
int GetContentViewX(int contents_width) const {
const auto shelf_type = Shelf::ForWindow(bar_view_->root())->alignment();
if (bar_view_->type() == DeskBarViewBase::Type::kOverview ||
shelf_type == ShelfAlignment::kBottom ||
shelf_type == ShelfAlignment::kBottomLocked) {
return (width_ - contents_width) / 2 +
kDeskBarDeskPreviewViewFocusRingThicknessAndPadding;
}
if (shelf_type == ShelfAlignment::kLeft) {
return kDeskBarDeskPreviewViewFocusRingThicknessAndPadding;
}
CHECK_EQ(shelf_type, ShelfAlignment::kRight);
return width_ - contents_width +
kDeskBarDeskPreviewViewFocusRingThicknessAndPadding;
}
void LayoutBackground() {
if (!bar_view_->background_view_) {
return;
}
const ShelfAlignment shelf_alignment =
Shelf::ForWindow(bar_view_->root_)->alignment();
const gfx::Rect preferred_bounds =
gfx::Rect(bar_view_->CalculatePreferredSize({}));
const gfx::Rect current_bounds = gfx::Rect(bar_view_->size());
gfx::Rect new_bounds = preferred_bounds;
if (shelf_alignment == ShelfAlignment::kBottom) {
new_bounds = current_bounds;
new_bounds.ClampToCenteredSize(preferred_bounds.size());
} else if ((shelf_alignment == ShelfAlignment::kLeft) ==
base::i18n::IsRTL()) {
new_bounds.Offset(current_bounds.width() - preferred_bounds.width(), 0);
}
bar_view_->background_view_->SetBoundsRect(new_bounds);
}
void LayoutDeskIconButtonLabel(views::Label* label,
const gfx::Rect& icon_button_bounds,
DeskNameView* desk_name_view,
int label_text_id) {
label->SetText(gfx::ElideText(
l10n_util::GetStringUTF16(label_text_id), gfx::FontList(),
icon_button_bounds.width() - desk_name_view->GetInsets().width(),
gfx::ELIDE_TAIL));
const gfx::Size button_label_size =
label->GetPreferredSize(views::SizeBounds(label->width(), {}));
label->SetBoundsRect(gfx::Rect(
gfx::Point(
icon_button_bounds.x() +
((icon_button_bounds.width() - button_label_size.width()) / 2),
icon_button_bounds.bottom() +
kDeskBarDeskIconButtonAndLabelSpacing),
gfx::Size(button_label_size.width(), desk_name_view->height())));
}
void UpdateChildViewsVisibility() {
auto* default_desk_button = bar_view_->default_desk_button();
auto* new_desk_button = bar_view_->new_desk_button();
auto* library_button = bar_view_->library_button();
auto* library_button_label = bar_view_->library_button_label();
const bool zero_state = bar_view_->IsZeroState();
default_desk_button->SetVisible(zero_state);
new_desk_button->SetVisible(true);
bar_view_->UpdateNewDeskButtonLabelVisibility(
!zero_state &&
new_desk_button->state() == DeskIconButton::State::kActive,
false);
if (library_button) {
library_button->SetVisible(bar_view_->ShouldShowLibraryUi());
}
if (library_button_label) {
library_button_label->SetVisible(!zero_state &&
library_button->state() ==
DeskIconButton::State::kActive);
}
}
void Layout(views::View* host) override {
TRACE_EVENT0("ui", "DeskBarScrollViewLayout::Layout");
const gfx::Rect scroll_bounds =
bar_view_->GetTopLevelViewWithContents().bounds();
UpdateChildViewsVisibility();
const gfx::Size contents_size = host->GetPreferredSize();
if (bar_view_->IsZeroState()) {
host->SetBoundsRect(scroll_bounds);
auto* default_desk_button = bar_view_->default_desk_button();
default_desk_button->SetBoundsRect(gfx::Rect(
gfx::Point((scroll_bounds.width() - contents_size.width()) / 2,
kDeskBarZeroStateY),
default_desk_button->GetPreferredSize()));
default_desk_button->UpdateLabelText();
auto* new_desk_button = bar_view_->new_desk_button();
new_desk_button->SetBoundsRect(
gfx::Rect(gfx::Point(default_desk_button->bounds().right() +
kDeskBarZeroStateButtonSpacing,
kDeskBarZeroStateY),
new_desk_button->GetPreferredSize()));
if (auto* library_button = bar_view_->library_button()) {
library_button->SetBoundsRect(
gfx::Rect(gfx::Point(new_desk_button->bounds().right() +
kDeskBarZeroStateButtonSpacing,
kDeskBarZeroStateY),
library_button->GetPreferredSize()));
}
return;
}
std::vector<raw_ptr<DeskMiniView, VectorExperimental>> mini_views =
bar_view_->mini_views();
if (mini_views.empty()) {
return;
}
const bool is_rtl = base::i18n::IsRTL();
if (is_rtl) {
std::ranges::reverse(mini_views);
}
width_ = std::max(scroll_bounds.width(), contents_size.width());
host->SetSize(gfx::Size(width_, contents_size.height()));
const int increment = is_rtl ? -1 : 1;
const int y =
kDeskBarMiniViewsY - mini_views[0]->GetPreviewBorderInsets().top();
const gfx::Size mini_view_size = mini_views[0]->GetPreferredSize();
auto layout_mini_views = [&](int& x) {
const int start = is_rtl ? mini_views.size() - 1 : 0;
const int end = is_rtl ? -1 : mini_views.size();
const int delta_x =
(mini_view_size.width() + kDeskBarMiniViewsSpacing) * increment;
for (int i = start; i != end; i += increment) {
auto* mini_view = mini_views[i].get();
mini_view->SetBoundsRect(
gfx::Rect(gfx::Point(is_rtl ? x - mini_view_size.width() : x, y),
mini_view_size));
x += delta_x;
}
};
auto* desk_name_view = mini_views[0]->desk_name_view();
auto layout_new_desk_button = [&](int& x) {
auto* new_desk_button = bar_view_->new_desk_button();
const gfx::Size new_desk_button_size =
new_desk_button->GetPreferredSize();
const gfx::Rect new_desk_button_bounds(gfx::Rect(
gfx::Point(is_rtl ? x - new_desk_button_size.width() : x, y),
new_desk_button_size));
new_desk_button->SetBoundsRect(new_desk_button_bounds);
if (bar_view_->new_desk_button_label()) {
LayoutDeskIconButtonLabel(bar_view_->new_desk_button_label(),
new_desk_button_bounds, desk_name_view,
IDS_ASH_DESKS_NEW_DESK_BUTTON_LABEL);
}
x +=
(new_desk_button_size.width() + kDeskBarMiniViewsSpacing) * increment;
};
auto layout_library_button = [&](int& x) {
auto* library_button = bar_view_->library_button();
if (!library_button) {
return;
}
const gfx::Size library_button_size =
library_button ? library_button->GetPreferredSize() : gfx::Size();
const gfx::Rect library_button_bounds(
gfx::Rect(gfx::Point(is_rtl ? x - library_button_size.width() : x, y),
library_button_size));
library_button->SetBoundsRect(library_button_bounds);
LayoutDeskIconButtonLabel(
bar_view_->library_button_label(), library_button_bounds,
desk_name_view,
saved_desk_util::AreDesksTemplatesEnabled()
? IDS_ASH_DESKS_TEMPLATES_DESKS_BAR_BUTTON_LIBRARY
: IDS_ASH_DESKS_TEMPLATES_DESKS_BAR_BUTTON_SAVED_FOR_LATER);
x += (library_button_size.width() + kDeskBarMiniViewsSpacing) * increment;
};
if (is_rtl) {
int x = width_ - GetContentViewX(contents_size.width());
layout_library_button(x);
layout_new_desk_button(x);
layout_mini_views(x);
} else {
int x = GetContentViewX(contents_size.width());
layout_mini_views(x);
layout_new_desk_button(x);
layout_library_button(x);
}
LayoutBackground();
}
gfx::Size GetPreferredSize(const views::View* host) const override {
return GetPreferredSize(host, {});
}
gfx::Size GetPreferredSize(
const views::View* host,
const views::SizeBounds& available_size) const override {
int width = 0;
std::vector<views::View*> child_views;
for (ash::DeskMiniView* mini_view : bar_view_->mini_views_) {
child_views.emplace_back(mini_view);
}
child_views.emplace_back(bar_view_->default_desk_button_);
child_views.emplace_back(bar_view_->new_desk_button_);
child_views.emplace_back(bar_view_->library_button_);
const int child_spacing =
bar_view_->state_ == DeskBarViewBase::State::kExpanded
? kDeskBarMiniViewsSpacing
: kDeskBarZeroStateButtonSpacing;
for (auto* child : child_views) {
if (!child || !child->GetVisible()) {
continue;
}
if (width) {
width += child_spacing;
}
width += child->GetPreferredSize().width();
}
width += kDeskBarDeskPreviewViewFocusRingThicknessAndPadding * 2;
return gfx::Size(
width, DeskBarViewBase::GetPreferredBarHeight(
bar_view_->root(), bar_view_->type_, bar_view_->state_));
}
private:
raw_ptr<DeskBarViewBase> bar_view_;
int width_ = 0;
};
class DeskBarHoverObserver : public ui::EventObserver {
public:
DeskBarHoverObserver(DeskBarViewBase* owner, aura::Window* widget_window)
: owner_(owner),
event_monitor_(views::EventMonitor::CreateWindowMonitor(
this,
widget_window,
{ui::EventType::kMousePressed, ui::EventType::kMouseDragged,
ui::EventType::kMouseReleased, ui::EventType::kMouseMoved,
ui::EventType::kMouseEntered, ui::EventType::kMouseExited,
ui::EventType::kGestureLongPress, ui::EventType::kGestureLongTap,
ui::EventType::kGestureTap, ui::EventType::kGestureTapDown})) {}
DeskBarHoverObserver(const DeskBarHoverObserver&) = delete;
DeskBarHoverObserver& operator=(const DeskBarHoverObserver&) = delete;
~DeskBarHoverObserver() override = default;
void OnEvent(const ui::Event& event) override {
switch (event.type()) {
case ui::EventType::kMousePressed:
case ui::EventType::kMouseDragged:
case ui::EventType::kMouseReleased:
case ui::EventType::kMouseMoved:
case ui::EventType::kMouseEntered:
case ui::EventType::kMouseExited:
owner_->OnHoverStateMayHaveChanged();
break;
case ui::EventType::kGestureLongPress:
case ui::EventType::kGestureLongTap:
owner_->OnGestureTap(GetGestureEventScreenRect(event),
true);
break;
case ui::EventType::kGestureTap:
case ui::EventType::kGestureTapDown:
owner_->OnGestureTap(GetGestureEventScreenRect(event),
false);
break;
default:
NOTREACHED();
}
}
private:
raw_ptr<DeskBarViewBase> owner_;
std::unique_ptr<views::EventMonitor> event_monitor_;
};
class DeskBarViewBase::PostLayoutOperation {
public:
virtual ~PostLayoutOperation() = default;
virtual void InitializePreLayout() {}
virtual void Run() = 0;
protected:
explicit PostLayoutOperation(DeskBarViewBase* bar_view)
: bar_view_(bar_view) {
CHECK(bar_view_);
}
const raw_ptr<DeskBarViewBase> bar_view_;
};
class DeskBarViewBase::AddDeskAnimation
: public DeskBarViewBase::PostLayoutOperation {
public:
AddDeskAnimation(DeskBarViewBase* bar_view,
const gfx::Rect& old_bar_bounds,
std::vector<DeskMiniView*> new_mini_views)
: PostLayoutOperation(bar_view),
old_bar_bounds_(old_bar_bounds),
new_mini_views_(std::move(new_mini_views)) {}
void InitializePreLayout() override {
views_previous_x_map_ = bar_view_->GetAnimatableViewsCurrentXMap();
}
void Run() override {
auto new_mini_view_it = new_mini_views_.begin();
while (new_mini_view_it != new_mini_views_.end()) {
if (base::Contains(bar_view_->mini_views_, *new_mini_view_it)) {
++new_mini_view_it;
} else {
new_mini_view_it = new_mini_views_.erase(new_mini_view_it);
}
}
if (bar_view_->type_ == Type::kDeskButton) {
PerformDeskBarAddDeskAnimation(bar_view_, old_bar_bounds_);
}
PerformAddDeskMiniViewAnimation(new_mini_views_);
PerformDeskBarChildViewShiftAnimation(bar_view_, views_previous_x_map_);
}
private:
const gfx::Rect old_bar_bounds_;
std::vector<DeskMiniView*> new_mini_views_;
base::flat_map<views::View*, int> views_previous_x_map_;
};
class DeskBarViewBase::DeskIconButtonScaleAnimation
: public DeskBarViewBase::PostLayoutOperation {
public:
DeskIconButtonScaleAnimation(DeskBarViewBase* bar_view,
DeskIconButton* button)
: PostLayoutOperation(bar_view), button_(button) {
CHECK(button_);
}
void InitializePreLayout() override {
begin_x_ = bar_view_->GetFirstMiniViewXOffset();
old_bounds_ = button_->GetBoundsInScreen();
}
void Run() override {
const gfx::RectF new_bounds = gfx::RectF(button_->GetBoundsInScreen());
gfx::Transform scale_transform;
const int shift_x = begin_x_ - bar_view_->GetFirstMiniViewXOffset();
scale_transform.Translate(shift_x, 0);
if (!old_bounds_.IsEmpty()) {
CHECK(!new_bounds.IsEmpty());
scale_transform.Scale(old_bounds_.width() / new_bounds.width(),
old_bounds_.height() / new_bounds.height());
}
PerformDeskIconButtonScaleAnimation(button_, bar_view_, scale_transform,
shift_x);
bar_view_->MaybeRefreshOverviewGridBounds();
}
private:
const raw_ptr<DeskIconButton> button_;
int begin_x_ = 0;
gfx::Rect old_bounds_;
};
class DeskBarViewBase::LibraryButtonVisibilityAnimation
: public DeskBarViewBase::PostLayoutOperation {
public:
explicit LibraryButtonVisibilityAnimation(DeskBarViewBase* bar_view)
: PostLayoutOperation(bar_view) {}
void InitializePreLayout() override {
begin_x_ = bar_view_->GetFirstMiniViewXOffset();
}
void Run() override {
PerformLibraryButtonVisibilityAnimation(
bar_view_->mini_views_, bar_view_->new_desk_button_,
begin_x_ - bar_view_->GetFirstMiniViewXOffset());
}
private:
int begin_x_ = 0;
};
class DeskBarViewBase::NewDeskButtonPressedScroll
: public DeskBarViewBase::PostLayoutOperation {
public:
explicit NewDeskButtonPressedScroll(DeskBarViewBase* bar_view)
: PostLayoutOperation(bar_view) {}
void Run() override {
bar_view_->NudgeDeskName(bar_view_->mini_views_.size() - 1);
if (!base::i18n::IsRTL()) {
bar_view_->ScrollToShowViewIfNecessary(bar_view_->new_desk_button_);
}
}
};
class DeskBarViewBase::RemoveDeskAnimation
: public DeskBarViewBase::PostLayoutOperation {
public:
RemoveDeskAnimation(DeskBarViewBase* bar_view,
DeskMiniView* removed_mini_view)
: PostLayoutOperation(bar_view),
removed_mini_view_(bar_view_->type_ == DeskBarViewBase::Type::kOverview
? removed_mini_view
: nullptr) {}
void InitializePreLayout() override {
if (bar_view_->type_ == DeskBarViewBase::Type::kDeskButton) {
old_background_bounds_ = bar_view_->background_view_->GetBoundsInScreen();
}
views_previous_x_map_ = bar_view_->GetAnimatableViewsCurrentXMap();
}
void Run() override {
if (removed_mini_view_) {
DeskMiniView* removed_mini_view = removed_mini_view_;
removed_mini_view_ = nullptr;
PerformRemoveDeskMiniViewAnimation(removed_mini_view);
} else {
PerformDeskBarRemoveDeskAnimation(bar_view_, old_background_bounds_);
}
PerformDeskBarChildViewShiftAnimation(bar_view_, views_previous_x_map_);
bar_view_->MaybeUpdateDeskActionButtonTooltips();
}
private:
raw_ptr<DeskMiniView> removed_mini_view_;
gfx::Rect old_background_bounds_;
base::flat_map<views::View*, int> views_previous_x_map_;
};
class DeskBarViewBase::ReorderDeskAnimation
: public DeskBarViewBase::PostLayoutOperation {
public:
ReorderDeskAnimation(DeskBarViewBase* bar_view,
size_t old_index,
size_t new_index)
: PostLayoutOperation(bar_view),
old_index_(old_index),
new_index_(new_index) {}
void Run() override {
const auto& mini_views = bar_view_->mini_views_;
if (old_index_ >= mini_views.size() || new_index_ >= mini_views.size()) {
return;
}
PerformReorderDeskMiniViewAnimation(old_index_, new_index_, mini_views);
bar_view_->MaybeUpdateDeskActionButtonTooltips();
}
private:
const size_t old_index_;
const size_t new_index_;
};
class DeskBarViewBase::ScrollForActiveMiniView
: public DeskBarViewBase::PostLayoutOperation {
public:
explicit ScrollForActiveMiniView(DeskBarViewBase* bar_view)
: PostLayoutOperation(bar_view) {}
void Run() override {
auto it = std::ranges::find_if(
bar_view_->mini_views_,
[](DeskMiniView* mini_view) { return mini_view->desk()->is_active(); });
if (it != bar_view_->mini_views_.end()) {
bar_view_->ScrollToShowViewIfNecessary(*it);
}
}
};
DeskBarViewBase::DeskBarViewBase(
aura::Window* root,
Type type,
base::WeakPtr<WindowOcclusionCalculator> window_occlusion_calculator)
: type_(type),
state_(GetPreferredState(type)),
root_(root),
window_occlusion_calculator_(window_occlusion_calculator) {
CHECK(root && root->IsRootWindow());
if (type_ == Type::kDeskButton) {
background_view_ = AddChildView(std::make_unique<views::View>());
}
MaybeSetupBackgroundView(this);
contents_view_ = AddChildView(std::make_unique<views::View>());
default_desk_button_ =
contents_view_->AddChildView(std::make_unique<DefaultDeskButton>(this));
new_desk_button_ =
contents_view_->AddChildView(std::make_unique<DeskIconButton>(
this, &kDesksNewDeskButtonIcon,
l10n_util::GetStringUTF16(IDS_ASH_DESKS_NEW_DESK_BUTTON),
cros_tokens::kCrosSysOnPrimary, cros_tokens::kCrosSysPrimary,
DesksController::Get()->CanCreateDesks(),
base::BindRepeating(
&DeskBarViewBase::OnNewDeskButtonPressed, base::Unretained(this),
type_ == Type::kDeskButton
? DesksCreationRemovalSource::kDeskButtonDeskBarButton
: DesksCreationRemovalSource::kButton),
base::BindRepeating(&DeskBarViewBase::InitScrollingIfRequired,
base::Unretained(this))));
new_desk_button_->SetProperty(views::kElementIdentifierKey,
kOverviewDeskBarNewDeskButtonElementId);
contents_view_->SetLayoutManager(
std::make_unique<DeskBarScrollViewLayout>(this));
DesksController::Get()->AddObserver(this);
}
DeskBarViewBase::~DeskBarViewBase() {
TRACE_EVENT0("ui", "DeskBarViewBase::~DeskBarViewBase");
DesksController::Get()->RemoveObserver(this);
if (drag_view_) {
EndDragDesk(drag_view_, false);
}
}
int DeskBarViewBase::GetPreferredBarHeight(aura::Window* root,
Type type,
State state) {
int height = 0;
switch (type) {
case Type::kDeskButton:
CHECK_EQ(State::kExpanded, state);
height =
DeskPreviewView::GetHeight(root) + kDeskBarNonPreviewAllocatedHeight;
break;
case Type::kOverview:
if (state == State::kZero) {
height = kDeskBarZeroStateHeight;
} else {
height = DeskPreviewView::GetHeight(root) + kExpandedDeskBarHeight;
}
break;
}
return height;
}
DeskBarViewBase::State DeskBarViewBase::GetPreferredState(Type type) {
State state = State::kZero;
switch (type) {
case Type::kDeskButton:
state = State::kExpanded;
break;
case Type::kOverview: {
OverviewController* overview_controller =
Shell::Get()->overview_controller();
DesksController* desk_controller = DesksController::Get();
if (desk_controller->GetNumberOfDesks() == 1 &&
overview_controller->InOverviewSession() &&
!overview_controller->overview_session()
->IsShowingSavedDeskLibrary()) {
state = State::kZero;
} else {
state = State::kExpanded;
}
break;
}
}
return state;
}
std::unique_ptr<views::Widget> DeskBarViewBase::CreateDeskWidget(
aura::Window* root,
const gfx::Rect& bounds,
Type type) {
CHECK(root && root->IsRootWindow());
std::unique_ptr<views::Widget> widget = std::make_unique<views::Widget>();
views::Widget::InitParams params(
views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET,
views::Widget::InitParams::TYPE_WINDOW_FRAMELESS);
params.activatable = views::Widget::InitParams::Activatable::kYes;
params.accept_events = true;
params.opacity = views::Widget::InitParams::WindowOpacity::kTranslucent;
params.bounds = bounds;
params.layer_type = ui::LAYER_NOT_DRAWN;
if (type == Type::kOverview) {
params.context = root;
params.name = "OverviewDeskBarWidget";
params.init_properties_container.SetProperty(kOverviewUiKey, true);
params.init_properties_container.SetProperty(kHideInDeskMiniViewKey, true);
} else {
params.parent =
Shell::GetContainer(root, kShellWindowId_ShelfBubbleContainer);
params.name = "DeskButtonDeskBarWidget";
}
widget->Init(std::move(params));
wm::SetWindowVisibilityAnimationTransition(widget->GetNativeWindow(),
wm::ANIMATE_NONE);
return widget;
}
void DeskBarViewBase::Layout(PassKey) {
TRACE_EVENT0("ui", "DeskBarViewBase::Layout");
if (pause_layout_) {
return;
}
if (type_ == Type::kOverview && !overview_grid_) {
return;
}
auto post_layout_operations = std::move(pending_post_layout_operations_);
pending_post_layout_operations_.clear();
for (const auto& post_layout_operation : post_layout_operations) {
post_layout_operation->InitializePreLayout();
}
const gfx::Insets insets = (type_ == Type::kOverview)
? overview_grid_->GetGridInsets()
: gfx::Insets();
CHECK(insets.left() == insets.right());
const int scroll_view_padding =
(type_ == Type::kOverview
? kDeskBarScrollViewMinimumHorizontalPaddingOverview
: kDeskBarScrollViewMinimumHorizontalPaddingDeskButton);
const int horizontal_padding = std::max(scroll_view_padding, insets.left());
if (IsScrollingInitialized()) {
left_scroll_button_->SetBounds(horizontal_padding - scroll_view_padding,
bounds().y(), kDeskBarScrollButtonWidth,
bounds().height());
right_scroll_button_->SetBounds(
bounds().right() - horizontal_padding -
(kDeskBarScrollButtonWidth - scroll_view_padding),
bounds().y(), kDeskBarScrollButtonWidth, bounds().height());
}
gfx::Rect scroll_bounds(size());
scroll_bounds.Inset(gfx::Insets::VH(0, horizontal_padding));
GetTopLevelViewWithContents().SetBoundsRect(scroll_bounds);
if (IsScrollingInitialized()) {
UpdateScrollButtonsVisibility();
UpdateGradientMask();
}
for (const auto& post_layout_operation : post_layout_operations) {
post_layout_operation->Run();
}
}
bool DeskBarViewBase::OnMousePressed(const ui::MouseEvent& event) {
if (desk_activation_timer_.IsRunning()) {
return false;
}
DeskNameView::CommitChanges(GetWidget());
return false;
}
void DeskBarViewBase::OnGestureEvent(ui::GestureEvent* event) {
if (desk_activation_timer_.IsRunning()) {
return;
}
switch (event->type()) {
case ui::EventType::kGestureLongPress:
case ui::EventType::kGestureLongTap:
case ui::EventType::kGestureTap:
case ui::EventType::kGestureTapDown:
DeskNameView::CommitChanges(GetWidget());
break;
default:
break;
}
}
void DeskBarViewBase::Init(aura::Window* desk_bar_widget_window) {
CHECK(desk_bar_widget_window);
Desk::ScopedContentUpdateNotificationDisabler desks_scoped_notify_disabler(
DesksController::Get()->desks(), false);
UpdateNewMiniViews(true,
false);
pending_post_layout_operations_.push_back(
std::make_unique<ScrollForActiveMiniView>(this));
hover_observer_ =
std::make_unique<DeskBarHoverObserver>(this, desk_bar_widget_window);
}
bool DeskBarViewBase::IsZeroState() const {
return state_ == DeskBarViewBase::State::kZero;
}
bool DeskBarViewBase::IsDraggingDesk() const {
return drag_view_ != nullptr;
}
bool DeskBarViewBase::IsDeskNameBeingModified() const {
if (!GetWidget() || !GetWidget()->IsActive()) {
return false;
}
for (ash::DeskMiniView* mini_view : mini_views_) {
if (mini_view->IsDeskNameBeingModified()) {
return true;
}
}
return false;
}
void DeskBarViewBase::ScrollToShowViewIfNecessary(const views::View* view) {
if (!IsScrollingInitialized()) {
return;
}
CHECK(base::Contains(contents_view_->children(), view));
const gfx::Rect visible_bounds = scroll_view_->GetVisibleRect();
const gfx::Rect view_bounds = view->bounds();
const bool beyond_left = view_bounds.x() < visible_bounds.x();
const bool beyond_right = view_bounds.right() > visible_bounds.right();
auto* scroll_bar = scroll_view_->horizontal_scroll_bar();
if (beyond_left) {
scroll_view_->ScrollToPosition(
scroll_bar, view_bounds.right() - scroll_view_->bounds().width());
} else if (beyond_right) {
scroll_view_->ScrollToPosition(scroll_bar, view_bounds.x());
}
}
DeskMiniView* DeskBarViewBase::FindMiniViewForDesk(const Desk* desk) const {
for (ash::DeskMiniView* mini_view : mini_views_) {
if (mini_view->desk() == desk) {
return mini_view;
}
}
return nullptr;
}
int DeskBarViewBase::GetMiniViewIndex(const DeskMiniView* mini_view) const {
auto iter = std::ranges::find(mini_views_, mini_view);
return (iter == mini_views_.cend())
? -1
: std::distance(mini_views_.cbegin(), iter);
}
void DeskBarViewBase::OnNewDeskButtonPressed(
DesksCreationRemovalSource desks_creation_removal_source) {
if (desk_activation_timer_.IsRunning()) {
return;
}
auto* controller = DesksController::Get();
if (!controller->CanCreateDesks()) {
return;
}
base::UmaHistogramBoolean(type_ == Type::kDeskButton
? kDeskButtonDeskBarNewDeskHistogramName
: kOverviewDeskBarNewDeskHistogramName,
true);
pending_post_layout_operations_.push_back(
std::make_unique<NewDeskButtonPressedScroll>(this));
controller->NewDesk(desks_creation_removal_source);
}
void DeskBarViewBase::NudgeDeskName(int desk_index) {
CHECK_LT(desk_index, static_cast<int>(mini_views_.size()));
auto* name_view = mini_views_[desk_index]->desk_name_view();
name_view->RequestFocus();
if (type_ == Type::kOverview) {
if (display::Screen::Get()->InTabletMode() && !HasExternalKeyboard()) {
keyboard::KeyboardUIController::Get()->ShowKeyboard(false);
}
}
}
void DeskBarViewBase::UpdateButtonsForSavedDeskGrid() {
if (IsZeroState() || !saved_desk_util::ShouldShowSavedDesksOptions()) {
return;
}
FindMiniViewForDesk(Shell::Get()->desks_controller()->active_desk())
->UpdateFocusColor();
if (type_ == Type::kOverview && library_button_) {
library_button_->set_paint_as_active(
overview_grid_->IsShowingSavedDeskLibrary());
library_button_->UpdateFocusState();
}
}
void DeskBarViewBase::UpdateDeskButtonsVisibility() {
const bool default_desk_button_new_visibility = IsZeroState();
if (default_desk_button_new_visibility !=
default_desk_button_->GetVisible()) {
default_desk_button_->SetVisible(default_desk_button_new_visibility);
contents_view_->InvalidateLayout();
}
UpdateNewDeskButtonLabelVisibility(
new_desk_button_->state() == DeskIconButton::State::kActive,
GetWidget());
UpdateLibraryButtonVisibility();
}
void DeskBarViewBase::UpdateLibraryButtonVisibility() {
if (!saved_desk_util::ShouldShowSavedDesksOptions()) {
return;
}
const bool should_show_library_button = ShouldShowLibraryUi();
DeskIconButton::State new_library_button_state = DeskIconButton::State::kZero;
if (type_ == Type::kOverview && overview_grid_->IsShowingSavedDeskLibrary()) {
new_library_button_state = DeskIconButton::State::kActive;
} else if (state_ == State::kExpanded) {
new_library_button_state = DeskIconButton::State::kExpanded;
}
if (should_show_library_button) {
GetOrCreateLibraryButton();
}
if (library_button_label_) {
library_button_label_->SetVisible(should_show_library_button &&
library_button_->state() ==
DeskIconButton::State::kActive);
}
if (!library_button_ ||
(library_button_->GetVisible() == ShouldShowLibraryUi() &&
library_button_->state() == new_library_button_state)) {
return;
}
library_button_->SetVisible(should_show_library_button);
if (should_show_library_button) {
library_button_->UpdateState(new_library_button_state);
}
if (mini_views_.empty()) {
return;
}
pending_post_layout_operations_.push_back(
std::make_unique<LibraryButtonVisibilityAnimation>(this));
contents_view_->InvalidateLayout();
}
void DeskBarViewBase::UpdateNewDeskButtonLabelVisibility(
bool new_visibility,
bool layout_if_changed) {
const bool current_visibility =
new_desk_button_label_ && new_desk_button_label_->GetVisible();
if (new_visibility) {
GetOrCreateNewDeskButtonLabel().SetVisible(true);
} else if (new_desk_button_label_) {
new_desk_button_label_->SetVisible(false);
}
if (new_visibility != current_visibility && layout_if_changed) {
contents_view_->InvalidateLayout();
}
}
void DeskBarViewBase::UpdateDeskIconButtonState(
DeskIconButton* button,
DeskIconButton::State target_state) {
CHECK_NE(target_state, DeskIconButton::State::kZero);
if (button->state() == target_state) {
return;
}
button->UpdateState(target_state);
pending_post_layout_operations_.push_back(
std::make_unique<DeskIconButtonScaleAnimation>(this, button));
contents_view_->InvalidateLayout();
}
void DeskBarViewBase::OnHoverStateMayHaveChanged() {
for (ash::DeskMiniView* mini_view : mini_views_) {
mini_view->UpdateDeskButtonVisibility();
}
}
void DeskBarViewBase::OnGestureTap(const gfx::Rect& screen_rect,
bool is_long_gesture) {
if (desk_activation_timer_.IsRunning()) {
return;
}
for (ash::DeskMiniView* mini_view : mini_views_) {
mini_view->OnWidgetGestureTap(screen_rect, is_long_gesture);
}
}
bool DeskBarViewBase::ShouldShowLibraryUi() {
if (library_ui_visibility_ == LibraryUiVisibility::kToBeChecked) {
if (!saved_desk_util::ShouldShowSavedDesksOptions() ||
display::Screen::Get()->InTabletMode()) {
library_ui_visibility_ = LibraryUiVisibility::kHidden;
} else {
auto* desk_model = Shell::Get()->saved_desk_delegate()->GetDeskModel();
CHECK(desk_model);
size_t saved_desk_count = desk_model->GetDeskTemplateEntryCount() +
desk_model->GetSaveAndRecallDeskEntryCount() +
desk_model->GetCoralEntryCount();
library_ui_visibility_ = saved_desk_count ? LibraryUiVisibility::kVisible
: LibraryUiVisibility::kHidden;
}
}
return library_ui_visibility_ == LibraryUiVisibility::kVisible;
}
void DeskBarViewBase::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 (ash::DeskMiniView* mini_view : mini_views_) {
mini_view->UpdateFocusColor();
}
if (DesksController::Get()->CanCreateDesks()) {
new_desk_button_->UpdateFocusState();
}
}
void DeskBarViewBase::HandlePressEvent(DeskMiniView* mini_view,
const ui::LocatedEvent& event) {
if (mini_view->is_animating_to_remove()) {
return;
}
DeskNameView::CommitChanges(GetWidget());
if (ui::EventTarget* target = event.target()) {
gfx::PointF location = target->GetScreenLocationF(event);
InitDragDesk(mini_view, location);
}
}
void DeskBarViewBase::HandleLongPressEvent(DeskMiniView* mini_view,
const ui::LocatedEvent& event) {
if (mini_view->is_animating_to_remove()) {
return;
}
DeskNameView::CommitChanges(GetWidget());
gfx::PointF location = event.target()->GetScreenLocationF(event);
InitDragDesk(mini_view, location);
StartDragDesk(mini_view, location, event.IsMouseEvent());
mini_view->OpenContextMenu(ui::mojom::MenuSourceType::kLongPress);
}
void DeskBarViewBase::HandleDragEvent(DeskMiniView* mini_view,
const ui::LocatedEvent& event) {
if (!drag_proxy_ || mini_view->is_animating_to_remove()) {
return;
}
mini_view->MaybeCloseContextMenu();
gfx::PointF location = event.target()->GetScreenLocationF(event);
switch (drag_proxy_->state()) {
case DeskDragProxy::State::kInitialized:
StartDragDesk(mini_view, location, event.IsMouseEvent());
break;
case DeskDragProxy::State::kStarted:
ContinueDragDesk(mini_view, location);
break;
default:
DUMP_WILL_BE_NOTREACHED();
}
}
bool DeskBarViewBase::HandleReleaseEvent(DeskMiniView* mini_view,
const ui::LocatedEvent& event) {
if (!drag_proxy_ || mini_view->is_animating_to_remove()) {
return false;
}
switch (drag_proxy_->state()) {
case DeskDragProxy::State::kInitialized:
FinalizeDragDesk();
return false;
case DeskDragProxy::State::kStarted:
EndDragDesk(drag_view_, true);
break;
default:
NOTREACHED();
}
return true;
}
void DeskBarViewBase::OnActivateDeskTimer(const base::Uuid& uuid) {
OnUiUpdateDone();
auto* desk_controller = DesksController::Get();
if (Desk* desk = desk_controller->GetDeskByUuid(uuid)) {
desk_controller->ActivateDesk(
desk, type_ == Type::kDeskButton
? DesksSwitchSource::kDeskButtonMiniViewButton
: DesksSwitchSource::kMiniViewButton);
}
}
void DeskBarViewBase::HandleClickEvent(DeskMiniView* mini_view) {
if (!gfx::ScopedAnimationDurationScaleMode::is_zero()) {
desk_activation_timer_.Start(
FROM_HERE,
gfx::ScopedAnimationDurationScaleMode::duration_multiplier() *
kAnimationDelayDuration,
base::BindOnce(&DeskBarViewBase::OnActivateDeskTimer,
base::Unretained(this), mini_view->desk()->uuid()));
} else {
OnActivateDeskTimer(mini_view->desk()->uuid());
}
}
void DeskBarViewBase::InitDragDesk(DeskMiniView* mini_view,
const gfx::PointF& location_in_screen) {
CHECK(!mini_view->is_animating_to_remove());
if (drag_view_) {
EndDragDesk(drag_view_, false);
}
drag_view_ = mini_view;
gfx::PointF preview_origin_in_screen(
drag_view_->GetPreviewBoundsInScreen().origin());
const float init_offset_x =
location_in_screen.x() - preview_origin_in_screen.x();
drag_proxy_ = std::make_unique<DeskDragProxy>(this, drag_view_, init_offset_x,
window_occlusion_calculator_);
}
void DeskBarViewBase::StartDragDesk(DeskMiniView* mini_view,
const gfx::PointF& location_in_screen,
bool is_mouse_dragging) {
CHECK(drag_view_);
CHECK(drag_proxy_);
CHECK_EQ(mini_view, drag_view_);
CHECK(!mini_view->is_animating_to_remove());
drag_view_->layer()->SetOpacity(0.0f);
drag_proxy_->InitAndScaleAndMoveToX(location_in_screen.x());
Shell::Get()->cursor_manager()->SetCursor(ui::mojom::CursorType::kGrabbing);
if (is_mouse_dragging) {
chromeos::haptics_util::PlayHapticTouchpadEffect(
ui::HapticTouchpadEffect::kTick,
ui::HapticTouchpadEffectStrength::kMedium);
}
}
void DeskBarViewBase::ContinueDragDesk(DeskMiniView* mini_view,
const gfx::PointF& location_in_screen) {
CHECK(drag_view_);
CHECK(drag_proxy_);
CHECK_EQ(mini_view, drag_view_);
CHECK(!mini_view->is_animating_to_remove());
drag_proxy_->DragToX(location_in_screen.x());
if (MaybeScrollByDraggedDesk()) {
return;
}
const auto drag_view_iter = std::ranges::find(mini_views_, drag_view_);
CHECK(drag_view_iter != mini_views_.cend());
const int old_index = drag_view_iter - mini_views_.cbegin();
const int drag_pos_screen_x = drag_proxy_->GetBoundsInScreen().origin().x();
const int new_index = DetermineMoveIndex(drag_pos_screen_x);
if (old_index != new_index) {
Shell::Get()->desks_controller()->ReorderDesk(old_index, new_index);
}
}
void DeskBarViewBase::EndDragDesk(DeskMiniView* mini_view, bool end_by_user) {
CHECK(drag_view_);
CHECK(drag_proxy_);
CHECK_EQ(mini_view, drag_view_);
CHECK(!mini_view->is_animating_to_remove());
base::UmaHistogramBoolean(type_ == Type::kDeskButton
? kDeskButtonDeskBarReorderDeskHistogramName
: kOverviewDeskBarReorderDeskHistogramName,
true);
Shell::Get()->desks_controller()->UpdateDesksDefaultNames();
Shell::Get()->cursor_manager()->SetCursor(ui::mojom::CursorType::kPointer);
MaybeUpdateDeskActionButtonTooltips();
if (IsScrollingInitialized()) {
left_scroll_button_->OnDeskHoverEnd();
right_scroll_button_->OnDeskHoverEnd();
}
if (end_by_user) {
ScrollToShowViewIfNecessary(drag_view_);
drag_proxy_->SnapBackToDragView();
} else {
FinalizeDragDesk();
}
}
void DeskBarViewBase::FinalizeDragDesk() {
if (drag_view_) {
drag_view_->layer()->SetOpacity(1.0f);
drag_view_ = nullptr;
}
drag_proxy_.reset();
}
void DeskBarViewBase::OnDeskAdded(const Desk* desk, bool from_undo) {
DeskNameView::CommitChanges(GetWidget());
const bool is_expanding_bar_view =
overview_grid() &&
new_desk_button_->state() == DeskIconButton::State::kZero;
UpdateNewMiniViews(false, is_expanding_bar_view);
MaybeUpdateDeskActionButtonTooltips();
if (!DesksController::Get()->CanCreateDesks()) {
new_desk_button_->SetEnabled(false);
}
}
void DeskBarViewBase::OnDeskRemoved(const Desk* desk) {
DeskNameView::CommitChanges(GetWidget());
auto iter = std::ranges::find_if(
mini_views_,
[desk](DeskMiniView* mini_view) { return mini_view->desk() == desk; });
if (iter == mini_views_.end()) {
return;
}
new_desk_button_->SetEnabled(true);
for (DeskMiniView* mini_view : mini_views_) {
mini_view->UpdateDeskButtonVisibility();
}
DeskMiniView* removed_mini_view = *iter;
mini_views_.erase(iter);
if (drag_view_ == removed_mini_view) {
EndDragDesk(removed_mini_view, false);
}
pending_post_layout_operations_.push_back(
std::make_unique<RemoveDeskAnimation>(this, removed_mini_view));
if (type_ == Type::kOverview) {
contents_view_->InvalidateLayout();
} else {
removed_mini_view->parent()->RemoveChildViewT(removed_mini_view);
contents_view_->InvalidateLayout();
}
}
void DeskBarViewBase::OnDeskReordered(int old_index, int new_index) {
desks_util::ReorderItem(mini_views_, old_index, new_index);
auto* reordered_view = mini_views_[new_index].get();
reordered_view->parent()->ReorderChildView(reordered_view, new_index);
reordered_view->parent()->NotifyAccessibilityEventDeprecated(
ax::mojom::Event::kTreeChanged, true);
reordered_view->UpdateDeskButtonVisibility();
mini_views_[old_index]->UpdateDeskButtonVisibility();
pending_post_layout_operations_.push_back(
std::make_unique<ReorderDeskAnimation>(this, old_index, new_index));
contents_view_->InvalidateLayout();
}
void DeskBarViewBase::OnDeskActivationChanged(const Desk* activated,
const Desk* deactivated) {
for (ash::DeskMiniView* mini_view : mini_views_) {
const Desk* desk = mini_view->desk();
if (desk == activated || desk == deactivated) {
mini_view->UpdateFocusColor();
}
}
}
void DeskBarViewBase::OnDeskNameChanged(const Desk* desk,
const std::u16string& new_name) {
MaybeUpdateDeskActionButtonTooltips();
}
void DeskBarViewBase::UpdateNewMiniViews(bool initializing_bar_view,
bool expanding_bar_view) {
TRACE_EVENT0("ui", "DeskBarViewBase::UpdateNewMiniViews");
const absl::Cleanup scrolling_check = [this] { InitScrollingIfRequired(); };
const auto& desks = DesksController::Get()->desks();
if (initializing_bar_view) {
UpdateDeskButtonsVisibility();
}
if (IsZeroState() && !expanding_bar_view) {
return;
}
DCHECK_LE(mini_views_.size(), desks.size());
int mini_view_index = 0;
std::vector<DeskMiniView*> new_mini_views;
for (const auto& desk : desks) {
if (!FindMiniViewForDesk(desk.get())) {
DeskMiniView* mini_view = contents_view_->AddChildViewAt(
std::make_unique<DeskMiniView>(this, root_, desk.get(),
window_occlusion_calculator_),
mini_view_index);
mini_views_.insert(mini_views_.begin() + mini_view_index, mini_view);
new_mini_views.push_back(mini_view);
}
++mini_view_index;
}
if (expanding_bar_view) {
SwitchToExpandedState();
return;
}
if (new_desk_button_->state() == DeskIconButton::State::kActive) {
new_desk_button_->UpdateState(DeskIconButton::State::kExpanded);
}
const gfx::Rect old_bar_bounds = this->GetBoundsInScreen();
pause_layout_ = true;
UpdateBarBounds();
pause_layout_ = false;
if (!initializing_bar_view) {
pending_post_layout_operations_.push_back(
std::make_unique<AddDeskAnimation>(this, old_bar_bounds,
std::move(new_mini_views)));
}
contents_view_->InvalidateLayout();
}
void DeskBarViewBase::SwitchToExpandedState() {
state_ = DeskBarViewBase::State::kExpanded;
UpdateDeskButtonsVisibility();
PerformZeroStateToExpandedStateMiniViewAnimation(this);
MaybeRefreshOverviewGridBounds();
}
void DeskBarViewBase::OnUiUpdateDone() {
if (on_update_ui_closure_for_testing_) {
std::move(on_update_ui_closure_for_testing_).Run();
}
}
DeskIconButton& DeskBarViewBase::GetOrCreateLibraryButton() {
if (library_button_) {
return *library_button_;
}
const int button_text_id =
saved_desk_util::AreDesksTemplatesEnabled()
? IDS_ASH_DESKS_TEMPLATES_DESKS_BAR_BUTTON_LIBRARY
: IDS_ASH_DESKS_TEMPLATES_DESKS_BAR_BUTTON_SAVED_FOR_LATER;
CHECK(contents_view_);
library_button_ =
contents_view_->AddChildView(std::make_unique<DeskIconButton>(
this, &kDesksTemplatesIcon, l10n_util::GetStringUTF16(button_text_id),
cros_tokens::kCrosSysOnSecondaryContainer,
cros_tokens::kCrosSysInversePrimary,
true,
base::BindRepeating(&DeskBarViewBase::OnLibraryButtonPressed,
base::Unretained(this)),
base::BindRepeating(&DeskBarViewBase::InitScrollingIfRequired,
base::Unretained(this))));
library_button_label_ =
contents_view_->AddChildView(std::make_unique<views::Label>());
library_button_label_->SetFontList(
TypographyProvider::Get()->ResolveTypographyToken(
TypographyToken::kCrosAnnotation1));
library_button_label_->SetPaintToLayer();
library_button_label_->layer()->SetFillsBoundsOpaquely(false);
return *library_button_;
}
views::Label& DeskBarViewBase::GetOrCreateNewDeskButtonLabel() {
if (new_desk_button_label_) {
return *new_desk_button_label_;
}
new_desk_button_label_ =
contents_view_->AddChildView(std::make_unique<views::Label>());
new_desk_button_label_->SetPaintToLayer();
new_desk_button_label_->layer()->SetFillsBoundsOpaquely(false);
return *new_desk_button_label_;
}
void DeskBarViewBase::UpdateBarBounds() {}
int DeskBarViewBase::GetFirstMiniViewXOffset() const {
return mini_views_.empty() ? bounds().CenterPoint().x()
: mini_views_[0]->GetMirroredX();
}
base::flat_map<views::View*, int>
DeskBarViewBase::GetAnimatableViewsCurrentXMap() const {
base::flat_map<views::View*, int> result;
auto insert_view = [&](views::View* view) {
if (view) {
result.emplace(view, view->GetBoundsInScreen().x());
}
};
for (ash::DeskMiniView* mini_view : mini_views_) {
insert_view(mini_view);
}
insert_view(new_desk_button_);
insert_view(library_button_);
return result;
}
int DeskBarViewBase::DetermineMoveIndex(int location_screen_x) const {
const int views_size = static_cast<int>(mini_views_.size());
for (int new_index = 0; new_index != views_size - 1; ++new_index) {
auto* mini_view = mini_views_[new_index].get();
gfx::Point center_screen_pos = mini_view->GetMirroredBounds().CenterPoint();
views::View::ConvertPointToScreen(mini_view->parent(), ¢er_screen_pos);
if (location_screen_x < center_screen_pos.x()) {
return new_index;
}
}
return views_size - 1;
}
void DeskBarViewBase::UpdateScrollButtonsVisibility() {
CHECK(IsScrollingInitialized());
const gfx::Rect visible_bounds = scroll_view_->GetVisibleRect();
left_scroll_button_->SetVisible(width() == GetAvailableBounds().width() &&
visible_bounds.x() > 0);
right_scroll_button_->SetVisible(width() == GetAvailableBounds().width() &&
visible_bounds.right() <
contents_view_->bounds().width());
}
void DeskBarViewBase::UpdateGradientMask() {
CHECK(IsScrollingInitialized());
const bool is_rtl = base::i18n::IsRTL();
const bool is_left_scroll_button_visible = left_scroll_button_->GetVisible();
const bool is_right_scroll_button_visible =
right_scroll_button_->GetVisible();
const bool is_left_visible_only =
is_left_scroll_button_visible && !is_right_scroll_button_visible;
bool should_show_start_gradient = false;
bool should_show_end_gradient = false;
if (scroll_view_->is_scrolling()) {
should_show_start_gradient =
is_rtl ? is_right_scroll_button_visible : is_left_scroll_button_visible;
should_show_end_gradient =
is_rtl ? is_left_scroll_button_visible : is_right_scroll_button_visible;
} else {
should_show_start_gradient =
is_rtl ? is_right_scroll_button_visible : is_left_visible_only;
should_show_end_gradient =
is_rtl ? is_left_visible_only : is_right_scroll_button_visible;
}
gfx::LinearGradient gradient_mask(0);
float fade_position = should_show_start_gradient || should_show_end_gradient
? static_cast<float>(kDeskBarGradientZoneLength) /
scroll_view_->bounds().width()
: 0;
fade_position = std::clamp(fade_position, 0.0f, 1.0f);
if (should_show_start_gradient) {
gradient_mask.AddStep(0, 0);
gradient_mask.AddStep(fade_position, 255);
}
if (should_show_end_gradient) {
gradient_mask.AddStep((1 - fade_position), 255);
gradient_mask.AddStep(1, 0);
}
scroll_view_->layer()->SetGradientMask(gradient_mask);
scroll_view_->SchedulePaint();
}
void DeskBarViewBase::ScrollToPreviousPage() {
CHECK(IsScrollingInitialized());
ui::ScopedLayerAnimationSettings settings(
contents_view_->layer()->GetAnimator());
InitScrollContentsAnimationSettings(settings);
scroll_view_->ScrollToPosition(
scroll_view_->horizontal_scroll_bar(),
GetAdjustedUncroppedScrollPosition(scroll_view_->GetVisibleRect().x() -
scroll_view_->width()));
}
void DeskBarViewBase::ScrollToNextPage() {
CHECK(IsScrollingInitialized());
ui::ScopedLayerAnimationSettings settings(
contents_view_->layer()->GetAnimator());
InitScrollContentsAnimationSettings(settings);
scroll_view_->ScrollToPosition(
scroll_view_->horizontal_scroll_bar(),
GetAdjustedUncroppedScrollPosition(scroll_view_->GetVisibleRect().x() +
scroll_view_->width()));
}
int DeskBarViewBase::GetAdjustedUncroppedScrollPosition(int position) const {
CHECK(IsScrollingInitialized());
if (position <= 0 ||
position >= contents_view_->bounds().width() - scroll_view_->width()) {
return position;
}
int adjusted_position = position;
int i = 0;
gfx::Rect mini_view_bounds;
const int mini_views_size = static_cast<int>(mini_views_.size());
for (; i < mini_views_size; i++) {
mini_view_bounds = mini_views_[i]->bounds();
if (mini_view_bounds.x() >= position) {
return position - kDeskBarDeskPreviewViewFocusRingThicknessAndPadding;
}
if (mini_view_bounds.x() < position &&
mini_view_bounds.right() > position) {
break;
}
}
CHECK_LT(i, mini_views_size);
if ((position - mini_view_bounds.x()) < mini_view_bounds.width() / 2) {
adjusted_position = mini_view_bounds.x();
} else {
adjusted_position = mini_view_bounds.right();
if (i + 1 < mini_views_size) {
adjusted_position = mini_views_[i + 1]->bounds().x();
}
}
return adjusted_position -
kDeskBarDeskPreviewViewFocusRingThicknessAndPadding;
}
void DeskBarViewBase::OnLibraryButtonPressed() {
if (desk_activation_timer_.IsRunning()) {
return;
}
RecordLoadSavedDeskLibraryHistogram();
base::UmaHistogramBoolean(type_ == Type::kDeskButton
? kDeskButtonDeskBarOpenLibraryHistogramName
: kOverviewDeskBarOpenLibraryHistogramName,
true);
if (IsDeskNameBeingModified()) {
DeskNameView::CommitChanges(GetWidget());
}
aura::Window* root = GetWidget()->GetNativeWindow()->GetRootWindow();
OverviewSession* overview_session;
if (overview_grid_) {
overview_session = overview_grid_->overview_session();
} else {
OverviewController* overview_controller =
Shell::Get()->overview_controller();
bool is_overview_started =
overview_controller &&
overview_controller->StartOverview(OverviewStartAction::kDeskButton);
if (!is_overview_started) {
return;
}
overview_session = overview_controller->overview_session();
}
overview_session->ShowSavedDeskLibrary(base::Uuid(), u"",
root);
}
void DeskBarViewBase::MaybeUpdateDeskActionButtonTooltips() {
auto* desk_controller = DesksController::Get();
for (ash::DeskMiniView* mini_view : mini_views_) {
auto* desk = mini_view->desk();
if (desk->is_desk_being_removed()) {
continue;
}
int desk_index = desk_controller->GetDeskIndex(desk);
auto* desk_action_view = mini_view->desk_action_view();
const std::u16string close_desk_tooltip =
desk->name().empty() && desk_index != -1
? desk_controller->GetDeskDefaultName(desk_index)
: desk->name();
desk_action_view->close_all_button()->UpdateTooltip(close_desk_tooltip);
}
}
void DeskBarViewBase::InitScrollingIfRequired() {
if (!scroll_view_ && IsScrollingRequired()) {
InitScrolling();
}
}
void DeskBarViewBase::InitScrolling() {
CHECK(!scroll_view_);
std::unique_ptr<views::View> scroll_view_contents =
contents_view_ ? RemoveChildViewT(contents_view_)
: std::make_unique<views::View>();
scroll_view_ = AddChildView(std::make_unique<views::ScrollView>(
views::ScrollView::ScrollWithLayers::kEnabled));
scroll_view_->SetPaintToLayer(ui::LAYER_NOT_DRAWN);
scroll_view_->layer()->SetMasksToBounds(true);
scroll_view_->SetBackgroundColor(std::nullopt);
scroll_view_->SetDrawOverflowIndicator(false);
scroll_view_->SetHorizontalScrollBarMode(
views::ScrollView::ScrollBarMode::kHiddenButEnabled);
scroll_view_->SetTreatAllScrollEventsAsHorizontal(true);
scroll_view_->SetAllowKeyboardScrolling(false);
left_scroll_button_ = AddChildView(std::make_unique<ScrollArrowButton>(
base::BindRepeating(&DeskBarViewBase::ScrollToPreviousPage,
base::Unretained(this)),
true, this));
left_scroll_button_->RemoveFromFocusList();
right_scroll_button_ = AddChildView(std::make_unique<ScrollArrowButton>(
base::BindRepeating(&DeskBarViewBase::ScrollToNextPage,
base::Unretained(this)),
false, this));
right_scroll_button_->RemoveFromFocusList();
if (type_ == Type::kDeskButton) {
left_scroll_button_->SetPaintToLayer();
left_scroll_button_->layer()->SetFillsBoundsOpaquely(false);
right_scroll_button_->SetPaintToLayer();
right_scroll_button_->layer()->SetFillsBoundsOpaquely(false);
}
contents_view_ = scroll_view_->SetContents(std::move(scroll_view_contents));
CHECK(contents_view_->layer());
on_contents_scrolled_subscription_ =
scroll_view_->AddContentsScrolledCallback(base::BindRepeating(
&DeskBarViewBase::OnContentsScrolled, base::Unretained(this)));
on_contents_scroll_ended_subscription_ =
scroll_view_->AddContentsScrollEndedCallback(base::BindRepeating(
&DeskBarViewBase::OnContentsScrollEnded, base::Unretained(this)));
if (GetWidget()) {
contents_view_->InvalidateLayout();
}
}
bool DeskBarViewBase::IsScrollingRequired() const {
CHECK(contents_view_);
const int current_desk_bar_width = contents_view_->GetPreferredSize().width();
const int available_width_for_desk_bar = GetAvailableBounds().width();
const int scrolling_threshold = available_width_for_desk_bar * 0.75f;
return current_desk_bar_width >= scrolling_threshold;
}
bool DeskBarViewBase::IsScrollingInitialized() const {
return !!scroll_view_;
}
views::View& DeskBarViewBase::GetTopLevelViewWithContents() {
CHECK(contents_view_);
return scroll_view_ ? *scroll_view_ : *contents_view_;
}
void DeskBarViewBase::OnContentsScrolled() {
UpdateScrollButtonsVisibility();
UpdateGradientMask();
}
void DeskBarViewBase::OnContentsScrollEnded() {
CHECK(IsScrollingInitialized());
const gfx::Rect visible_bounds = scroll_view_->GetVisibleRect();
const int current_position = visible_bounds.x();
const int adjusted_position =
GetAdjustedUncroppedScrollPosition(current_position);
if (current_position != adjusted_position) {
scroll_view_->ScrollToPosition(scroll_view_->horizontal_scroll_bar(),
adjusted_position);
}
UpdateGradientMask();
}
bool DeskBarViewBase::MaybeScrollByDraggedDesk() {
CHECK(drag_proxy_);
const gfx::Rect proxy_bounds = drag_proxy_->GetBoundsInScreen();
for (ScrollArrowButton* scroll_button : {
left_scroll_button_,
right_scroll_button_,
}) {
if (!IsScrollingInitialized()) {
continue;
}
if (scroll_button->GetVisible() &&
proxy_bounds.Intersects(scroll_button->GetBoundsInScreen())) {
scroll_button->OnDeskHoverStart();
return true;
}
scroll_button->OnDeskHoverEnd();
}
return false;
}
void DeskBarViewBase::MaybeRefreshOverviewGridBounds() {
if (type_ == DeskBarViewBase::Type::kOverview &&
overview_grid_->scoped_overview_wallpaper_clipper()) {
CHECK(overview_grid_);
overview_grid_->RefreshGridBounds(true);
}
}
BEGIN_METADATA(DeskBarViewBase)
END_METADATA
}