#include "ash/shelf/shelf_layout_manager.h"
#include <algorithm>
#include <cmath>
#include <vector>
#include "ash/accessibility/accessibility_controller.h"
#include "ash/animation/animation_change_type.h"
#include "ash/app_list/app_list_controller_impl.h"
#include "ash/app_list/app_list_metrics.h"
#include "ash/app_list/views/app_list_view.h"
#include "ash/constants/ash_features.h"
#include "ash/constants/ash_pref_names.h"
#include "ash/constants/ash_switches.h"
#include "ash/controls/contextual_tooltip.h"
#include "ash/display/screen_orientation_controller.h"
#include "ash/public/cpp/app_list/app_list_features.h"
#include "ash/public/cpp/app_list/app_list_types.h"
#include "ash/public/cpp/shelf_config.h"
#include "ash/public/cpp/shelf_types.h"
#include "ash/public/cpp/shell_window_ids.h"
#include "ash/root_window_controller.h"
#include "ash/screen_util.h"
#include "ash/session/session_controller_impl.h"
#include "ash/shelf/desk_button_widget.h"
#include "ash/shelf/drag_handle.h"
#include "ash/shelf/home_to_overview_nudge_controller.h"
#include "ash/shelf/hotseat_widget.h"
#include "ash/shelf/in_app_to_home_nudge_controller.h"
#include "ash/shelf/login_shelf_widget.h"
#include "ash/shelf/scrollable_shelf_view.h"
#include "ash/shelf/shelf.h"
#include "ash/shelf/shelf_layout_manager_observer.h"
#include "ash/shelf/shelf_metrics.h"
#include "ash/shelf/shelf_navigation_widget.h"
#include "ash/shelf/shelf_view.h"
#include "ash/shelf/shelf_widget.h"
#include "ash/shelf/swipe_home_to_overview_controller.h"
#include "ash/shell.h"
#include "ash/system/locale/locale_update_controller_impl.h"
#include "ash/system/status_area_widget.h"
#include "ash/wallpaper/wallpaper_controller_impl.h"
#include "ash/wm/float/float_controller.h"
#include "ash/wm/fullscreen_window_finder.h"
#include "ash/wm/lock_state_controller.h"
#include "ash/wm/mru_window_tracker.h"
#include "ash/wm/overview/overview_controller.h"
#include "ash/wm/overview/overview_utils.h"
#include "ash/wm/screen_pinning_controller.h"
#include "ash/wm/snap_group/snap_group_controller.h"
#include "ash/wm/splitview/split_view_controller.h"
#include "ash/wm/window_pin_util.h"
#include "ash/wm/window_state.h"
#include "ash/wm/window_util.h"
#include "ash/wm/work_area_insets.h"
#include "ash/wm/workspace/workspace_types.h"
#include "ash/wm/workspace_controller.h"
#include "base/auto_reset.h"
#include "base/command_line.h"
#include "base/functional/bind.h"
#include "base/i18n/rtl.h"
#include "base/logging.h"
#include "base/memory/raw_ptr.h"
#include "base/metrics/histogram_macros.h"
#include "base/task/single_thread_task_runner.h"
#include "chromeos/ash/components/growth/campaigns_constants.h"
#include "chromeos/ash/components/growth/campaigns_manager.h"
#include "chromeos/constants/chromeos_features.h"
#include "chromeos/ui/base/window_pin_type.h"
#include "components/prefs/pref_service.h"
#include "ui/aura/client/drag_drop_client.h"
#include "ui/aura/client/screen_position_client.h"
#include "ui/base/hit_test.h"
#include "ui/base/ui_base_switches.h"
#include "ui/compositor/animation_throughput_reporter.h"
#include "ui/compositor/layer.h"
#include "ui/compositor/layer_animation_observer.h"
#include "ui/compositor/layer_animator.h"
#include "ui/compositor/presentation_time_recorder.h"
#include "ui/compositor/scoped_layer_animation_settings.h"
#include "ui/display/display.h"
#include "ui/display/screen.h"
#include "ui/events/android/gesture_event_type.h"
#include "ui/events/event.h"
#include "ui/events/event_handler.h"
#include "ui/events/gesture_event_details.h"
#include "ui/events/types/event_type.h"
#include "ui/gfx/geometry/insets.h"
#include "ui/views/border.h"
#include "ui/views/widget/widget.h"
#include "ui/wm/core/coordinate_conversion.h"
#include "ui/wm/public/activation_client.h"
namespace ash {
namespace {
constexpr float kFloatingShelfDimOpacity = 0.74f;
constexpr float kMaximizedShelfDimOpacity = 0.6f;
constexpr float kDefaultShelfOpacity = 1.0f;
constexpr int kAutoHideDelayMS = 200;
constexpr int kNotificationBubbleGapHeight = 6;
constexpr int kMaxAutoHideShowShelfRegionSize = 10;
constexpr int kShelfPalmRejectionSwipeOffset = 80;
constexpr int kQuickShowMinAllowDistance = 100;
const constexpr char* const kStylusAppIds[] = {
"fhapgmpiiiigioilnjmkiohjhlegnceb",
"apignacaigpffemhdbhmnajajaccbckh",
"ieailfmhaghpphfffooibmlghaeopach",
"eilembjdkfgodjkcjnpgpaenohkicgjd",
"ifeodkfobgahmoofeclbhkdacaaopkek",
"gjcfgmjegppjhimhlldbhhkfgkdjngcc",
"afihfgfghkmdmggakhkgnfhlikhdpima",
"ffhnkanmhmmnfebldhpffiopadhbeimp"
};
constexpr int kShelfFlingVelocityThresehold = 100;
aura::client::DragDropClient* GetDragDropClient(ShelfWidget* shelf_widget) {
if (shelf_widget) {
if (aura::Window* window = shelf_widget->GetNativeWindow()) {
if (aura::Window* root_window = window->GetRootWindow())
return aura::client::GetDragDropClient(root_window);
}
}
return nullptr;
}
aura::Window* GetDragHandleNudgeWindow(ShelfWidget* shelf_widget) {
if (!shelf_widget->GetDragHandle())
return nullptr;
if (!shelf_widget->GetDragHandle()->drag_handle_nudge())
return nullptr;
return shelf_widget->GetDragHandle()
->drag_handle_nudge()
->GetWidget()
->GetNativeWindow();
}
ui::Layer* GetLayer(views::Widget* widget) {
return widget->GetNativeView()->layer();
}
void SetupAnimator(ui::ScopedLayerAnimationSettings* animation_setter,
base::TimeDelta animation_duration,
gfx::Tween::Type type) {
animation_setter->SetTransitionDuration(animation_duration);
if (!animation_duration.is_zero()) {
animation_setter->SetTweenType(type);
animation_setter->SetPreemptionStrategy(
ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
}
}
void AnimateOpacity(ui::Layer* layer,
float target_opacity,
base::TimeDelta animation_duration,
gfx::Tween::Type type) {
ui::ScopedLayerAnimationSettings animation_setter(layer->GetAnimator());
SetupAnimator(&animation_setter, animation_duration, type);
layer->SetOpacity(target_opacity);
}
bool IsAppListWindow(const aura::Window* window) {
const aura::Window* parent = window->parent();
return parent && parent->GetId() == kShellWindowId_AppListContainer;
}
WorkspaceWindowState GetShelfWorkspaceWindowState(aura::Window* shelf_window) {
DCHECK(shelf_window);
auto* controller =
GetActiveWorkspaceController(shelf_window->GetRootWindow());
DCHECK(controller);
return controller->GetWindowState();
}
int GetShelfInset(ShelfVisibilityState visibility_state, int size) {
return visibility_state == SHELF_VISIBLE ? size : 0;
}
gfx::Insets CalculateShelfInsets(ShelfAlignment alignment,
ShelfVisibilityState visibility_state) {
const int default_shelf_inset =
GetShelfInset(visibility_state, ShelfConfig::Get()->shelf_size());
const int horizontal_inset =
display::Screen::Get()->InTabletMode()
? GetShelfInset(visibility_state,
ShelfConfig::Get()->in_app_shelf_size())
: default_shelf_inset;
return SelectValueByShelfAlignment(
alignment, gfx::Insets::TLBR(0, 0, horizontal_inset, 0),
gfx::Insets::TLBR(0, default_shelf_inset, 0, 0),
gfx::Insets::TLBR(0, 0, 0, default_shelf_inset));
}
int GetOffset(int offset, const char* pref_name) {
PrefService* prefs =
Shell::Get()->session_controller()->GetLastActiveUserPrefService();
return prefs->GetBoolean(pref_name) ? -offset : offset;
}
int GetScrollOffsetInAllowedDirection(int offset, bool app_list_visible) {
return app_list_visible ? -offset : offset;
}
bool CanDragFloatedWindowFromShelf(aura::Window* floated_window,
const gfx::Point& location_in_screen) {
if (!Shell::Get()->float_controller()->IsFloatedWindowAlignedWithShelf(
floated_window)) {
return false;
}
DCHECK(floated_window->IsVisible());
const gfx::Rect floated_window_bounds = floated_window->GetBoundsInScreen();
return location_in_screen.x() <= floated_window_bounds.right() &&
location_in_screen.x() >= floated_window_bounds.x();
}
bool IsDragOverShelfWithFloatedWindow(const gfx::Point& location_in_screen) {
aura::Window* top_window = window_util::GetTopNonFloatedWindow();
if (top_window && top_window->IsVisible()) {
return false;
}
aura::Window* floated_window = window_util::GetFloatedWindowForActiveDesk();
return floated_window &&
!CanDragFloatedWindowFromShelf(floated_window, location_in_screen);
}
aura::Window* GetWindowForDragToHomeOrOverview(
const gfx::Point& location_in_screen) {
if (!display::Screen::Get()->InTabletMode()) {
return nullptr;
}
auto mru_windows =
Shell::Get()->mru_window_tracker()->BuildWindowForCycleList(kActiveDesk);
if (mru_windows.empty()) {
return nullptr;
}
SplitViewController* split_view_controller =
SplitViewController::Get(Shell::GetPrimaryRootWindow());
const bool is_in_splitview = split_view_controller->InSplitViewMode();
if (!is_in_splitview &&
Shell::Get()->overview_controller()->InOverviewSession()) {
return nullptr;
}
if (aura::Window* floated_window =
window_util::GetFloatedWindowForActiveDesk();
floated_window &&
CanDragFloatedWindowFromShelf(floated_window, location_in_screen)) {
return floated_window;
}
if (!is_in_splitview) {
aura::Window* window = window_util::GetTopNonFloatedWindow();
return window && window->IsVisible() ? window : nullptr;
}
aura::Window* window = nullptr;
aura::Window* left_window = split_view_controller->primary_window();
aura::Window* right_window = split_view_controller->secondary_window();
const int divider_position = split_view_controller->GetDividerPosition();
const bool is_landscape = IsCurrentScreenOrientationLandscape();
const bool is_primary = IsCurrentScreenOrientationPrimary();
const gfx::Rect work_area =
screen_util::GetDisplayWorkAreaBoundsInScreenForActiveDeskContainer(
split_view_controller->GetDefaultSnappedWindow());
if (is_landscape) {
if (location_in_screen.x() < work_area.x() + divider_position) {
window = is_primary ? left_window : right_window;
} else {
window = is_primary ? right_window : left_window;
}
} else {
window = is_primary ? right_window : left_window;
}
return window && window->IsVisible() ? window : nullptr;
}
std::optional<InAppShelfGestures> CalculateHotseatGestureToRecord(
std::optional<ShelfWindowDragResult> window_drag_result,
bool transitioned_from_overview_to_home,
HotseatState old_state,
HotseatState current_state) {
if (window_drag_result.has_value() &&
(window_drag_result == ShelfWindowDragResult::kGoToOverviewMode ||
window_drag_result == ShelfWindowDragResult::kGoToSplitviewMode) &&
old_state == HotseatState::kHidden) {
return InAppShelfGestures::kSwipeUpToShow;
}
if (window_drag_result.has_value() &&
window_drag_result == ShelfWindowDragResult::kGoToHomeScreen) {
return InAppShelfGestures::kFlingUpToShowHomeScreen;
}
if (transitioned_from_overview_to_home)
return InAppShelfGestures::kFlingUpToShowHomeScreen;
if (old_state == current_state)
return std::nullopt;
if (current_state == HotseatState::kHidden)
return InAppShelfGestures::kSwipeDownToHide;
if (current_state == HotseatState::kExtended)
return InAppShelfGestures::kSwipeUpToShow;
return std::nullopt;
}
bool IsInImmersiveFullscreen() {
WindowState* active_window = WindowState::ForActiveWindow();
return active_window && active_window->IsInImmersiveFullscreen();
}
void RecordGrowthCampaignsHoverEvent() {
auto* campaigns_manager = growth::CampaignsManager::Get();
if (campaigns_manager) {
campaigns_manager->RecordEvent(growth::kGrowthCampaignsEventHotseatHover,
true);
}
}
class HotseatEventHandler : public ui::EventHandler,
public ShelfLayoutManagerObserver {
public:
explicit HotseatEventHandler(ShelfLayoutManager* shelf_layout_manager)
: shelf_layout_manager_(shelf_layout_manager) {
shelf_layout_manager_->AddObserver(this);
Shell::Get()->AddPreTargetHandler(this);
}
HotseatEventHandler(const HotseatEventHandler&) = delete;
HotseatEventHandler& operator=(const HotseatEventHandler&) = delete;
~HotseatEventHandler() override {
shelf_layout_manager_->RemoveObserver(this);
Shell::Get()->RemovePreTargetHandler(this);
}
void OnHotseatStateChanged(HotseatState old_state,
HotseatState new_state) override {
should_forward_event_ = new_state == HotseatState::kExtended;
}
void OnGestureEvent(ui::GestureEvent* event) override {
if (!should_forward_event_)
return;
shelf_layout_manager_->ProcessGestureEventOfInAppHotseat(
event, static_cast<aura::Window*>(event->target()));
}
private:
bool should_forward_event_ = false;
const raw_ptr<ShelfLayoutManager> shelf_layout_manager_;
};
}
ShelfLayoutManager::State::State() = default;
bool ShelfLayoutManager::State::IsAddingSecondaryUser() const {
return session_state == session_manager::SessionState::LOGIN_SECONDARY;
}
bool ShelfLayoutManager::State::IsScreenLocked() const {
return session_state == session_manager::SessionState::LOCKED;
}
bool ShelfLayoutManager::State::IsActiveSessionState() const {
return session_state == session_manager::SessionState::ACTIVE;
}
bool ShelfLayoutManager::State::IsShelfAutoHidden() const {
return visibility_state == SHELF_AUTO_HIDE &&
auto_hide_state == SHELF_AUTO_HIDE_HIDDEN;
}
bool ShelfLayoutManager::State::IsShelfAutoHiddenInSession() const {
return in_session_visibility_state == SHELF_AUTO_HIDE &&
in_session_auto_hide_state == SHELF_AUTO_HIDE_HIDDEN;
}
bool ShelfLayoutManager::State::IsShelfVisible() const {
return visibility_state == SHELF_VISIBLE ||
(visibility_state == SHELF_AUTO_HIDE &&
auto_hide_state == SHELF_AUTO_HIDE_SHOWN);
}
bool ShelfLayoutManager::State::Equals(const State& other) const {
return other.visibility_state == visibility_state &&
(visibility_state != SHELF_AUTO_HIDE ||
other.auto_hide_state == auto_hide_state) &&
other.window_state == window_state &&
other.pre_lock_screen_animation_active ==
pre_lock_screen_animation_active &&
other.session_state == session_state;
}
ShelfLayoutManager::ScopedSuspendWorkAreaUpdate::ScopedSuspendWorkAreaUpdate(
ShelfLayoutManager* manager)
: manager_(manager) {
manager_->SuspendWorkAreaUpdate();
}
ShelfLayoutManager::ScopedSuspendWorkAreaUpdate::
~ScopedSuspendWorkAreaUpdate() {
manager_->ResumeWorkAreaUpdate();
}
ShelfLayoutManager::ScopedVisibilityLock::ScopedVisibilityLock(
ShelfLayoutManager* shelf)
: shelf_(shelf->weak_factory_.GetWeakPtr()) {
++shelf_->suspend_visibility_update_;
}
ShelfLayoutManager::ScopedVisibilityLock::~ScopedVisibilityLock() {
if (!shelf_)
return;
--shelf_->suspend_visibility_update_;
DCHECK_GE(shelf_->suspend_visibility_update_, 0);
if (shelf_->suspend_visibility_update_ == 0)
shelf_->UpdateVisibilityState(false);
}
ShelfLayoutManager::ShelfLayoutManager(ShelfWidget* shelf_widget, Shelf* shelf)
: shelf_widget_(shelf_widget),
shelf_(shelf),
is_background_blur_enabled_(chromeos::features::IsSystemBlurEnabled()) {
DCHECK(shelf_widget_);
DCHECK(shelf_);
}
ShelfLayoutManager::~ShelfLayoutManager() {
hotseat_event_handler_.reset();
overview_suspend_work_area_update_.reset();
for (auto& observer : observers_)
observer.WillDeleteShelfLayoutManager();
auto* shell = Shell::Get();
shell->locale_update_controller()->RemoveObserver(this);
shell->RemoveShellObserver(this);
shell->lock_state_controller()->RemoveObserver(this);
if (shell->app_list_controller())
shell->app_list_controller()->RemoveObserver(this);
if (shell->overview_controller())
shell->overview_controller()->RemoveObserver(this);
}
void ShelfLayoutManager::InitObservers() {
shelf_->AddObserver(this);
auto* shell = Shell::Get();
shell->AddShellObserver(this);
SplitViewController::Get(shelf_widget_->GetNativeWindow())->AddObserver(this);
if (SnapGroupController* snap_group_controller = SnapGroupController::Get()) {
snap_group_controller->AddObserver(this);
}
ShelfConfig::Get()->AddObserver(this);
shell->overview_controller()->AddObserver(this);
shell->app_list_controller()->AddObserver(this);
shell->lock_state_controller()->AddObserver(this);
shell->activation_client()->AddObserver(this);
shell->locale_update_controller()->AddObserver(this);
state_.session_state = shell->session_controller()->GetSessionState();
shelf_background_type_ = ComputeShelfBackgroundType();
wallpaper_controller_observation_.Observe(shell->wallpaper_controller());
if (DesksController* desks_controller = DesksController::Get()) {
desks_controller->AddObserver(this);
}
}
void ShelfLayoutManager::PrepareForShutdown() {
if (in_shutdown_)
return;
in_shutdown_ = true;
Shell::Get()->activation_client()->RemoveObserver(this);
ShelfConfig::Get()->RemoveObserver(this);
if (DesksController::Get())
DesksController::Get()->RemoveObserver(this);
SplitViewController::Get(shelf_widget_->GetNativeWindow())
->RemoveObserver(this);
if (SnapGroupController* snap_group_controller = SnapGroupController::Get()) {
snap_group_controller->RemoveObserver(this);
}
shelf_->RemoveObserver(this);
}
bool ShelfLayoutManager::IsVisible() const {
return shelf_widget_->status_area_widget() &&
shelf_widget_->status_area_widget()->IsVisible() &&
state_.IsShelfVisible();
}
gfx::Rect ShelfLayoutManager::GetIdealBounds() const {
const int shelf_size = ShelfConfig::Get()->shelf_size();
aura::Window* shelf_window = shelf_widget_->GetNativeWindow();
gfx::Rect rect(screen_util::GetDisplayBoundsInParent(shelf_window));
return shelf_->SelectValueForShelfAlignment(
gfx::Rect(rect.x(), rect.bottom() - shelf_size, rect.width(), shelf_size),
gfx::Rect(rect.x(), rect.y(), shelf_size, rect.height()),
gfx::Rect(rect.right() - shelf_size, rect.y(), shelf_size,
rect.height()));
}
gfx::Rect ShelfLayoutManager::GetIdealBoundsForWorkAreaCalculation() const {
if (!display::Screen::Get()->InTabletMode() ||
state_.session_state != session_manager::SessionState::ACTIVE) {
return GetIdealBounds();
}
aura::Window* shelf_window = shelf_widget_->GetNativeWindow();
gfx::Rect rect(screen_util::GetDisplayBoundsInParent(shelf_window));
const int in_app_shelf_size = ShelfConfig::Get()->in_app_shelf_size();
rect.set_y(rect.bottom() - in_app_shelf_size);
rect.set_height(in_app_shelf_size);
return rect;
}
void ShelfLayoutManager::UpdateShelfWorkAreaInsets() {
if (suspend_work_area_update_ || in_shutdown_) {
return;
}
auto* shelf_native_window = shelf_widget_->GetNativeWindow();
if (!shelf_native_window) {
return;
}
gfx::Insets shelf_insets =
CalculateShelfInsets(shelf_->alignment(), visibility_state());
gfx::Insets in_session_shelf_insets;
if (shelf_->alignment() == ShelfAlignment::kBottomLocked) {
if (shelf_->auto_hide_behavior() == ShelfAutoHideBehavior::kAlways ||
shelf_->in_session_auto_hide_behavior() ==
ShelfAutoHideBehavior::kAlways) {
in_session_shelf_insets = gfx::Insets();
} else {
in_session_shelf_insets = CalculateShelfInsets(
shelf_->in_session_alignment(), state_.in_session_visibility_state);
}
} else {
in_session_shelf_insets = shelf_insets;
}
if (display::Screen::Get()->InTabletMode() && IsVisible()) {
gfx::Rect shelf_bounds_for_workarea_calculation =
GetIdealBoundsForWorkAreaCalculation();
wm::ConvertRectToScreen(shelf_native_window->GetRootWindow(),
&shelf_bounds_for_workarea_calculation);
UpdateWorkAreaInsetsAndNotifyObserversInternal(
shelf_bounds_for_workarea_calculation, shelf_insets,
in_session_shelf_insets);
} else {
UpdateWorkAreaInsetsAndNotifyObserversInternal(
shelf_widget_->GetTargetBounds(), shelf_insets,
in_session_shelf_insets);
}
}
void ShelfLayoutManager::UpdateDisplayWorkArea() {
if (suspend_work_area_update_ || in_shutdown_) {
return;
}
auto* shelf_native_window = shelf_widget_->GetNativeWindow();
if (!shelf_native_window) {
return;
}
base::AutoReset scoped_update(&updating_work_area_, true);
UpdateShelfWorkAreaInsets();
display_ =
display::Screen::Get()->GetDisplayNearestWindow(shelf_native_window);
const bool in_overview =
Shell::Get()->overview_controller()->InOverviewSession();
const bool in_splitview =
SplitViewController::Get(shelf_native_window)->InSplitViewMode();
const WorkAreaInsets* const work_area =
WorkAreaInsets::ForWindow(shelf_native_window);
const gfx::Insets user_work_area_insets = work_area->user_work_area_insets();
if (state_.IsActiveSessionState()) {
if (!in_overview && (shelf_->alignment() != ShelfAlignment::kBottomLocked ||
display_.work_area() == display_.bounds())) {
gfx::Insets insets;
if (!state_.IsAddingSecondaryUser() || work_area->IsKeyboardShown()) {
insets = user_work_area_insets;
}
Shell::Get()
->window_tree_host_manager()
->UpdateWorkAreaOfDisplayNearestWindow(shelf_native_window, insets);
} else if (in_overview && in_splitview) {
Shell::Get()
->window_tree_host_manager()
->UpdateWorkAreaOfDisplayNearestWindow(shelf_native_window,
user_work_area_insets);
}
} else {
Shell::Get()
->window_tree_host_manager()
->UpdateWorkAreaOfDisplayNearestWindow(
shelf_native_window, work_area->GetAccessibilityInsets());
}
}
void ShelfLayoutManager::LayoutShelf(bool animate) {
animate &= !IsShelfContainerAnimating();
if (in_shutdown_ || !shelf_widget_->native_widget())
return;
CalculateTargetBounds();
UpdateBoundsAndOpacity(animate);
}
void ShelfLayoutManager::UpdateVisibilityState(bool force_layout) {
aura::Window* shelf_window = shelf_widget_->GetNativeWindow();
if (in_shutdown_ || !shelf_window || suspend_visibility_update_)
return;
SetState(CalculateShelfVisibility(), force_layout);
const WorkspaceWindowState window_state =
GetShelfWorkspaceWindowState(shelf_window);
UpdateWorkspaceMask(window_state);
SendA11yAlertForFullscreenWorkspaceState(window_state);
}
void ShelfLayoutManager::UpdateVisibilityStateForBackGesture() {
base::AutoReset<bool> back_gesture(&state_forced_by_back_gesture_, true);
SetState(SHELF_VISIBLE, false);
}
void ShelfLayoutManager::UpdateAutoHideState() {
ShelfAutoHideState auto_hide_state =
CalculateAutoHideState(visibility_state());
if (auto_hide_state != state_.auto_hide_state) {
if (auto_hide_state == SHELF_AUTO_HIDE_HIDDEN) {
UpdateVisibilityState(false);
} else {
if (!auto_hide_timer_.IsRunning()) {
mouse_over_shelf_when_auto_hide_timer_started_ =
shelf_widget_->GetWindowBoundsInScreen().Contains(
display::Screen::Get()->GetCursorScreenPoint());
drag_over_shelf_when_auto_hide_timer_started_ =
in_drag_drop_ && shelf_widget_->GetWindowBoundsInScreen().Contains(
last_drag_drop_position_in_screen_);
}
StartAutoHideTimer();
}
} else {
StopAutoHideTimer();
}
}
void ShelfLayoutManager::UpdateAutoHideForMouseEvent(ui::MouseEvent* event,
aura::Window* target) {
in_mouse_drag_ =
(event->type() == ui::EventType::kMouseDragged ||
(in_mouse_drag_ && event->type() != ui::EventType::kMouseReleased &&
event->type() != ui::EventType::kMouseCaptureChanged)) &&
!IsShelfWindow(target) && !IsStatusAreaWindow(target);
if (visibility_state() != SHELF_AUTO_HIDE || in_shutdown_)
return;
if (event->type() == ui::EventType::kMousePressed ||
event->type() == ui::EventType::kMouseMoved) {
if (shelf_->shelf_widget()->GetVisibleShelfBounds().Contains(
display::Screen::Get()->GetCursorScreenPoint())) {
UpdateAutoHideState();
last_seen_mouse_position_was_over_shelf_ = true;
} else {
if (event->type() == ui::EventType::kMousePressed) {
UpdateAutoHideState();
} else if (last_seen_mouse_position_was_over_shelf_) {
StartAutoHideTimer();
}
last_seen_mouse_position_was_over_shelf_ = false;
}
}
}
void ShelfLayoutManager::UpdateContextualNudges() {
if (!features::IsHideShelfControlsInTabletModeEnabled()) {
return;
}
if (Shell::Get()->session_controller()->GetSessionState() !=
session_manager::SessionState::ACTIVE) {
return;
}
const bool in_app_shelf = ShelfConfig::Get()->is_in_app();
const bool in_tablet_mode = ShelfConfig::Get()->in_tablet_mode();
const bool in_overview_mode = ShelfConfig::Get()->in_overview_mode();
contextual_tooltip::SetDragHandleNudgeDisabledForHiddenShelf(!IsVisible());
if (in_app_shelf && in_tablet_mode && !in_app_to_home_nudge_controller_) {
in_app_to_home_nudge_controller_ =
std::make_unique<InAppToHomeNudgeController>(shelf_widget_);
}
if (in_app_to_home_nudge_controller_) {
in_app_to_home_nudge_controller_->SetNudgeAllowedForCurrentShelf(
in_tablet_mode, in_app_shelf,
ShelfConfig::Get()->shelf_controls_shown());
}
const bool allow_home_to_overview_nudge =
in_tablet_mode && !in_app_shelf &&
!ShelfConfig::Get()->shelf_controls_shown() && !in_overview_mode &&
!base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kAshNoNudges);
if (allow_home_to_overview_nudge && !home_to_overview_nudge_controller_) {
home_to_overview_nudge_controller_ =
std::make_unique<HomeToOverviewNudgeController>(
shelf_->hotseat_widget());
}
if (home_to_overview_nudge_controller_) {
home_to_overview_nudge_controller_->SetNudgeAllowedForCurrentShelf(
allow_home_to_overview_nudge);
}
}
void ShelfLayoutManager::HideContextualNudges() {
if (!features::IsHideShelfControlsInTabletModeEnabled()) {
return;
}
shelf_widget_->HideDragHandleNudge(
contextual_tooltip::DismissNudgeReason::kOther);
if (home_to_overview_nudge_controller_)
home_to_overview_nudge_controller_->SetNudgeAllowedForCurrentShelf(false);
}
void ShelfLayoutManager::ProcessGestureEventOfAutoHideShelf(
ui::GestureEvent* event,
aura::Window* target) {
const bool is_shelf_window = IsShelfWindow(target);
if (IsVisible() || in_shutdown_) {
if (!is_shelf_window && !IsStatusAreaWindow(target) &&
visibility_state() == SHELF_AUTO_HIDE &&
state_.auto_hide_state == SHELF_AUTO_HIDE_SHOWN &&
event->type() == ui::EventType::kGestureTap) {
UpdateAutoHideState();
}
if (is_shelf_window && !IsStatusAreaWindow(target) &&
hotseat_state() == HotseatState::kHidden &&
ShelfConfig::Get()->is_in_app()) {
UpdateAutoHideState();
}
if (is_shelf_window && !IsStatusAreaWindow(target) &&
visibility_state() == SHELF_AUTO_HIDE &&
state_.auto_hide_state == SHELF_AUTO_HIDE_SHOWN &&
event->type() == ui::EventType::kGestureEnd &&
drag_status_ != kDragNone) {
CompleteDrag(*event);
}
return;
}
if (is_shelf_window) {
ui::GestureEvent event_in_screen(*event);
gfx::Point location_in_screen(event->location());
::wm::ConvertPointToScreen(target, &location_in_screen);
event_in_screen.set_location(location_in_screen);
if (ProcessGestureEvent(event_in_screen))
event->StopPropagation();
}
}
void ShelfLayoutManager::ProcessGestureEventOfInAppHotseat(
ui::GestureEvent* event,
aura::Window* target) {
if (!display::Screen::Get()->InTabletMode()) {
return;
}
DCHECK_EQ(hotseat_state(), HotseatState::kExtended);
if (IsShelfWindow(target) || drag_status_ != DragStatus::kDragNone)
return;
base::AutoReset<bool> hide_hotseat(&should_hide_hotseat_, true);
const bool in_overview =
Shell::Get()->overview_controller() &&
Shell::Get()->overview_controller()->InOverviewSession();
ui::EventType interesting_type =
in_overview ? ui::EventType::kGestureTap : ui::EventType::kGestureBegin;
if (event->type() == interesting_type) {
UMA_HISTOGRAM_ENUMERATION(
kHotseatGestureHistogramName,
InAppShelfGestures::kHotseatHiddenDueToInteractionOutsideOfShelf);
}
UpdateVisibilityState(false);
}
void ShelfLayoutManager::AddObserver(ShelfLayoutManagerObserver* observer) {
observers_.AddObserver(observer);
}
void ShelfLayoutManager::RemoveObserver(ShelfLayoutManagerObserver* observer) {
observers_.RemoveObserver(observer);
}
bool ShelfLayoutManager::ProcessGestureEvent(
const ui::GestureEvent& event_in_screen) {
if (!IsDragAllowed())
return false;
if (event_in_screen.type() == ui::EventType::kGestureScrollBegin) {
return StartGestureDrag(event_in_screen);
}
if (drag_status_ != kDragInProgress &&
drag_status_ != kDragHomeToOverviewInProgress &&
drag_status_ != kFlingBubbleLauncherInProgress) {
return false;
}
if (event_in_screen.type() == ui::EventType::kGestureShowPress) {
return true;
}
if (event_in_screen.type() == ui::EventType::kGestureScrollUpdate) {
UpdateGestureDrag(event_in_screen);
return true;
}
if (event_in_screen.type() == ui::EventType::kScrollFlingStart) {
if (MaybeHandleShelfFling(event_in_screen))
return true;
}
if (event_in_screen.type() == ui::EventType::kGestureScrollEnd ||
event_in_screen.type() == ui::EventType::kScrollFlingStart) {
if (event_in_screen.type() == ui::EventType::kScrollFlingStart) {
last_drag_velocity_ =
event_in_screen.AsGestureEvent()->details().velocity_y();
}
if (drag_status_ == kDragHomeToOverviewInProgress) {
CompleteDragHomeToOverview(event_in_screen);
} else if (drag_status_ == kFlingBubbleLauncherInProgress) {
CompleteShelfFling(event_in_screen);
} else {
CompleteDrag(event_in_screen);
}
return true;
}
CancelDrag(std::nullopt);
return false;
}
void ShelfLayoutManager::ProcessMouseEventFromShelf(
const ui::MouseEvent& event_in_screen) {
ui::EventType event_type = event_in_screen.type();
DCHECK(event_type == ui::EventType::kMousePressed ||
event_type == ui::EventType::kMouseDragged ||
event_type == ui::EventType::kMouseReleased);
if (!IsDragAllowed())
return;
switch (event_type) {
case ui::EventType::kMousePressed:
AttemptToDragByMouse(event_in_screen);
break;
case ui::EventType::kMouseDragged:
UpdateMouseDrag(event_in_screen);
return;
case ui::EventType::kMouseReleased:
ReleaseMouseDrag(event_in_screen);
return;
default:
NOTREACHED();
}
}
void ShelfLayoutManager::ProcessGestureEventFromShelfWidget(
ui::GestureEvent* event_in_screen) {
if (ProcessGestureEvent(*event_in_screen))
event_in_screen->StopPropagation();
}
void ShelfLayoutManager::ProcessScrollOffset(int offset,
const ui::LocatedEvent& event) {
const int adjusted_offset = GetScrollOffsetInAllowedDirection(
offset, Shell::Get()->app_list_controller()->IsVisible(display_.id()));
if (adjusted_offset <=
ShelfConfig::Get()->mousewheel_scroll_offset_threshold()) {
return;
}
if (!IsLocationInBubbleLauncherShowBounds(event.root_location())) {
return;
}
Shell::Get()->app_list_controller()->ToggleAppList(
display::Screen::Get()
->GetDisplayNearestWindow(shelf_widget_->GetNativeWindow())
.id(),
AppListShowSource::kScrollFromShelf, event.time_stamp());
}
void ShelfLayoutManager::ProcessScrollEventFromShelf(ui::ScrollEvent* event) {
if (shelf_->IsHorizontalAlignment()) {
ProcessScrollOffset(GetOffset(event->y_offset(), prefs::kNaturalScroll),
*event);
} else {
int offset = shelf_->alignment() == ShelfAlignment::kLeft
? -event->x_offset()
: event->x_offset();
offset = GetOffset(offset, prefs::kNaturalScroll),
ProcessScrollOffset(offset, *event);
}
}
bool ShelfLayoutManager::IsBubbleLauncherShowOnGestureScrollAvailable() {
if (!state_.IsShelfVisible())
return false;
if (display::Screen::Get()->InTabletMode()) {
return false;
}
return Shell::Get()->app_list_controller();
}
bool ShelfLayoutManager::MaybeHandleShelfFling(
const ui::GestureEvent& event_in_screen) {
if (!IsBubbleLauncherShowOnGestureScrollAvailable())
return false;
int velocity = shelf_->SelectValueForShelfAlignment(
event_in_screen.AsGestureEvent()->details().velocity_y(),
-event_in_screen.AsGestureEvent()->details().velocity_x(),
event_in_screen.AsGestureEvent()->details().velocity_x());
const int adjusted_velocity = GetScrollOffsetInAllowedDirection(
velocity, Shell::Get()->app_list_controller()->IsVisible(display_.id()));
if (adjusted_velocity > -kShelfFlingVelocityThresehold)
return false;
if (!IsLocationInBubbleLauncherShowBounds(drag_start_point_in_screen_))
return false;
Shell::Get()->app_list_controller()->ToggleAppList(
display::Screen::Get()
->GetDisplayNearestWindow(shelf_widget_->GetNativeWindow())
.id(),
AppListShowSource::kSwipeFromShelf, event_in_screen.time_stamp());
return true;
}
bool ShelfLayoutManager::IsLocationInBubbleLauncherShowBounds(
const gfx::Point& location_in_screen) {
const gfx::Rect shelf_bounds_in_screen =
shelf_->shelf_widget()->GetWindowBoundsInScreen();
if (!shelf_bounds_in_screen.Contains(location_in_screen))
return false;
const int distance_from_start = shelf_->PrimaryAxisValue(
(base::i18n::IsRTL()
? shelf_bounds_in_screen.right() - location_in_screen.x()
: location_in_screen.x() - shelf_bounds_in_screen.x()),
location_in_screen.y());
if (distance_from_start < kQuickShowMinAllowDistance)
return true;
if (shelf_->IsHorizontalAlignment() &&
distance_from_start >
Shell::Get()->app_list_controller()->GetPreferredBubbleWidth(
shelf_widget_->GetNativeWindow())) {
return false;
}
ScrollableShelfView* scrollable_shelf_view =
shelf_->hotseat_widget()->scrollable_shelf_view();
gfx::Rect available_bounds_in_screen =
scrollable_shelf_view->GetHotseatBackgroundBounds();
views::View::ConvertRectToScreen(scrollable_shelf_view,
&available_bounds_in_screen);
const int mirrored_left_for_hotseat =
base::i18n::IsRTL()
? shelf_bounds_in_screen.right() - available_bounds_in_screen.right()
: available_bounds_in_screen.x();
return distance_from_start <
shelf_->PrimaryAxisValue(mirrored_left_for_hotseat,
available_bounds_in_screen.y());
}
void ShelfLayoutManager::ProcessMouseWheelEventFromShelf(
ui::MouseWheelEvent* event) {
const int y_offset =
GetOffset(event->offset().y(), prefs::kMouseReverseScroll);
ProcessScrollOffset(y_offset, *event);
}
ShelfBackgroundType ShelfLayoutManager::ComputeShelfBackgroundType() const {
if (state_.pre_lock_screen_animation_active)
return ShelfBackgroundType::kDefaultBg;
if (state_.session_state == session_manager::SessionState::OOBE)
return ShelfBackgroundType::kOobe;
if (state_.session_state != session_manager::SessionState::ACTIVE) {
if (Shell::Get()->wallpaper_controller()->HasShownAnyWallpaper() &&
!Shell::Get()
->wallpaper_controller()
->IsWallpaperBlurredForLockState()) {
return ShelfBackgroundType::kLoginNonBlurredWallpaper;
}
return ShelfBackgroundType::kLogin;
}
const aura::Window* shelf_native_window = shelf_widget_->GetNativeWindow();
const bool in_split_view_mode =
SplitViewController::Get(shelf_native_window)->InSplitViewMode();
SnapGroupController* snap_group_controller = SnapGroupController::Get();
const bool has_visible_snap_group =
snap_group_controller &&
snap_group_controller->GetTopmostVisibleSnapGroup(
shelf_native_window->GetRootWindow(),
false);
const bool maximized =
in_split_view_mode || has_visible_snap_group ||
state_.window_state == WorkspaceWindowState::kFullscreen ||
state_.window_state == WorkspaceWindowState::kMaximized;
const bool in_overview = IsInOverviewSession();
if (display::Screen::Get()->InTabletMode()) {
const bool app_list_target_visibility =
Shell::Get()->app_list_controller() &&
Shell::Get()->app_list_controller()->GetTargetVisibility(display_.id());
if (app_list_target_visibility) {
if (ShelfConfig::Get()->is_virtual_keyboard_shown())
return ShelfBackgroundType::kInApp;
return ShelfBackgroundType::kHomeLauncher;
}
return in_overview ? ShelfBackgroundType::kOverview
: ShelfBackgroundType::kInApp;
}
if (maximized) {
if (state_.visibility_state == SHELF_AUTO_HIDE)
return ShelfBackgroundType::kDefaultBg;
return ShelfBackgroundType::kMaximized;
}
if (in_overview)
return ShelfBackgroundType::kOverview;
return ShelfBackgroundType::kDefaultBg;
}
void ShelfLayoutManager::MaybeUpdateShelfBackground(AnimationChangeType type) {
const ShelfBackgroundType new_background_type = ComputeShelfBackgroundType();
if (new_background_type == shelf_background_type_) {
return;
}
shelf_background_type_ = new_background_type;
for (auto& observer : observers_)
observer.OnBackgroundUpdated(shelf_background_type_, type);
}
bool ShelfLayoutManager::ShouldBlurShelfBackground() {
if (!is_background_blur_enabled_)
return false;
return shelf_background_type_ == ShelfBackgroundType::kDefaultBg &&
state_.session_state == session_manager::SessionState::ACTIVE;
}
bool ShelfLayoutManager::HasVisibleWindow() const {
aura::Window* root = shelf_widget_->GetNativeWindow()->GetRootWindow();
const aura::Window::Windows windows =
Shell::Get()->mru_window_tracker()->BuildWindowListIgnoreModal(
kActiveDesk);
for (aura::Window* window : windows) {
if (window->IsVisible() && !IsAppListWindow(window) &&
root->Contains(window)) {
return true;
}
}
return false;
}
void ShelfLayoutManager::CancelDragOnShelfIfInProgress() {
if (drag_status_ == kDragInProgress ||
drag_status_ == kDragHomeToOverviewInProgress) {
CancelDrag(std::nullopt);
}
}
void ShelfLayoutManager::SuspendVisibilityUpdateForShutdown() {
++suspend_visibility_update_;
}
void ShelfLayoutManager::OnShelfItemSelected(ShelfAction action) {
switch (action) {
case SHELF_ACTION_NONE:
case SHELF_ACTION_APP_LIST_SHOWN:
case SHELF_ACTION_APP_LIST_DISMISSED:
case SHELF_ACTION_APP_LIST_BACK:
case SHELF_ACTION_WINDOW_MINIMIZED:
break;
case SHELF_ACTION_NEW_WINDOW_CREATED:
case SHELF_ACTION_WINDOW_ACTIVATED: {
base::AutoReset<bool> reset(&should_hide_hotseat_, true);
UpdateVisibilityState(false);
} break;
}
}
void ShelfLayoutManager::OnWindowResized() {
LayoutShelf();
UpdateDisplayWorkArea();
}
void ShelfLayoutManager::SetChildBounds(aura::Window* child,
const gfx::Rect& requested_bounds) {
WmDefaultLayoutManager::SetChildBounds(child, requested_bounds);
if (phase_ != ShelfLayoutPhase::kMoving &&
((shelf_widget_->GetNativeWindow() == child) ||
(shelf_widget_->status_area_widget()->GetNativeWindow() == child))) {
LayoutShelf(true);
}
}
void ShelfLayoutManager::OnShelfAutoHideBehaviorChanged() {
UpdateVisibilityState(false);
}
void ShelfLayoutManager::OnUserWorkAreaInsetsChanged(
aura::Window* root_window) {
LayoutShelf();
UpdateDisplayWorkArea();
}
void ShelfLayoutManager::OnPinnedStateChanged(aura::Window* pinned_window) {
UpdateVisibilityState(false);
}
void ShelfLayoutManager::OnRootWindowWillShutdown(aura::Window* root_window) {
aura::Window* root = shelf_widget_->GetNativeWindow()->GetRootWindow();
if (root == root_window) {
PrepareForShutdown();
}
}
void ShelfLayoutManager::OnShellDestroying() {
PrepareForShutdown();
}
void ShelfLayoutManager::OnSplitViewStateChanged(
SplitViewController::State previous_state,
SplitViewController::State state) {
if (previous_state == SplitViewController::State::kNoSnap ||
state == SplitViewController::State::kNoSnap) {
MaybeUpdateShelfBackground(AnimationChangeType::ANIMATE);
}
}
void ShelfLayoutManager::OnSnapGroupAdded(SnapGroup* snap_group) {
MaybeUpdateShelfBackground(AnimationChangeType::ANIMATE);
}
void ShelfLayoutManager::OnSnapGroupRemoving(SnapGroup* snap_group,
SnapGroupExitPoint exit_pint) {
MaybeUpdateShelfBackground(AnimationChangeType::ANIMATE);
}
void ShelfLayoutManager::OnOverviewModeWillStart() {
auto* active_window = window_util::GetActiveWindow();
if (active_window && IsShelfWindow(active_window)) {
wm::DeactivateWindow(active_window);
}
overview_mode_will_start_ = true;
}
void ShelfLayoutManager::OnOverviewModeStarting() {
overview_mode_will_start_ = false;
if (!overview_suspend_work_area_update_) {
overview_suspend_work_area_update_.emplace(this);
}
UpdateContextualNudges();
}
void ShelfLayoutManager::OnOverviewModeStartingAnimationComplete(
bool canceled) {
if (canceled)
return;
overview_suspend_work_area_update_.reset();
}
void ShelfLayoutManager::OnOverviewModeEnding(OverviewSession* session) {
if (!overview_suspend_work_area_update_) {
overview_suspend_work_area_update_.emplace(this);
}
}
void ShelfLayoutManager::OnOverviewModeEndingAnimationComplete(bool canceled) {
if (canceled)
return;
overview_suspend_work_area_update_.reset();
}
void ShelfLayoutManager::OnOverviewModeEnded() {
UpdateVisibilityState(false);
}
void ShelfLayoutManager::OnAppListVisibilityWillChange(bool shown,
int64_t display_id) {
OnAppListVisibilityChanged(shown, display_id);
}
void ShelfLayoutManager::OnAppListVisibilityChanged(bool shown,
int64_t display_id) {
DVLOG(1) << __PRETTY_FUNCTION__ << " shown " << shown << " display "
<< display_id;
if (!shelf_widget_ || !shelf_widget_->GetNativeWindow())
return;
if (display_.id() != display_id)
return;
UpdateVisibilityState(false);
MaybeUpdateShelfBackground(AnimationChangeType::IMMEDIATE);
}
void ShelfLayoutManager::OnWindowActivated(ActivationReason reason,
aura::Window* gained_active,
aura::Window* lost_active) {
if (!IsShelfWindow(gained_active) &&
!(window_drag_controller_ &&
window_drag_controller_->during_window_restoration())) {
shelf_->hotseat_widget()->set_manually_extended(false);
}
UpdateAutoHideStateNow();
}
void ShelfLayoutManager::OnLockStateEvent(LockStateObserver::EventType event) {
if (event == EVENT_LOCK_ANIMATION_STARTED) {
state_.pre_lock_screen_animation_active = true;
UpdateShelfVisibilityAfterLoginUIChange();
} else {
state_.pre_lock_screen_animation_active = false;
}
MaybeUpdateShelfBackground(AnimationChangeType::ANIMATE);
}
void ShelfLayoutManager::OnAppModeSessionStarted() {
UpdateShelfVisibilityAfterLoginUIChange();
}
void ShelfLayoutManager::OnSessionStateChanged(
session_manager::SessionState state) {
const bool was_adding_user = state_.IsAddingSecondaryUser();
const bool was_locked = state_.IsScreenLocked();
state_.session_state = state;
bool animate_background = !IsShelfContainerAnimating();
MaybeUpdateShelfBackground(animate_background
? AnimationChangeType::ANIMATE
: AnimationChangeType::IMMEDIATE);
HideContextualNudges();
{
base::AutoReset<bool> immediate_transition(
&state_change_animation_disabled_,
!animate_background || state_.IsActiveSessionState() ||
was_locked != state_.IsScreenLocked());
UpdateShelfVisibilityAfterLoginUIChange();
}
if (was_adding_user == state_.IsAddingSecondaryUser()) {
UpdateContextualNudges();
}
}
void ShelfLayoutManager::OnLoginStatusChanged(LoginStatus login_status) {
UpdateVisibilityState(false);
}
void ShelfLayoutManager::OnWallpaperBlurChanged() {
MaybeUpdateShelfBackground(AnimationChangeType::ANIMATE);
}
void ShelfLayoutManager::OnFirstWallpaperShown() {
MaybeUpdateShelfBackground(AnimationChangeType::ANIMATE);
}
void ShelfLayoutManager::OnDisplayMetricsChanged(
const display::Display& display,
uint32_t changed_metrics) {
if (updating_work_area_ || phase_ == ShelfLayoutPhase::kMoving ||
changed_metrics == display::DisplayObserver::DISPLAY_METRIC_WORK_AREA) {
return;
}
LayoutShelf();
UpdateShelfWorkAreaInsets();
}
void ShelfLayoutManager::OnLocaleChanged() {
shelf_->login_shelf_widget()->HandleLocaleChange();
shelf_->status_area_widget()->HandleLocaleChange();
shelf_->navigation_widget()->HandleLocaleChange();
shelf_widget_->desk_button_widget()->HandleLocaleChange();
LayoutShelf();
}
void ShelfLayoutManager::OnDeskSwitchAnimationLaunching() {
++suspend_visibility_update_;
}
void ShelfLayoutManager::OnDeskSwitchAnimationFinished() {
--suspend_visibility_update_;
DCHECK_GE(suspend_visibility_update_, 0);
if (!suspend_visibility_update_) {
UpdateVisibilityState(true);
}
}
float ShelfLayoutManager::GetOpacity() const {
return target_opacity_;
}
void ShelfLayoutManager::LockAutoHideState(bool lock_auto_hide_state) {
if (is_auto_hide_state_locked_ == lock_auto_hide_state)
return;
is_auto_hide_state_locked_ = lock_auto_hide_state;
if (!is_auto_hide_state_locked_) {
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(&ShelfLayoutManager::UpdateAutoHideState,
weak_factory_.GetWeakPtr()));
}
}
void ShelfLayoutManager::OnShelfConfigUpdated() {
UpdateVisibilityState(true);
MaybeUpdateShelfBackground(AnimationChangeType::IMMEDIATE);
}
void ShelfLayoutManager::SuspendWorkAreaUpdate() {
++suspend_work_area_update_;
}
void ShelfLayoutManager::ResumeWorkAreaUpdate() {
--suspend_work_area_update_;
DCHECK_GE(suspend_work_area_update_, 0);
if (suspend_work_area_update_ || in_shutdown_)
return;
UpdateVisibilityState(true);
}
void ShelfLayoutManager::SetState(ShelfVisibilityState visibility_state,
bool force_layout) {
if (suspend_visibility_update_)
return;
State state;
const HotseatState previous_hotseat_state = hotseat_state();
state.visibility_state = visibility_state;
state.auto_hide_state = CalculateAutoHideState(visibility_state);
if (state_.IsActiveSessionState()) {
state_.in_session_visibility_state = visibility_state;
state_.in_session_auto_hide_state = state.auto_hide_state;
}
state.window_state =
GetShelfWorkspaceWindowState(shelf_widget_->GetNativeWindow());
HotseatState new_hotseat_state =
CalculateHotseatState(visibility_state, state.auto_hide_state);
state.session_state = state_.session_state;
state.pre_lock_screen_animation_active =
state_.pre_lock_screen_animation_active;
const bool force_update = force_layout ||
drag_status_ == kDragCancelInProgress ||
drag_status_ == kDragCompleteInProgress;
if (!force_update && state_.Equals(state) &&
previous_hotseat_state == new_hotseat_state) {
return;
}
if (visibility_state == SHELF_AUTO_HIDE &&
state_.visibility_state != SHELF_AUTO_HIDE) {
DCHECK(!drag_drop_observer_);
if (auto* drag_drop_client = GetDragDropClient(shelf_widget_)) {
drag_drop_observer_ = std::make_unique<ScopedDragDropObserver>(
drag_drop_client,
base::BindRepeating(
&ShelfLayoutManager::UpdateAutoHideForDragDrop,
base::Unretained(this)));
}
} else if (visibility_state != SHELF_AUTO_HIDE &&
state_.visibility_state == SHELF_AUTO_HIDE) {
drag_drop_observer_.reset();
}
StopAutoHideTimer();
State old_state = state_;
state_ = state;
AnimationChangeType change_type = AnimationChangeType::ANIMATE;
bool delay_background_change = false;
if (state.window_state == WorkspaceWindowState::kMaximized &&
((state.visibility_state == SHELF_VISIBLE &&
old_state.visibility_state != SHELF_VISIBLE) ||
is_background_blur_enabled_)) {
change_type = AnimationChangeType::IMMEDIATE;
} else {
if (state.visibility_state == SHELF_VISIBLE &&
old_state.IsShelfAutoHidden()) {
delay_background_change = true;
}
}
if (!delay_background_change)
MaybeUpdateShelfBackground(change_type);
CalculateTargetBounds();
HotseatWidget* hotseat_widget = shelf_->hotseat_widget();
hotseat_widget->SetState(new_hotseat_state);
HotseatWidget::ScopedInStateTransition scoped_in_state_transition(
hotseat_widget, previous_hotseat_state, new_hotseat_state);
UpdateBoundsAndOpacity(!state_change_animation_disabled_);
UpdateDisplayWorkArea();
if (old_state.visibility_state != visibility_state) {
for (auto& observer : observers_) {
observer.OnShelfVisibilityStateChanged(*state_.visibility_state);
}
}
if ((old_state.visibility_state != state_.visibility_state &&
state_.visibility_state == SHELF_AUTO_HIDE) ||
old_state.auto_hide_state != state_.auto_hide_state) {
for (auto& observer : observers_)
observer.OnAutoHideStateChanged(state_.auto_hide_state);
}
if (previous_hotseat_state != hotseat_state()) {
if (hotseat_state() == HotseatState::kShownHomeLauncher) {
RecordGrowthCampaignsHoverEvent();
}
if (hotseat_state() == HotseatState::kExtended)
hotseat_event_handler_ = std::make_unique<HotseatEventHandler>(this);
else
hotseat_event_handler_.reset();
for (auto& observer : observers_)
observer.OnHotseatStateChanged(previous_hotseat_state, hotseat_state());
}
UpdateContextualNudges();
}
HotseatState ShelfLayoutManager::CalculateHotseatState(
ShelfVisibilityState visibility_state,
ShelfAutoHideState auto_hide_state) const {
auto* const screen_pinning_controller =
Shell::Get()->screen_pinning_controller();
if (screen_pinning_controller && screen_pinning_controller->IsPinned() &&
(GetWindowPinType(screen_pinning_controller->pinned_window()) ==
chromeos::WindowPinType::kLockedFullscreen)) {
return HotseatState::kHidden;
}
if (!display::Screen::Get()->InTabletMode() ||
!shelf_->IsHorizontalAlignment()) {
return HotseatState::kShownClamshell;
}
auto* app_list_controller = Shell::Get()->app_list_controller();
if (!app_list_controller) {
return hotseat_state();
}
const auto* overview_controller = Shell::Get()->overview_controller();
const bool in_overview =
((overview_controller && overview_controller->InOverviewSession()) ||
overview_mode_will_start_) &&
!overview_controller->IsCompletingShutdownAnimations();
const bool app_list_target_visibility =
app_list_controller->GetTargetVisibility(display_.id()) ||
app_list_controller->ShouldHomeLauncherBeVisible();
if (ShelfConfig::Get()->is_virtual_keyboard_shown()) {
return HotseatState::kHidden;
}
if (shelf_widget_->IsHotseatForcedShowInTabletMode() &&
drag_status_ == kDragNone) {
return app_list_target_visibility ? HotseatState::kShownHomeLauncher
: HotseatState::kExtended;
}
const int hotseat_size = shelf_->hotseat_widget()->GetHotseatSize();
switch (drag_status_) {
case kDragNone:
case kDragHomeToOverviewInProgress: {
if (app_list_target_visibility && !in_overview) {
return HotseatState::kShownHomeLauncher;
}
if (shelf_->hotseat_widget()->IsShowingShelfMenu()) {
return HotseatState::kExtended;
}
if (in_overview) {
if (hotseat_state() == HotseatState::kShownHomeLauncher) {
return HotseatState::kShownHomeLauncher;
}
return HotseatState::kHidden;
}
if (state_forced_by_back_gesture_) {
return HotseatState::kExtended;
}
if (visibility_state == SHELF_AUTO_HIDE) {
return HotseatState::kHidden;
}
if (shelf_->hotseat_widget()->is_manually_extended() &&
!should_hide_hotseat_) {
return HotseatState::kExtended;
}
shelf_->hotseat_widget()->set_manually_extended(false);
return HotseatState::kHidden;
}
case kDragCompleteInProgress:
if (visibility_state == SHELF_AUTO_HIDE) {
DCHECK_EQ(drag_auto_hide_state_, auto_hide_state);
return auto_hide_state == SHELF_AUTO_HIDE_SHOWN
? HotseatState::kExtended
: HotseatState::kHidden;
}
[[fallthrough]];
case kDragCancelInProgress: {
if (!hotseat_is_in_drag_) {
return hotseat_state();
}
if (app_list_target_visibility) {
return HotseatState::kShownHomeLauncher;
}
if (in_overview && should_hide_hotseat_) {
return HotseatState::kHidden;
}
if (shelf_->hotseat_widget()->IsExtended()) {
return HotseatState::kExtended;
}
const int drag_base =
(hotseat_state() == HotseatState::kExtended &&
state_.visibility_state == SHELF_VISIBLE)
? (hotseat_size + ShelfConfig::Get()->hotseat_bottom_padding())
: 0;
const float total_amount_dragged = drag_base + drag_amount_;
const float end_of_drag_in_screen =
drag_start_point_in_screen_.y() + total_amount_dragged;
const int screen_bottom =
display::Screen::Get()
->GetDisplayNearestView(shelf_widget_->GetNativeView())
.bounds()
.bottom();
const bool dragged_to_bezel =
std::ceil(end_of_drag_in_screen) >= screen_bottom;
if (dragged_to_bezel) {
return HotseatState::kHidden;
}
if (std::abs(last_drag_velocity_) >= 120) {
if (last_drag_velocity_ > 0) {
return HotseatState::kHidden;
}
return HotseatState::kExtended;
}
const int top_of_hotseat_to_screen_bottom =
screen_bottom -
shelf_->hotseat_widget()->GetWindowBoundsInScreen().y();
const bool dragged_over_half_hotseat_size =
top_of_hotseat_to_screen_bottom < hotseat_size / 2;
if (dragged_over_half_hotseat_size) {
return HotseatState::kHidden;
}
return HotseatState::kExtended;
}
default:
return hotseat_state();
}
NOTREACHED();
}
ShelfVisibilityState ShelfLayoutManager::CalculateShelfVisibility() {
if (shelf_->ShouldHideOnSecondaryDisplay(state_.session_state)) {
return SHELF_HIDDEN;
}
if (Shell::Get()->session_controller()->IsRunningInAppMode()) {
return SHELF_HIDDEN;
}
if (!state_.IsActiveSessionState()) {
return SHELF_VISIBLE;
}
if (Shell::Get()->screen_pinning_controller()->IsPinned()) {
return SHELF_HIDDEN;
}
aura::Window* shelf_window = shelf_widget_->GetNativeWindow();
const WorkspaceWindowState window_state =
GetShelfWorkspaceWindowState(shelf_window);
switch (window_state) {
case WorkspaceWindowState::kFullscreen:
if (IsShelfAutoHideForFullscreenMaximized()) {
return SHELF_AUTO_HIDE;
}
if (IsShelfHiddenForFullscreen()) {
return SHELF_HIDDEN;
}
return SHELF_AUTO_HIDE;
case WorkspaceWindowState::kMaximized:
if (IsShelfAutoHideForFullscreenMaximized()) {
return SHELF_AUTO_HIDE;
}
break;
case WorkspaceWindowState::kDefault:
break;
}
switch (shelf_->auto_hide_behavior()) {
case ShelfAutoHideBehavior::kAlways:
return SHELF_AUTO_HIDE;
case ShelfAutoHideBehavior::kNever:
return SHELF_VISIBLE;
case ShelfAutoHideBehavior::kAlwaysHidden:
return SHELF_HIDDEN;
}
return SHELF_VISIBLE;
}
bool ShelfLayoutManager::SetDimmed(bool dimmed) {
if (dimmed_for_inactivity_ == dimmed)
return false;
if (!state_.IsShelfVisible())
return false;
std::unique_ptr<Shelf::ScopedAutoHideLock> auto_hide_lock =
std::make_unique<Shelf::ScopedAutoHideLock>(shelf_);
dimmed_for_inactivity_ = dimmed;
CalculateTargetBounds();
const base::TimeDelta dim_animation_duration =
ShelfConfig::Get()->DimAnimationDuration();
const gfx::Tween::Type dim_animation_tween =
ShelfConfig::Get()->DimAnimationTween();
const bool animate = !dim_animation_duration.is_zero();
std::optional<ui::AnimationThroughputReporter> navigation_widget_reporter;
if (animate) {
navigation_widget_reporter.emplace(
GetLayer(shelf_->navigation_widget())->GetAnimator(),
shelf_->GetNavigationWidgetAnimationReportCallback(hotseat_state()));
}
AnimateOpacity(GetLayer(shelf_->navigation_widget()), target_opacity_,
dim_animation_duration, dim_animation_tween);
AnimateOpacity(shelf_->hotseat_widget()->GetShelfView()->layer(),
shelf_->hotseat_widget()->CalculateShelfViewOpacity(),
dim_animation_duration, dim_animation_tween);
AnimateOpacity(GetLayer(shelf_->status_area_widget()), target_opacity_,
dim_animation_duration, dim_animation_tween);
shelf_->login_shelf_widget()->SetLoginShelfButtonOpacity(target_opacity_);
return true;
}
void ShelfLayoutManager::UpdateBoundsAndOpacity(bool animate) {
DCHECK_EQ(phase_, ShelfLayoutPhase::kAiming) << " Aim before moving!";
phase_ = ShelfLayoutPhase::kMoving;
ShelfNavigationWidget* nav_widget = shelf_->navigation_widget();
HotseatWidget* hotseat_widget = shelf_->hotseat_widget();
StatusAreaWidget* status_widget = shelf_widget_->status_area_widget();
bool force_immediate_shelf_widget_transition = false;
if (visibility_state() == SHELF_AUTO_HIDE &&
auto_hide_state() == SHELF_AUTO_HIDE_HIDDEN && animate) {
gfx::Rect current_shelf_bounds =
shelf_->shelf_widget()->GetWindowBoundsInScreen();
gfx::Rect shelf_target_bounds = shelf_->shelf_widget()->GetTargetBounds();
force_immediate_shelf_widget_transition =
shelf_->SelectValueForShelfAlignment(
current_shelf_bounds.y() >= shelf_target_bounds.y(),
current_shelf_bounds.right() <= shelf_target_bounds.right(),
current_shelf_bounds.x() >= shelf_target_bounds.x());
}
shelf_->shelf_widget()->UpdateLayout(
animate && !force_immediate_shelf_widget_transition);
hotseat_widget->UpdateLayout(animate);
status_widget->UpdateLayout(animate);
nav_widget->UpdateLayout(animate);
shelf_widget_->desk_button_widget()->UpdateLayout(animate);
shelf_->login_shelf_widget()->UpdateLayout(animate);
phase_ = ShelfLayoutPhase::kAtRest;
}
bool ShelfLayoutManager::IsDraggingWindowFromTopOrCaptionArea() const {
if (!display::Screen::Get()->InTabletMode()) {
return false;
}
auto windows =
Shell::Get()->mru_window_tracker()->BuildMruWindowList(kActiveDesk);
for (aura::Window* window : windows) {
WindowState* window_state = WindowState::Get(window);
if (window_state && window_state->is_dragged() &&
(window_state->IsMaximized() || window_state->IsFullscreen()) &&
(window_state->drag_details()->window_component == HTCLIENT ||
window_state->drag_details()->window_component == HTCAPTION)) {
return true;
}
}
return false;
}
void ShelfLayoutManager::UpdateTargetBounds(const State& state,
HotseatState hotseat_target_state) {
shelf_->shelf_widget()->CalculateTargetBounds();
shelf_->status_area_widget()->CalculateTargetBounds();
shelf_->navigation_widget()->CalculateTargetBounds();
if (shelf_->desk_button_widget()->ShouldReserveSpaceFromShelf()) {
CalculateDeskButtonAndHotseatTargetBounds();
} else {
shelf_->hotseat_widget()->ReserveSpaceForAdjacentWidgets(gfx::Insets());
shelf_->hotseat_widget()->CalculateTargetBounds();
}
shelf_->login_shelf_widget()->CalculateTargetBounds();
target_opacity_ = ComputeTargetOpacity(state);
if (drag_status_ == kDragInProgress)
UpdateTargetBoundsForGesture(hotseat_target_state);
}
void ShelfLayoutManager::CalculateTargetBounds() {
if (phase_ == ShelfLayoutPhase::kMoving)
DVLOG(1) << "Careful when switching targets mid-move!";
phase_ = ShelfLayoutPhase::kAiming;
HotseatState hotseat_target_state =
CalculateHotseatState(visibility_state(), auto_hide_state());
UpdateTargetBounds(state_, hotseat_target_state);
}
void ShelfLayoutManager::UpdateWorkAreaInsetsAndNotifyObserversInternal(
const gfx::Rect& shelf_bounds_for_workarea_calculation,
const gfx::Insets& shelf_insets,
const gfx::Insets& in_session_shelf_insets) {
WorkAreaInsets::ForWindow(shelf_widget_->GetNativeWindow())
->SetShelfBoundsAndInsets(shelf_bounds_for_workarea_calculation,
shelf_insets, in_session_shelf_insets);
for (auto& observer : observers_)
observer.OnWorkAreaInsetsChanged();
}
void ShelfLayoutManager::UpdateWorkAreaInsetsAndNotifyObservers(
const gfx::Rect& shelf_bounds_for_workarea_calculation,
const gfx::Insets& shelf_insets,
const gfx::Insets& in_session_shelf_insets) {
UpdateWorkAreaInsetsAndNotifyObserversInternal(
shelf_bounds_for_workarea_calculation, shelf_insets,
in_session_shelf_insets);
auto* shelf_native_window = shelf_widget_->GetNativeWindow();
Shell::Get()
->window_tree_host_manager()
->UpdateWorkAreaOfDisplayNearestWindow(shelf_native_window, shelf_insets);
}
void ShelfLayoutManager::HandleScrollableShelfContainerBoundsChange() {
DeskButtonWidget* desk_button = shelf_widget_->desk_button_widget();
if (desk_button && desk_button->ShouldReserveSpaceFromShelf()) {
CalculateDeskButtonAndHotseatTargetBounds();
desk_button->UpdateLayout(true);
}
}
void ShelfLayoutManager::UpdateTargetBoundsForGesture(
HotseatState hotseat_target_state) {
if (hotseat_target_state == HotseatState::kShownHomeLauncher)
return;
CHECK_EQ(kDragInProgress, drag_status_);
bool shelf_hidden_at_start = false;
if (drag_auto_hide_state_ == SHELF_AUTO_HIDE_HIDDEN &&
visibility_state() == SHELF_AUTO_HIDE &&
auto_hide_state() != SHELF_AUTO_HIDE_SHOWN) {
shelf_hidden_at_start = true;
}
const int shelf_size = ShelfConfig::Get()->shelf_size();
const gfx::Rect available_bounds =
screen_util::GetDisplayBoundsWithShelf(shelf_widget_->GetNativeWindow());
const int baseline = shelf_->SelectValueForShelfAlignment(
available_bounds.bottom() - (shelf_hidden_at_start ? 0 : shelf_size),
available_bounds.x() - (shelf_hidden_at_start ? shelf_size : 0),
available_bounds.right() - (shelf_hidden_at_start ? 0 : shelf_size));
const int shelf_position = baseline + drag_amount_;
if (!display::Screen::Get()->InTabletMode()) {
int shelf_ideal_bound = shelf_->SelectValueForShelfAlignment(
available_bounds.bottom() - shelf_size, 0,
available_bounds.right() - shelf_size);
int adjusted_shelf_position = shelf_->SelectValueForShelfAlignment(
std::max(shelf_ideal_bound, static_cast<int>(shelf_position)),
std::min(shelf_ideal_bound, static_cast<int>(shelf_position)),
std::max(shelf_ideal_bound, static_cast<int>(shelf_position)));
shelf_->shelf_widget()->UpdateTargetBoundsForGesture(
adjusted_shelf_position);
shelf_->hotseat_widget()->UpdateTargetBoundsForGesture(
adjusted_shelf_position);
shelf_->desk_button_widget()->UpdateTargetBoundsForGesture(
adjusted_shelf_position);
shelf_->navigation_widget()->UpdateTargetBoundsForGesture(
adjusted_shelf_position);
shelf_->status_area_widget()->UpdateTargetBoundsForGesture(
adjusted_shelf_position);
return;
}
int adjusted_shelf_position = std::max(available_bounds.bottom() - shelf_size,
static_cast<int>(shelf_position));
const bool move_shelf_with_hotseat =
!Shell::Get()->overview_controller()->InOverviewSession() &&
visibility_state() == SHELF_AUTO_HIDE;
if (move_shelf_with_hotseat) {
if (IsWindowDragInProgress())
adjusted_shelf_position = available_bounds.bottom() - shelf_size;
gfx::Rect updated_target_bounds = shelf_->shelf_widget()->GetTargetBounds();
updated_target_bounds.set_y(adjusted_shelf_position);
shelf_->shelf_widget()->set_target_bounds(updated_target_bounds);
shelf_->navigation_widget()->UpdateTargetBoundsForGesture(
adjusted_shelf_position);
shelf_->status_area_widget()->UpdateTargetBoundsForGesture(
adjusted_shelf_position);
}
const int hotseat_extended_offset =
shelf_->hotseat_widget()->GetHotseatSize() +
Shell::Get()->shelf_config()->hotseat_bottom_padding();
const int hotseat_offset_baseline =
(hotseat_target_state == HotseatState::kExtended)
? -hotseat_extended_offset
: shelf_size;
bool use_hotseat_offset_baseline =
(hotseat_target_state == HotseatState::kExtended &&
visibility_state() == SHELF_AUTO_HIDE) ||
(hotseat_target_state == HotseatState::kHidden &&
visibility_state() != SHELF_AUTO_HIDE);
int hotseat_offset =
std::max(-hotseat_extended_offset,
static_cast<int>(
(use_hotseat_offset_baseline ? hotseat_offset_baseline : 0) +
drag_amount_));
if (IsWindowDragInProgress())
hotseat_offset = -hotseat_extended_offset;
gfx::Rect hotseat_bounds = shelf_->hotseat_widget()->GetTargetBounds();
hotseat_bounds.set_y(hotseat_offset + adjusted_shelf_position);
shelf_->hotseat_widget()->set_target_bounds(hotseat_bounds);
}
void ShelfLayoutManager::UpdateAutoHideForDragDrop(
ScopedDragDropObserver::EventType event_type,
const ui::DropTargetEvent* event) {
DCHECK_EQ(visibility_state(), SHELF_AUTO_HIDE);
if (event_type != ScopedDragDropObserver::EventType::kDragUpdated) {
if (!in_mouse_drag_ && in_drag_drop_ &&
shelf_->shelf_widget()->GetVisibleShelfBounds().Contains(
last_drag_drop_position_in_screen_)) {
last_drag_drop_position_in_screen_ = gfx::Point(0, 0);
UpdateAutoHideState();
}
in_mouse_drag_ = false;
in_drag_drop_ = false;
return;
}
in_drag_drop_ = true;
last_drag_drop_position_in_screen_ = event->root_location();
::wm::ConvertPointToScreen(shelf_->GetWindow()->GetRootWindow(),
&last_drag_drop_position_in_screen_);
bool is_drag_over_shelf =
shelf_->shelf_widget()->GetVisibleShelfBounds().Contains(
last_drag_drop_position_in_screen_);
if (is_drag_over_shelf != last_seen_drag_position_was_over_shelf_) {
last_seen_drag_position_was_over_shelf_ = is_drag_over_shelf;
if (is_drag_over_shelf) {
UpdateAutoHideState();
} else {
StartAutoHideTimer();
}
}
}
void ShelfLayoutManager::UpdateAutoHideStateNow() {
SetState(visibility_state(), false);
StopAutoHideTimer();
}
void ShelfLayoutManager::StartAutoHideTimer() {
auto_hide_timer_.Start(FROM_HERE, base::Milliseconds(kAutoHideDelayMS), this,
&ShelfLayoutManager::UpdateAutoHideStateNow);
}
void ShelfLayoutManager::StopAutoHideTimer() {
auto_hide_timer_.Stop();
mouse_over_shelf_when_auto_hide_timer_started_ = false;
drag_over_shelf_when_auto_hide_timer_started_ = false;
}
gfx::Rect ShelfLayoutManager::GetAutoHideShowShelfRegionInScreen() const {
gfx::Rect shelf_bounds_in_screen =
shelf_->shelf_widget()->GetVisibleShelfBounds();
gfx::Vector2d offset = shelf_->SelectValueForShelfAlignment(
gfx::Vector2d(0, shelf_bounds_in_screen.height()),
gfx::Vector2d(-kMaxAutoHideShowShelfRegionSize, 0),
gfx::Vector2d(shelf_bounds_in_screen.width(), 0));
gfx::Rect show_shelf_region_in_screen = shelf_bounds_in_screen;
show_shelf_region_in_screen += offset;
if (shelf_->IsHorizontalAlignment())
show_shelf_region_in_screen.set_height(kMaxAutoHideShowShelfRegionSize);
else
show_shelf_region_in_screen.set_width(kMaxAutoHideShowShelfRegionSize);
return screen_util::SnapBoundsToDisplayEdge(show_shelf_region_in_screen,
shelf_widget_->GetNativeWindow());
}
ShelfAutoHideState ShelfLayoutManager::CalculateAutoHideState(
ShelfVisibilityState visibility_state) const {
if (is_auto_hide_state_locked_)
return state_.auto_hide_state;
if (!Shell::Get()->session_controller()->IsActiveUserSessionStarted())
return SHELF_AUTO_HIDE_HIDDEN;
if (visibility_state != SHELF_AUTO_HIDE)
return SHELF_AUTO_HIDE_HIDDEN;
if (shelf_->auto_hide_lock())
return state_.auto_hide_state;
if (shelf_->disable_auto_hide()) {
return SHELF_AUTO_HIDE_SHOWN;
}
const bool in_tablet_mode = display::Screen::Get()->InTabletMode();
if (in_tablet_mode &&
Shell::Get()->accessibility_controller()->spoken_feedback().enabled()) {
return SHELF_AUTO_HIDE_SHOWN;
}
if (auto* app_list_controller = Shell::Get()->app_list_controller();
!in_tablet_mode &&
app_list_controller->GetTargetVisibility(display_.id())) {
return SHELF_AUTO_HIDE_SHOWN;
}
if (shelf_widget_->status_area_widget() &&
shelf_widget_->status_area_widget()->ShouldShowShelf()) {
return SHELF_AUTO_HIDE_SHOWN;
}
if (shelf_widget_->IsShowingMenu())
return SHELF_AUTO_HIDE_SHOWN;
if (shelf_widget_->IsActive() || shelf_->navigation_widget()->IsActive() ||
shelf_->hotseat_widget()->IsActive() ||
(shelf_widget_->status_area_widget() &&
shelf_widget_->status_area_widget()->IsActive()) ||
(shelf_->desk_button_widget() &&
shelf_->desk_button_widget()->IsActive())) {
return SHELF_AUTO_HIDE_SHOWN;
}
if (!HasVisibleWindow())
return SHELF_AUTO_HIDE_SHOWN;
if (IsDraggingWindowFromTopOrCaptionArea())
return SHELF_AUTO_HIDE_SHOWN;
if (Shell::Get()->overview_controller() &&
Shell::Get()->overview_controller()->InOverviewSession()) {
return SHELF_AUTO_HIDE_SHOWN;
}
if (drag_status_ == kDragHomeToOverviewInProgress) {
return SHELF_AUTO_HIDE_SHOWN;
}
if (drag_status_ == kDragCompleteInProgress ||
drag_status_ == kDragCancelInProgress ||
drag_status_ == kFlingBubbleLauncherInProgress) {
return drag_auto_hide_state_;
}
if (in_drag_drop_)
return CalculateAutoHideStateBasedOnDragLocation();
if (in_mouse_drag_)
return SHELF_AUTO_HIDE_HIDDEN;
const auto auto_hide_state_from_cursor =
CalculateAutoHideStateBasedOnCursorLocation();
if (auto_hide_state_from_cursor.has_value())
return *auto_hide_state_from_cursor;
if (window_drag_controller_ &&
window_drag_controller_->during_window_restoration()) {
return SHELF_AUTO_HIDE_SHOWN;
}
return SHELF_AUTO_HIDE_HIDDEN;
}
ShelfAutoHideState
ShelfLayoutManager::CalculateAutoHideStateBasedOnDragLocation() const {
gfx::Rect shelf_region = shelf_->shelf_widget()->GetVisibleShelfBounds();
if (shelf_widget_->status_area_widget() &&
shelf_widget_->status_area_widget()->IsMessageBubbleShown() &&
IsVisible()) {
ShelfAlignment alignment = shelf_->alignment();
shelf_region.Inset(gfx::Insets::TLBR(
shelf_->IsHorizontalAlignment() ? -kNotificationBubbleGapHeight : 0,
alignment == ShelfAlignment::kRight ? -kNotificationBubbleGapHeight : 0,
0,
alignment == ShelfAlignment::kLeft ? -kNotificationBubbleGapHeight
: 0));
}
if (shelf_region.Contains(last_drag_drop_position_in_screen_))
return SHELF_AUTO_HIDE_SHOWN;
if ((state_.auto_hide_state == SHELF_AUTO_HIDE_SHOWN ||
drag_over_shelf_when_auto_hide_timer_started_) &&
GetAutoHideShowShelfRegionInScreen().Contains(
last_drag_drop_position_in_screen_)) {
return SHELF_AUTO_HIDE_SHOWN;
}
return SHELF_AUTO_HIDE_HIDDEN;
}
std::optional<ShelfAutoHideState>
ShelfLayoutManager::CalculateAutoHideStateBasedOnCursorLocation() const {
const bool in_tablet_mode = display::Screen::Get()->InTabletMode();
if (in_tablet_mode)
return std::nullopt;
if (!shelf_widget_->IsMouseEventsEnabled())
return SHELF_AUTO_HIDE_HIDDEN;
gfx::Rect shelf_region = shelf_->shelf_widget()->GetVisibleShelfBounds();
if (shelf_widget_->status_area_widget() &&
shelf_widget_->status_area_widget()->IsMessageBubbleShown() &&
IsVisible()) {
ShelfAlignment alignment = shelf_->alignment();
shelf_region.Inset(gfx::Insets::TLBR(
shelf_->IsHorizontalAlignment() ? -kNotificationBubbleGapHeight : 0,
alignment == ShelfAlignment::kRight ? -kNotificationBubbleGapHeight : 0,
0,
alignment == ShelfAlignment::kLeft ? -kNotificationBubbleGapHeight
: 0));
}
gfx::Point cursor_position_in_screen =
display::Screen::Get()->GetCursorScreenPoint();
if (shelf_region.Contains(cursor_position_in_screen) && !in_tablet_mode)
return SHELF_AUTO_HIDE_SHOWN;
if ((state_.auto_hide_state == SHELF_AUTO_HIDE_SHOWN ||
mouse_over_shelf_when_auto_hide_timer_started_) &&
GetAutoHideShowShelfRegionInScreen().Contains(
cursor_position_in_screen)) {
return SHELF_AUTO_HIDE_SHOWN;
}
return std::nullopt;
}
bool ShelfLayoutManager::IsShelfWindow(aura::Window* window) {
if (!window)
return false;
const aura::Window* shelf_window = shelf_widget_->GetNativeWindow();
const aura::Window* navigation_window =
shelf_->navigation_widget()->GetNativeWindow();
const aura::Window* hotseat_window =
shelf_->hotseat_widget()->GetNativeWindow();
const aura::Window* status_area_window =
shelf_widget_->status_area_widget()->GetNativeWindow();
const aura::Window* drag_handle_nudge_window =
GetDragHandleNudgeWindow(shelf_widget_);
const aura::Window* login_shelf_window =
shelf_->login_shelf_widget()->GetNativeWindow();
bool window_in_login_shelf_widget =
(login_shelf_window && login_shelf_window->Contains(window));
bool window_in_desk_button_widget = false;
const aura::Window* desk_button_window =
shelf_->desk_button_widget()->GetNativeWindow();
window_in_desk_button_widget =
(desk_button_window && desk_button_window->Contains(window));
return (shelf_window && shelf_window->Contains(window)) ||
(navigation_window && navigation_window->Contains(window)) ||
(hotseat_window && hotseat_window->Contains(window)) ||
(status_area_window && status_area_window->Contains(window)) ||
(drag_handle_nudge_window &&
drag_handle_nudge_window->Contains(window)) ||
window_in_login_shelf_widget || window_in_desk_button_widget;
}
bool ShelfLayoutManager::IsStatusAreaWindow(aura::Window* window) {
if (!window)
return false;
const aura::Window* status_window =
shelf_widget_->status_area_widget()->GetNativeWindow();
return status_window && status_window->Contains(window);
}
void ShelfLayoutManager::UpdateShelfVisibilityAfterLoginUIChange() {
UpdateVisibilityState(true);
}
float ShelfLayoutManager::ComputeTargetOpacity(const State& state) const {
if (display::Screen::Get()->InTabletMode()) {
auto* app_list_controller = Shell::Get()->app_list_controller();
if (app_list_controller->GetTargetVisibility(display_.id()) &&
!app_list_controller->IsVisible(display_.id())) {
return 1.0f;
}
}
float opacity_when_visible = kDefaultShelfOpacity;
if (dimmed_for_inactivity_) {
opacity_when_visible =
(ComputeShelfBackgroundType() == ShelfBackgroundType::kMaximized)
? kMaximizedShelfDimOpacity
: kFloatingShelfDimOpacity;
}
if (drag_status_ == kDragInProgress ||
state.visibility_state == SHELF_VISIBLE) {
return opacity_when_visible;
}
return (state.visibility_state == SHELF_AUTO_HIDE &&
state.auto_hide_state == SHELF_AUTO_HIDE_SHOWN)
? opacity_when_visible
: 0.0f;
}
bool ShelfLayoutManager::IsShelfHiddenForFullscreen() const {
if (!display::Screen::Get()->InTabletMode() &&
Shell::Get()->app_list_controller()->GetTargetVisibility(display_.id())) {
return false;
}
if (shelf_widget_->status_area_widget() &&
shelf_widget_->status_area_widget()->ShouldShowShelf()) {
return false;
}
const aura::Window* fullscreen_window = GetWindowForFullscreenModeInRoot(
shelf_widget_->GetNativeWindow()->GetRootWindow());
return fullscreen_window &&
WindowState::Get(fullscreen_window)->GetHideShelfWhenFullscreen();
}
bool ShelfLayoutManager::IsShelfAutoHideForFullscreenMaximized() const {
WindowState* active_window = WindowState::ForActiveWindow();
return active_window &&
active_window->autohide_shelf_when_maximized_or_fullscreen();
}
bool ShelfLayoutManager::IsActiveWindowStylusApp() const {
WindowState* active_window_state = WindowState::ForActiveWindow();
if (!active_window_state)
return false;
aura::Window* active_window = active_window_state->window();
if (!active_window || active_window->GetRootWindow() !=
shelf_widget_->GetNativeWindow()->GetRootWindow()) {
return false;
}
const ShelfID active_id =
ShelfID::Deserialize(active_window->GetProperty(kShelfIDKey));
if (active_id.IsNull())
return false;
return base::Contains(kStylusAppIds, active_id.app_id);
}
bool ShelfLayoutManager::StartGestureDrag(
const ui::GestureEvent& gesture_in_screen) {
if (drag_status_ != kDragNone)
return false;
float scroll_y_hint = gesture_in_screen.details().scroll_y_hint();
if (display::Screen::Get()->InTabletMode() &&
((Shell::Get()->app_list_controller()->IsVisible(display_.id()) &&
Shell::Get()->app_list_controller()->GetTargetVisibility(
display_.id())) ||
IsDragOverShelfWithFloatedWindow(gesture_in_screen.location())) &&
scroll_y_hint < 0) {
drag_status_ = kDragHomeToOverviewInProgress;
swipe_home_to_overview_controller_ =
std::make_unique<SwipeHomeToOverviewController>(display_.id());
return true;
}
if (display::Screen::Get()->InTabletMode() &&
Shell::Get()->app_list_controller()->IsVisible(display_.id())) {
return true;
}
if (StartShelfDrag(gesture_in_screen,
gfx::Vector2dF(gesture_in_screen.details().scroll_x_hint(),
scroll_y_hint))) {
return true;
}
if (IsBubbleLauncherShowOnGestureScrollAvailable()) {
drag_status_ = kFlingBubbleLauncherInProgress;
return true;
}
return false;
}
void ShelfLayoutManager::UpdateGestureDrag(
const ui::GestureEvent& gesture_in_screen) {
float scroll_x = gesture_in_screen.details().scroll_x();
float scroll_y = gesture_in_screen.details().scroll_y();
if (drag_status_ == kDragHomeToOverviewInProgress) {
DCHECK(swipe_home_to_overview_controller_);
swipe_home_to_overview_controller_->Drag(
gesture_in_screen.details().bounding_box_f().CenterPoint(), scroll_x,
scroll_y);
return;
}
if (drag_status_ == kFlingBubbleLauncherInProgress) {
if (drag_start_point_in_screen_ == gfx::Point())
drag_start_point_in_screen_ = gesture_in_screen.location();
return;
}
UpdateDrag(gesture_in_screen, scroll_x, scroll_y);
}
void ShelfLayoutManager::AttemptToDragByMouse(
const ui::MouseEvent& mouse_in_screen) {
if (drag_status_ != kDragNone)
return;
drag_status_ = kDragAttempt;
last_mouse_drag_position_in_screen_ = mouse_in_screen.location();
}
void ShelfLayoutManager::StartMouseDrag(const ui::MouseEvent& mouse_in_screen) {
float scroll_y_hint =
mouse_in_screen.y() - last_mouse_drag_position_in_screen_.y();
StartShelfDrag(mouse_in_screen,
gfx::Vector2dF(mouse_in_screen.x() -
last_mouse_drag_position_in_screen_.x(),
scroll_y_hint));
}
void ShelfLayoutManager::UpdateMouseDrag(
const ui::MouseEvent& mouse_in_screen) {
if (drag_status_ == kDragNone)
return;
DCHECK(drag_status_ == kDragAttempt || drag_status_ == kDragInProgress ||
drag_status_ == kDragHomeToOverviewInProgress);
if (drag_status_ == kDragAttempt) {
if (abs(mouse_in_screen.location().y() -
last_mouse_drag_position_in_screen_.y()) < kMouseDragThreshold) {
return;
}
StartMouseDrag(mouse_in_screen);
} else {
int scroll_x = mouse_in_screen.location().x() -
last_mouse_drag_position_in_screen_.x();
int scroll_y = mouse_in_screen.location().y() -
last_mouse_drag_position_in_screen_.y();
UpdateDrag(mouse_in_screen, scroll_x, scroll_y);
last_mouse_drag_position_in_screen_ = mouse_in_screen.location();
}
}
void ShelfLayoutManager::ReleaseMouseDrag(
const ui::MouseEvent& mouse_in_screen) {
if (drag_status_ == kDragNone)
return;
DCHECK(drag_status_ == kDragAttempt ||
drag_status_ == kDragHomeToOverviewInProgress ||
drag_status_ == kDragInProgress);
switch (drag_status_) {
case kDragAttempt:
drag_status_ = kDragNone;
break;
case kDragHomeToOverviewInProgress:
CompleteDragHomeToOverview(mouse_in_screen);
break;
case kDragInProgress:
CompleteDrag(mouse_in_screen);
break;
default:
NOTREACHED();
}
last_mouse_drag_position_in_screen_ = gfx::Point();
}
bool ShelfLayoutManager::IsDragAllowed() const {
SessionControllerImpl* controller = Shell::Get()->session_controller();
if (!controller->NumberOfLoggedInUsers() || controller->IsScreenLocked())
return false;
if (IsShelfHiddenForFullscreen())
return false;
return true;
}
bool ShelfLayoutManager::StartShelfDrag(const ui::LocatedEvent& event_in_screen,
const gfx::Vector2dF& scroll_hint) {
const bool is_tablet_mode = display::Screen::Get()->InTabletMode();
if (Shell::Get()->app_list_controller()->IsVisible(display_.id()) &&
!is_tablet_mode) {
return false;
}
if (!is_tablet_mode &&
shelf_->auto_hide_behavior() != ShelfAutoHideBehavior::kAlways &&
!IsInImmersiveFullscreen()) {
return false;
}
drag_status_ = kDragInProgress;
drag_auto_hide_state_ =
(!Shell::Get()->overview_controller()->InOverviewSession() &&
visibility_state() == SHELF_AUTO_HIDE)
? auto_hide_state()
: SHELF_AUTO_HIDE_SHOWN;
MaybeSetupHotseatDrag(event_in_screen);
if (hotseat_is_in_drag_) {
DCHECK(!hotseat_presentation_time_recorder_);
hotseat_presentation_time_recorder_ =
CreatePresentationTimeHistogramRecorder(
shelf_->hotseat_widget()->GetCompositor(),
"Ash.HotseatTransition.Drag.PresentationTime",
"Ash.HotseatTransition.Drag.PresentationTime.MaxLatency");
}
MaybeUpdateShelfBackground(AnimationChangeType::ANIMATE);
if (hotseat_state() == HotseatState::kExtended &&
visibility_state() == SHELF_VISIBLE) {
drag_amount_ = -(shelf_->hotseat_widget()->GetHotseatSize() +
ShelfConfig::Get()->hotseat_bottom_padding());
} else {
drag_amount_ = is_tablet_mode && IsActiveWindowStylusApp()
? kShelfPalmRejectionSwipeOffset
: 0;
}
const gfx::Rect shelf_bounds =
shelf_->shelf_widget()->GetVisibleShelfBounds();
allow_window_drag_on_extended_hotseat_ =
event_in_screen.location_f().y() >= shelf_bounds.y();
MaybeStartDragWindowFromShelf(event_in_screen, scroll_hint);
return true;
}
void ShelfLayoutManager::MaybeSetupHotseatDrag(
const ui::LocatedEvent& event_in_screen) {
if (!display::Screen::Get()->InTabletMode()) {
return;
}
if (hotseat_state() == HotseatState::kShownHomeLauncher ||
hotseat_state() == HotseatState::kShownClamshell) {
return;
}
if (hotseat_is_in_drag_)
return;
shelf_->hotseat_widget()->StackAtTop();
hotseat_is_in_drag_ = true;
}
void ShelfLayoutManager::UpdateDrag(const ui::LocatedEvent& event_in_screen,
float scroll_x,
float scroll_y) {
const int starting_hotseat_y =
shelf_->hotseat_widget()->GetTargetBounds().y();
if (drag_start_point_in_screen_ == gfx::Point())
drag_start_point_in_screen_ = event_in_screen.location();
drag_amount_ += shelf_->PrimaryAxisValue(scroll_y, scroll_x);
if (event_in_screen.type() == ui::EventType::kScrollFlingStart) {
last_drag_velocity_ =
event_in_screen.AsGestureEvent()->details().velocity_y();
}
if (!IsWindowDragInProgress()) {
LayoutShelf();
}
if (hotseat_is_in_drag_ &&
starting_hotseat_y != shelf_->hotseat_widget()->GetTargetBounds().y()) {
DCHECK(hotseat_presentation_time_recorder_);
hotseat_presentation_time_recorder_->RequestNext();
}
MaybeUpdateWindowDrag(event_in_screen, gfx::Vector2dF(scroll_x, scroll_y));
}
void ShelfLayoutManager::CompleteDrag(const ui::LocatedEvent& event_in_screen) {
std::optional<ShelfWindowDragResult> window_drag_result =
MaybeEndWindowDrag(event_in_screen);
HotseatState old_hotseat_state = hotseat_state();
const bool transitioned_from_overview_to_home =
MaybeEndDragFromOverviewToHome(event_in_screen);
allow_fling_from_overview_to_home_ = true;
DCHECK(!transitioned_from_overview_to_home ||
!window_drag_result.has_value());
std::optional<base::AutoReset<bool>> reset;
if (window_drag_result &&
(*window_drag_result == ShelfWindowDragResult::kGoToOverviewMode ||
*window_drag_result == ShelfWindowDragResult::kGoToSplitviewMode)) {
reset.emplace(&should_hide_hotseat_, true);
}
if (ShouldChangeVisibilityAfterDrag(event_in_screen))
CompleteDragWithChangedVisibility();
else
CancelDrag(window_drag_result);
if (display::Screen::Get()->InTabletMode()) {
std::optional<InAppShelfGestures> gesture_to_record =
CalculateHotseatGestureToRecord(window_drag_result,
transitioned_from_overview_to_home,
old_hotseat_state, hotseat_state());
if (gesture_to_record.has_value()) {
UMA_HISTOGRAM_ENUMERATION(kHotseatGestureHistogramName,
gesture_to_record.value());
}
}
}
void ShelfLayoutManager::CompleteShelfFling(
const ui::LocatedEvent& event_in_screen) {
drag_status_ = kDragNone;
drag_start_point_in_screen_ = gfx::Point();
last_drag_velocity_ = 0;
}
void ShelfLayoutManager::CompleteDragHomeToOverview(
const ui::LocatedEvent& event_in_screen) {
std::optional<float> velocity_y;
if (event_in_screen.type() == ui::EventType::kScrollFlingStart) {
velocity_y = std::make_optional(
event_in_screen.AsGestureEvent()->details().velocity_y());
}
DCHECK(swipe_home_to_overview_controller_);
swipe_home_to_overview_controller_->EndDrag(event_in_screen.location_f(),
velocity_y);
swipe_home_to_overview_controller_.reset();
drag_status_ = kDragNone;
}
void ShelfLayoutManager::CancelDrag(
std::optional<ShelfWindowDragResult> window_drag_result) {
if (drag_status_ == kDragHomeToOverviewInProgress) {
swipe_home_to_overview_controller_->CancelDrag();
swipe_home_to_overview_controller_.reset();
} else {
drag_status_ = kDragCancelInProgress;
UpdateVisibilityState(false);
if (window_drag_controller_.get()) {
if (window_drag_result.has_value())
window_drag_controller_->FinalizeDraggedWindow();
else
MaybeCancelWindowDrag();
}
}
if (hotseat_is_in_drag_) {
shelf_->hotseat_widget()->set_manually_extended(
hotseat_state() == HotseatState::kExtended &&
(!Shell::Get()->overview_controller()->InOverviewSession() ||
SplitViewController::Get(shelf_widget_->GetNativeWindow())
->InSplitViewMode() ||
(window_drag_result.has_value() &&
(window_drag_result.value() ==
ShelfWindowDragResult::kRestoreToOriginalBounds ||
window_drag_result.value() ==
ShelfWindowDragResult::kGoToSplitviewMode))));
hotseat_presentation_time_recorder_.reset();
}
hotseat_is_in_drag_ = false;
drag_status_ = kDragNone;
drag_start_point_in_screen_ = gfx::Point();
last_drag_velocity_ = 0;
}
void ShelfLayoutManager::CompleteDragWithChangedVisibility() {
shelf_widget_->Deactivate();
shelf_widget_->status_area_widget()->Deactivate();
drag_auto_hide_state_ = drag_auto_hide_state_ == SHELF_AUTO_HIDE_SHOWN
? SHELF_AUTO_HIDE_HIDDEN
: SHELF_AUTO_HIDE_SHOWN;
drag_status_ = kDragCompleteInProgress;
UpdateVisibilityState(false);
if (window_drag_controller_.get())
window_drag_controller_->FinalizeDraggedWindow();
drag_status_ = kDragNone;
if (hotseat_is_in_drag_)
hotseat_presentation_time_recorder_.reset();
hotseat_is_in_drag_ = false;
last_drag_velocity_ = 0;
}
bool ShelfLayoutManager::IsSwipingCorrectDirection() {
switch (shelf_->alignment()) {
case ShelfAlignment::kBottom:
case ShelfAlignment::kBottomLocked:
case ShelfAlignment::kRight:
if (drag_auto_hide_state_ == SHELF_AUTO_HIDE_SHOWN)
return drag_amount_ > 0;
return drag_amount_ < 0;
case ShelfAlignment::kLeft:
if (drag_auto_hide_state_ == SHELF_AUTO_HIDE_SHOWN)
return drag_amount_ < 0;
return drag_amount_ > 0;
}
return false;
}
bool ShelfLayoutManager::ShouldChangeVisibilityAfterDrag(
const ui::LocatedEvent& event_in_screen) {
if (visibility_state() == SHELF_VISIBLE)
return false;
if (Shell::Get()->overview_controller()->InOverviewSession())
return false;
if (event_in_screen.type() == ui::EventType::kGestureScrollEnd ||
event_in_screen.type() == ui::EventType::kMouseReleased) {
const gfx::Rect bounds = GetIdealBounds();
const float drag_ratio =
fabs(drag_amount_) /
(shelf_->IsHorizontalAlignment() ? bounds.height() : bounds.width());
return IsSwipingCorrectDirection() &&
drag_ratio > ShelfConfig::Get()->drag_hide_ratio_threshold();
}
if (event_in_screen.type() == ui::EventType::kScrollFlingStart) {
return IsSwipingCorrectDirection();
}
return false;
}
void ShelfLayoutManager::UpdateWorkspaceMask(
WorkspaceWindowState window_state) {
auto* root_window_controller =
RootWindowController::ForWindow(shelf_widget_->GetNativeWindow());
auto* container = root_window_controller->GetContainer(
kShellWindowId_NonLockScreenContainersContainer);
switch (window_state) {
case WorkspaceWindowState::kMaximized:
case WorkspaceWindowState::kFullscreen:
container->layer()->SetMasksToBounds(false);
break;
case WorkspaceWindowState::kDefault:
container->layer()->SetMasksToBounds(true);
break;
}
}
void ShelfLayoutManager::SendA11yAlertForFullscreenWorkspaceState(
WorkspaceWindowState current_workspace_window_state) {
if (previous_workspace_window_state_ != WorkspaceWindowState::kFullscreen &&
current_workspace_window_state == WorkspaceWindowState::kFullscreen) {
Shell::Get()->accessibility_controller()->TriggerAccessibilityAlert(
AccessibilityAlert::WORKSPACE_FULLSCREEN_STATE_ENTERED);
} else if (previous_workspace_window_state_ ==
WorkspaceWindowState::kFullscreen &&
current_workspace_window_state !=
WorkspaceWindowState::kFullscreen) {
Shell::Get()->accessibility_controller()->TriggerAccessibilityAlert(
AccessibilityAlert::WORKSPACE_FULLSCREEN_STATE_EXITED);
}
previous_workspace_window_state_ = current_workspace_window_state;
}
bool ShelfLayoutManager::MaybeStartDragWindowFromShelf(
const ui::LocatedEvent& event_in_screen,
const gfx::Vector2dF& scroll) {
if (!display::Screen::Get()->InTabletMode()) {
return false;
}
if (drag_status_ != kDragInProgress)
return false;
if ((visibility_state() == SHELF_AUTO_HIDE &&
auto_hide_state() == SHELF_AUTO_HIDE_HIDDEN) ||
visibility_state() == SHELF_HIDDEN) {
return false;
}
if (hotseat_state() == HotseatState::kShownHomeLauncher)
return false;
if (hotseat_state() == HotseatState::kHidden) {
const int full_drag_amount =
-shelf_->hotseat_widget()->GetHotseatFullDragAmount();
if (drag_amount_ > full_drag_amount)
return false;
} else if (hotseat_state() == HotseatState::kExtended) {
if (!allow_window_drag_on_extended_hotseat_)
return false;
if (scroll.y() >= 0)
return false;
}
if (window_drag_controller_ &&
window_drag_controller_->IsDraggedWindowAnimating()) {
return false;
}
shelf_widget_->GetDragHandle()->SetWindowDragFromShelfInProgress(true);
aura::Window* window =
GetWindowForDragToHomeOrOverview(event_in_screen.location());
allow_fling_from_overview_to_home_ = !window;
if (!window)
return false;
window_drag_controller_ = std::make_unique<DragWindowFromShelfController>(
window, event_in_screen.location_f());
return true;
}
void ShelfLayoutManager::MaybeUpdateWindowDrag(
const ui::LocatedEvent& event_in_screen,
const gfx::Vector2dF& scroll) {
if (!IsWindowDragInProgress() &&
!MaybeStartDragWindowFromShelf(event_in_screen, scroll)) {
return;
}
DCHECK_EQ(drag_status_, kDragInProgress);
window_drag_controller_->Drag(event_in_screen.location_f(), scroll.x(),
scroll.y());
}
std::optional<ShelfWindowDragResult> ShelfLayoutManager::MaybeEndWindowDrag(
const ui::LocatedEvent& event_in_screen) {
if (!IsWindowDragInProgress())
return std::nullopt;
shelf_widget_->GetDragHandle()->SetWindowDragFromShelfInProgress(false);
DCHECK_EQ(drag_status_, kDragInProgress);
std::optional<float> velocity_y;
if (event_in_screen.type() == ui::EventType::kScrollFlingStart) {
velocity_y = std::make_optional(
event_in_screen.AsGestureEvent()->details().velocity_y());
}
return window_drag_controller_->EndDrag(event_in_screen.location_f(),
velocity_y);
}
bool ShelfLayoutManager::MaybeEndDragFromOverviewToHome(
const ui::LocatedEvent& event_in_screen) {
if (!display::Screen::Get()->InTabletMode()) {
return false;
}
if (!allow_fling_from_overview_to_home_ ||
!Shell::Get()->overview_controller()->InOverviewSession()) {
return false;
}
shelf_widget_->GetDragHandle()->SetWindowDragFromShelfInProgress(false);
if (event_in_screen.type() != ui::EventType::kScrollFlingStart) {
return false;
}
const float velocity_y =
event_in_screen.AsGestureEvent()->details().velocity_y();
if (velocity_y >
-DragWindowFromShelfController::kVelocityToHomeScreenThreshold) {
return false;
}
if (hotseat_state() == HotseatState::kHidden) {
const float kHotseatSizeMultiplier = 2;
ShelfConfig* shelf_config = ShelfConfig::Get();
const int drag_amount_threshold =
-(shelf_config->shelf_size() + shelf_config->hotseat_bottom_padding() +
kHotseatSizeMultiplier * shelf_->hotseat_widget()->GetHotseatSize());
if (drag_amount_ > drag_amount_threshold)
return false;
}
Shell::Get()->app_list_controller()->GoHome(display_.id());
return true;
}
void ShelfLayoutManager::MaybeCancelWindowDrag() {
if (!IsWindowDragInProgress())
return;
DCHECK(drag_status_ == kDragInProgress ||
drag_status_ == kDragCancelInProgress);
shelf_widget_->GetDragHandle()->SetWindowDragFromShelfInProgress(false);
window_drag_controller_->CancelDrag();
}
bool ShelfLayoutManager::IsWindowDragInProgress() const {
return window_drag_controller_ && window_drag_controller_->drag_started();
}
void ShelfLayoutManager::UpdateVisibilityStateForTrayBubbleChange(
bool bubble_shown) {
std::optional<base::AutoReset<bool>> reset;
if (bubble_shown && hotseat_state() == HotseatState::kExtended) {
reset.emplace(&should_hide_hotseat_, true);
}
UpdateVisibilityState(false);
}
void ShelfLayoutManager::HandleShelfAlignmentChange() {
base::AutoReset<bool> immediate_transition(&state_change_animation_disabled_,
true);
shelf_->desk_button_widget()->PrepareForAlignmentChange();
UpdateVisibilityState(true);
}
void ShelfLayoutManager::OnShelfTrayBubbleVisibilityChanged(bool bubble_shown) {
visibility_update_for_tray_callback_.Reset(base::BindOnce(
&ShelfLayoutManager::UpdateVisibilityStateForTrayBubbleChange,
base::Unretained(this), bubble_shown));
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, visibility_update_for_tray_callback_.callback());
}
bool ShelfLayoutManager::IsShelfContainerAnimating() const {
if (!shelf_widget_ || !shelf_widget_->native_widget_private() ||
!shelf_widget_->GetNativeWindow()) {
return false;
}
return shelf_widget_->GetNativeWindow()
->parent()
->layer()
->GetAnimator()
->is_animating();
}
void ShelfLayoutManager::CalculateDeskButtonAndHotseatTargetBounds() {
CHECK(shelf_->desk_button_widget() &&
shelf_->desk_button_widget()->ShouldReserveSpaceFromShelf());
auto reserve_space_for_desk_button_widget = [](Shelf* shelf) {
const int length_needed =
DeskButtonWidget::GetMaxLength(shelf->IsHorizontalAlignment());
shelf->hotseat_widget()->ReserveSpaceForAdjacentWidgets(
shelf->IsHorizontalAlignment()
? (base::i18n::IsRTL() ? gfx::Insets::TLBR(0, 0, 0, length_needed)
: gfx::Insets::TLBR(0, length_needed, 0, 0))
: gfx::Insets::TLBR(length_needed, 0, 0, 0));
};
reserve_space_for_desk_button_widget(shelf_);
shelf_->hotseat_widget()->CalculateTargetBounds();
shelf_->desk_button_widget()->CalculateTargetBounds();
}
}