1 // Copyright 2018 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "chrome/browser/vr/elements/scrollable_element.h"
6 
7 #include "base/numerics/ranges.h"
8 #include "chrome/browser/vr/input_event.h"
9 
10 namespace vr {
11 
12 namespace {
13 
14 constexpr float kScrollScaleFactor = 1.0f / 400.0f;
15 constexpr float kFloatTolerance = FLT_EPSILON * 2.0f;
16 
SizeEquals(const gfx::SizeF & a,const gfx::SizeF & b)17 bool SizeEquals(const gfx::SizeF& a, const gfx::SizeF& b) {
18   return base::IsApproximatelyEqual(a.width(), b.width(), kFloatTolerance) &&
19          base::IsApproximatelyEqual(a.height(), b.height(), kFloatTolerance);
20 }
21 
22 }  // namespace
23 
ScrollableElement(Orientation orientation)24 ScrollableElement::ScrollableElement(Orientation orientation)
25     : orientation_(orientation) {
26   set_clip_descendants(true);
27   set_bounds_contain_children(true);
28 
29   auto inner_element = std::make_unique<UiElement>();
30   inner_element->set_bounds_contain_children(true);
31   inner_element_ = inner_element.get();
32   AddChild(std::move(inner_element));
33 }
34 
35 ScrollableElement::~ScrollableElement() = default;
36 
set_max_span(float span)37 void ScrollableElement::set_max_span(float span) {
38   DCHECK(span > 0.0f);
39   max_span_ = span;
40 }
41 
OnSetSize(const gfx::SizeF & size)42 void ScrollableElement::OnSetSize(const gfx::SizeF& size) {
43   // If there is nothing to scroll, the element declares itself as
44   // non-scrollable for targeting purposes.
45   set_scrollable(size.width() < inner_element_->size().width() ||
46                  size.height() < inner_element_->size().height());
47 }
48 
SetScrollAnchoring(LayoutAlignment anchoring)49 void ScrollableElement::SetScrollAnchoring(LayoutAlignment anchoring) {
50   scrolling_anchoring_ = anchoring;
51   SetInitialScroll();
52 }
53 
ComputeScrollSpan() const54 float ScrollableElement::ComputeScrollSpan() const {
55   float scroll_span;
56   if (orientation_ == kVertical) {
57     scroll_span = inner_element_->size().height() - size().height();
58   } else {
59     scroll_span = inner_element_->size().width() - size().width();
60   }
61   return std::max(scroll_span, 0.0f);
62 }
63 
SetInitialScroll()64 void ScrollableElement::SetInitialScroll() {
65   float half_scroll_span = ComputeScrollSpan() / 2.0f;
66   if (scrolling_anchoring_ == BOTTOM || scrolling_anchoring_ == LEFT) {
67     scroll_offset_ = half_scroll_span;
68   } else if (scrolling_anchoring_ == TOP || scrolling_anchoring_ == RIGHT) {
69     scroll_offset_ = -half_scroll_span;
70   } else {
71     scroll_offset_ = 0.0f;
72   }
73 }
74 
ComputeContributingChildrenBounds()75 gfx::RectF ScrollableElement::ComputeContributingChildrenBounds() {
76   auto size = inner_element_->size();
77   if (orientation_ == kHorizontal) {
78     size.set_width(std::min(size.width(), max_span_));
79   } else {
80     size.set_height(std::min(size.height(), max_span_));
81   }
82   return gfx::RectF(size);
83 }
84 
LayOutNonContributingChildren()85 void ScrollableElement::LayOutNonContributingChildren() {
86   if (!SizeEquals(inner_size_, inner_element_->size())) {
87     inner_size_ = inner_element_->size();
88     SetInitialScroll();
89   }
90   if (orientation_ == kVertical) {
91     inner_element_->SetLayoutOffset(0.0f, scroll_offset_);
92   } else {
93     inner_element_->SetLayoutOffset(scroll_offset_, 0.0f);
94   }
95 }
96 
AddScrollingChild(std::unique_ptr<UiElement> child)97 void ScrollableElement::AddScrollingChild(std::unique_ptr<UiElement> child) {
98   inner_element_->AddChild(std::move(child));
99 }
100 
OnScrollBegin(std::unique_ptr<InputEvent> gesture,const gfx::PointF & position)101 void ScrollableElement::OnScrollBegin(std::unique_ptr<InputEvent> gesture,
102                                       const gfx::PointF& position) {
103   cached_transition_ = animation().transition();
104   animation().set_transition(Transition());
105 }
106 
OnScrollUpdate(std::unique_ptr<InputEvent> gesture,const gfx::PointF & position)107 void ScrollableElement::OnScrollUpdate(std::unique_ptr<InputEvent> gesture,
108                                        const gfx::PointF& position) {
109   float half_scroll_span = ComputeScrollSpan() / 2.0f;
110   if (orientation_ == kHorizontal) {
111     scroll_offset_ -= gesture->scroll_data.delta_x * kScrollScaleFactor;
112   } else {
113     scroll_offset_ -= gesture->scroll_data.delta_y * kScrollScaleFactor;
114   }
115   scroll_offset_ =
116       base::ClampToRange(scroll_offset_, -half_scroll_span, half_scroll_span);
117 }
118 
OnScrollEnd(std::unique_ptr<InputEvent> gesture,const gfx::PointF & position)119 void ScrollableElement::OnScrollEnd(std::unique_ptr<InputEvent> gesture,
120                                     const gfx::PointF& position) {
121   animation().set_transition(cached_transition_);
122 }
123 
124 }  // namespace vr
125