[go: nahoru, domu]

[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