| // Copyright 2017 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/login/ui/views_utils.h" |
| |
| #include <algorithm> |
| #include <memory> |
| |
| #include "ash/login/ui/non_accessible_view.h" |
| #include "ash/public/cpp/shelf_config.h" |
| #include "ui/display/display.h" |
| #include "ui/display/screen.h" |
| #include "ui/views/controls/focus_ring.h" |
| #include "ui/views/controls/highlight_path_generator.h" |
| #include "ui/views/layout/box_layout.h" |
| #include "ui/views/view_targeter_delegate.h" |
| #include "ui/views/widget/widget.h" |
| |
| namespace ash { |
| |
| namespace { |
| |
| class ContainerView : public NonAccessibleView, |
| public views::ViewTargeterDelegate { |
| public: |
| ContainerView() { |
| SetEventTargeter(std::make_unique<views::ViewTargeter>(this)); |
| } |
| ~ContainerView() override = default; |
| |
| // views::ViewTargeterDelegate: |
| bool DoesIntersectRect(const views::View* target, |
| const gfx::Rect& rect) const override { |
| const auto& children = target->children(); |
| const auto hits_child = [target, rect](const views::View* child) { |
| gfx::RectF child_rect(rect); |
| views::View::ConvertRectToTarget(target, child, &child_rect); |
| return child->GetVisible() && |
| child->HitTestRect(gfx::ToEnclosingRect(child_rect)); |
| }; |
| return std::any_of(children.cbegin(), children.cend(), hits_child); |
| } |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(ContainerView); |
| }; |
| |
| } // namespace |
| |
| namespace login_views_utils { |
| |
| std::unique_ptr<views::View> WrapViewForPreferredSize( |
| std::unique_ptr<views::View> view) { |
| auto proxy = std::make_unique<NonAccessibleView>(); |
| auto layout_manager = std::make_unique<views::BoxLayout>( |
| views::BoxLayout::Orientation::kVertical); |
| layout_manager->set_cross_axis_alignment( |
| views::BoxLayout::CrossAxisAlignment::kStart); |
| proxy->SetLayoutManager(std::move(layout_manager)); |
| proxy->AddChildView(std::move(view)); |
| return proxy; |
| } |
| |
| bool ShouldShowLandscape(const views::Widget* widget) { |
| // |widget| is null when the view is being constructed. Default to landscape |
| // in that case. A new layout will happen when the view is attached to a |
| // widget (see LockContentsView::AddedToWidget), which will let us fetch the |
| // correct display orientation. |
| if (!widget) |
| return true; |
| |
| // Get the orientation for |widget|. |
| const display::Display& display = |
| display::Screen::GetScreen()->GetDisplayNearestWindow( |
| widget->GetNativeWindow()); |
| |
| // The display bounds are updated after a rotation. This means that if the |
| // device has resolution 800x600, and the rotation is |
| // display::Display::ROTATE_0, bounds() is 800x600. On |
| // display::Display::ROTATE_90, bounds() is 600x800. |
| // |
| // ash/login/ui assumes landscape means width>height, and portrait means |
| // height>width. |
| // |
| // Considering the actual rotation of the device introduces edge-cases, ie, |
| // when the device resolution in display::Display::ROTATE_0 is 768x1024, such |
| // as in https://crbug.com/858858. |
| return display.bounds().width() > display.bounds().height(); |
| } |
| |
| bool HasFocusInAnyChildView(views::View* view) { |
| // Find the topmost ancestor of the focused view, or |view|, whichever comes |
| // first. |
| views::View* search = view->GetFocusManager()->GetFocusedView(); |
| while (search && search != view) |
| search = search->parent(); |
| return search == view; |
| } |
| |
| views::Label* CreateBubbleLabel(const std::u16string& message, |
| views::View* view_defining_max_width, |
| SkColor color, |
| const gfx::FontList& font_list, |
| int line_height) { |
| views::Label* label = |
| new views::Label(message, views::style::CONTEXT_DIALOG_BODY_TEXT); |
| label->SetAutoColorReadabilityEnabled(false); |
| label->SetHorizontalAlignment(gfx::ALIGN_LEFT); |
| label->SetEnabledColor(color); |
| label->SetSubpixelRenderingEnabled(false); |
| label->SetFontList(font_list); |
| label->SetLineHeight(line_height); |
| if (view_defining_max_width != nullptr) { |
| label->SetMultiLine(true); |
| label->SetAllowCharacterBreak(true); |
| // Make sure to set a maximum label width, otherwise text wrapping will |
| // significantly increase width and layout may not work correctly if |
| // the input string is very long. |
| label->SetMaximumWidth(view_defining_max_width->GetPreferredSize().width()); |
| } |
| return label; |
| } |
| |
| views::View* GetBubbleContainer(views::View* view) { |
| views::View* v = view; |
| while (v->parent() != nullptr) |
| v = v->parent(); |
| |
| views::View* root_view = v; |
| // An arbitrary id that no other child of root view should use. |
| const int kMenuContainerId = 1000; |
| views::View* container = nullptr; |
| for (auto* child : root_view->children()) { |
| if (child->GetID() == kMenuContainerId) { |
| container = child; |
| break; |
| } |
| } |
| |
| if (!container) { |
| container = root_view->AddChildView(std::make_unique<ContainerView>()); |
| container->SetID(kMenuContainerId); |
| } |
| |
| return container; |
| } |
| |
| gfx::Point CalculateBubblePositionBeforeAfterStrategy(gfx::Rect anchor, |
| gfx::Size bubble, |
| gfx::Rect bounds) { |
| gfx::Rect result(anchor.x() - bubble.width(), anchor.y(), bubble.width(), |
| bubble.height()); |
| // Trying to show before (on the left side in LTR). |
| // If there is not enough space show after (on the right side in LTR). |
| if (result.x() < bounds.x()) { |
| result.Offset(anchor.width() + result.width(), 0); |
| } |
| result.AdjustToFit(bounds); |
| return result.origin(); |
| } |
| |
| gfx::Point CalculateBubblePositionAfterBeforeStrategy(gfx::Rect anchor, |
| gfx::Size bubble, |
| gfx::Rect bounds) { |
| gfx::Rect result(anchor.x() + anchor.width(), anchor.y(), bubble.width(), |
| bubble.height()); |
| // Trying to show after (on the right side in LTR). |
| // If there is not enough space show before (on the left side in LTR). |
| if (result.right() > bounds.right()) { |
| result.Offset(-anchor.width() - result.width(), 0); |
| } |
| result.AdjustToFit(bounds); |
| return result.origin(); |
| } |
| |
| void ConfigureRectFocusRingCircleInkDrop(views::View* view, |
| views::FocusRing* focus_ring, |
| base::Optional<int> radius) { |
| DCHECK(view); |
| DCHECK(focus_ring); |
| focus_ring->SetColor(ShelfConfig::Get()->shelf_focus_border_color()); |
| focus_ring->SetPathGenerator( |
| std::make_unique<views::RectHighlightPathGenerator>()); |
| |
| if (radius) { |
| views::InstallFixedSizeCircleHighlightPathGenerator(view, *radius); |
| } else { |
| views::InstallCircleHighlightPathGenerator(view); |
| } |
| } |
| |
| } // namespace login_views_utils |
| } // namespace ash |