[Shelf] Add LoginShelfWidget that contains LoginShelfView
Currently, `LoginShelfView` lives in `ShelfWidget` along with
other Shelf components, such as the opaque background. As a result,
when the login shelf is focused, `ShelfWidget` is stacked above its
sibling widgets, which brings some unexpected side effects.
To fix this issue, this CL adds a widget called `LoginShelfWidget`
to contain `LoginShelfView`. `LoginShelfWidget` is used only when
the corresponding feature flag is enabled.
This CL should not bring any noticeable differences since the flag
is still disabled.
Bug: 1197052
Change-Id: Ib5992a70fcd841976430bab5097339ee6718ed22
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3760071
Reviewed-by: Toni Barzic <tbarzic@chromium.org>
Commit-Queue: Andrew Xu <andrewxu@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1028015}
diff --git a/ash/shelf/login_shelf_widget.cc b/ash/shelf/login_shelf_widget.cc
new file mode 100644
index 0000000..123ff27
--- /dev/null
+++ b/ash/shelf/login_shelf_widget.cc
@@ -0,0 +1,189 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ash/shelf/login_shelf_widget.h"
+
+#include "ash/focus_cycler.h"
+#include "ash/root_window_controller.h"
+#include "ash/shelf/login_shelf_view.h"
+#include "ash/shelf/shelf.h"
+#include "ash/shelf/shelf_layout_manager.h"
+#include "ash/shelf/shelf_widget.h"
+#include "ui/views/accessible_pane_view.h"
+#include "ui/views/focus/focus_search.h"
+#include "ui/views/layout/fill_layout.h"
+
+namespace ash {
+
+// LoginShelfWidget::LoginShelfWidgetDelegate ----------------------------------
+// The delegate of the login shelf widget.
+
+class LoginShelfWidget::LoginShelfWidgetDelegate
+ : public views::AccessiblePaneView,
+ public views::WidgetDelegate {
+ public:
+ explicit LoginShelfWidgetDelegate(Shelf* shelf) : shelf_(shelf) {
+ SetOwnedByWidget(true);
+ set_allow_deactivate_on_esc(true);
+ SetLayoutManager(std::make_unique<views::FillLayout>());
+ }
+
+ LoginShelfWidgetDelegate(const LoginShelfWidgetDelegate&) = delete;
+ LoginShelfWidgetDelegate& operator=(const LoginShelfWidgetDelegate&) = delete;
+
+ ~LoginShelfWidgetDelegate() override = default;
+
+ // views::View:
+ views::View* GetDefaultFocusableChild() override {
+ // `login_shelf_view` is added to the widget delegate as a child when the
+ // login shelf widget is constructed and is removed when the widget is
+ // destructed. Therefore, `login_shelf_view` is not null here.
+ views::View* login_shelf_view = children()[0];
+
+ views::FocusSearch search(login_shelf_view, default_last_focusable_child_,
+ /*accessibility_mode=*/false);
+ views::FocusTraversable* dummy_focus_traversable;
+ views::View* dummy_focus_traversable_view;
+
+ return search.FindNextFocusableView(
+ login_shelf_view,
+ default_last_focusable_child_
+ ? views::FocusSearch::SearchDirection::kBackwards
+ : views::FocusSearch::SearchDirection::kForwards,
+ views::FocusSearch::TraversalDirection::kDown,
+ views::FocusSearch::StartingViewPolicy::kSkipStartingView,
+ views::FocusSearch::AnchoredDialogPolicy::kCanGoIntoAnchoredDialog,
+ &dummy_focus_traversable, &dummy_focus_traversable_view);
+ }
+
+ // views::WidgetDelegate:
+ bool CanActivate() const override {
+ // We don't want mouse clicks to activate us, but we need to allow
+ // activation when the user is using the keyboard (FocusCycler).
+ bool can_active = Shell::Get()->focus_cycler()->widget_activating() ==
+ views::View::GetWidget();
+ return can_active;
+ }
+
+ void ChildPreferredSizeChanged(views::View* child) override {
+ shelf_->shelf_layout_manager()->LayoutShelf(/*animate=*/false);
+ }
+
+ void set_default_last_focusable_child(bool reverse) {
+ default_last_focusable_child_ = reverse;
+ }
+
+ private:
+ const base::raw_ptr<Shelf> shelf_ = nullptr;
+
+ // When true, the default focus of the shelf is the last focusable child.
+ bool default_last_focusable_child_ = false;
+};
+
+// LoginShelfWidget ------------------------------------------------------------
+
+LoginShelfWidget::LoginShelfWidget(Shelf* shelf, aura::Window* container)
+ : shelf_(shelf),
+ delegate_(new LoginShelfWidgetDelegate(shelf)),
+ scoped_session_observer_(this) {
+ DCHECK(container);
+ login_shelf_view_ = delegate_->AddChildView(std::make_unique<LoginShelfView>(
+ RootWindowController::ForWindow(container)
+ ->lock_screen_action_background_controller()));
+
+ views::Widget::InitParams params(
+ views::Widget::InitParams::TYPE_WINDOW_FRAMELESS);
+ params.name = "LoginShelfWidget";
+ params.delegate = delegate_;
+ params.opacity = views::Widget::InitParams::WindowOpacity::kTranslucent;
+ params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
+ params.parent = container;
+ Init(std::move(params));
+ SetContentsView(delegate_);
+
+ // Hide the login shelf by default.
+ Hide();
+
+ // TODO(https://crbug.com/1343114): currently, some logics in shelf check the
+ // login shelf view's visibility. Update them to check whether the login shelf
+ // view is drawn or the widget's visibility. Then remove this line.
+ login_shelf_view_->SetVisible(false);
+}
+
+LoginShelfWidget::~LoginShelfWidget() = default;
+
+void LoginShelfWidget::SetDefaultLastFocusableChild(bool reverse) {
+ delegate_->set_default_last_focusable_child(reverse);
+}
+
+void LoginShelfWidget::SetLoginShelfButtonOpacity(float target_opacity) {
+ login_shelf_view_->SetButtonOpacity(target_opacity);
+}
+
+void LoginShelfWidget::HandleLocaleChange() {
+ login_shelf_view_->HandleLocaleChange();
+}
+
+void LoginShelfWidget::CalculateTargetBounds() {
+ const gfx::Point shelf_origin =
+ shelf_->shelf_widget()->GetTargetBounds().origin();
+
+ gfx::Point origin = gfx::Point(shelf_origin.x(), shelf_origin.y());
+ const int target_bounds_width = delegate_->GetPreferredSize().width();
+ if (shelf_->IsHorizontalAlignment() && base::i18n::IsRTL()) {
+ origin.set_x(shelf_->shelf_widget()->GetTargetBounds().size().width() -
+ target_bounds_width);
+ }
+
+ target_bounds_ =
+ gfx::Rect(origin, {target_bounds_width,
+ shelf_->shelf_widget()->GetTargetBounds().height()});
+}
+
+gfx::Rect LoginShelfWidget::GetTargetBounds() const {
+ return target_bounds_;
+}
+
+void LoginShelfWidget::UpdateLayout(bool animate) {
+ if (GetNativeView()->bounds() == target_bounds_)
+ return;
+
+ SetBounds(target_bounds_);
+}
+
+bool LoginShelfWidget::OnNativeWidgetActivationChanged(bool active) {
+ if (!Widget::OnNativeWidgetActivationChanged(active))
+ return false;
+
+ if (active)
+ delegate_->SetPaneFocusAndFocusDefault();
+
+ return true;
+}
+
+void LoginShelfWidget::OnSessionStateChanged(
+ session_manager::SessionState state) {
+ bool is_active = (state == session_manager::SessionState::ACTIVE);
+
+ // The visibility of `login_shelf_view_` is accessed in different places.
+ // Therefore, ensure the consistency between the widget's visibility and the
+ // view's visibility.
+ if (!is_active && !shelf_->ShouldHideOnSecondaryDisplay(state)) {
+ if (!IsVisible()) {
+ Show();
+ login_shelf_view_->SetVisible(true);
+ }
+ } else if (IsVisible()) {
+ Hide();
+ login_shelf_view_->SetVisible(false);
+ }
+
+ login_shelf_view_->UpdateAfterSessionChange();
+}
+
+void LoginShelfWidget::OnUserSessionAdded(const AccountId& account_id) {
+ login_shelf_view_->UpdateAfterSessionChange();
+}
+
+} // namespace ash