Commit 2d90fd89 authored by Christopher Grant's avatar Christopher Grant Committed by Commit Bot

VR: Allow linear layout to target a specified size

In most cases, linear layouts expand to contain all children. However,
in some cases, we want elements to fit into a specified space.  In this
case, one element can now be tagged as allowing sizing by its parent,
and LinearLayout can be configured to adjust that size according to the
other elements it needs to fit.

The first concrete example of this is a URL bar, where the text field
consumes all remaining space after any icons or indicators are added.

BUG=806886

Cq-Include-Trybots: master.tryserver.chromium.android:android_optional_gpu_tests_rel;master.tryserver.chromium.linux:linux_optional_gpu_tests_rel;master.tryserver.chromium.linux:linux_vr;master.tryserver.chromium.mac:mac_optional_gpu_tests_rel;master.tryserver.chromium.win:win_optional_gpu_tests_rel
Change-Id: Ib9261c3b882f248aec2269a660cad9e9c6d8bbbf
Reviewed-on: https://chromium-review.googlesource.com/962712
Commit-Queue: Christopher Grant <cjgrant@chromium.org>
Reviewed-by: default avatarIan Vollick <vollick@chromium.org>
Cr-Commit-Position: refs/heads/master@{#543144}
parent da202f03
......@@ -23,19 +23,34 @@ float GetExtent(const UiElement& element, bool horizontal) {
LinearLayout::LinearLayout(Direction direction) : direction_(direction) {}
LinearLayout::~LinearLayout() {}
void LinearLayout::LayOutChildren() {
bool horizontal =
direction_ == LinearLayout::kLeft || direction_ == LinearLayout::kRight;
float total_extent = -margin_;
float minor_extent = 0;
bool LinearLayout::SizeAndLayOut() {
bool changed = false;
for (auto& child : children()) {
if (child->requires_layout()) {
total_extent += GetExtent(*child, horizontal) + margin_;
minor_extent = std::max(minor_extent, GetExtent(*child, !horizontal));
if (layout_length > 0.0f) {
UiElement* element_to_resize = nullptr;
for (auto& child : children()) {
if (child->resizable_by_layout()) {
DCHECK_EQ(nullptr, element_to_resize);
element_to_resize = child.get();
} else {
changed |= child->SizeAndLayOut();
}
}
DCHECK_NE(element_to_resize, nullptr);
changed |= AdjustResizableElement(element_to_resize);
changed |= element_to_resize->SizeAndLayOut();
} else {
for (auto& child : children()) {
changed |= child->SizeAndLayOut();
}
}
changed |= PrepareToDraw();
DoLayOutChildren();
return changed;
}
void LinearLayout::LayOutChildren() {
float x_factor = 0.f;
float y_factor = 0.f;
switch (direction_) {
......@@ -53,6 +68,11 @@ void LinearLayout::LayOutChildren() {
break;
}
float total_extent;
float minor_extent;
GetTotalExtent(nullptr, &total_extent, &minor_extent);
bool horizontal = Horizontal();
float cumulative_offset = -0.5 * total_extent;
for (auto& child : children()) {
if (!child->requires_layout())
......@@ -67,4 +87,47 @@ void LinearLayout::LayOutChildren() {
!horizontal ? total_extent : minor_extent);
}
bool LinearLayout::Horizontal() const {
return direction_ == LinearLayout::kLeft ||
direction_ == LinearLayout::kRight;
}
void LinearLayout::GetTotalExtent(const UiElement* element_to_exclude,
float* major_extent,
float* minor_extent) const {
*major_extent = -margin_;
*minor_extent = 0.f;
bool horizontal = Horizontal();
for (auto& child : children()) {
if (child->requires_layout()) {
*major_extent += margin_;
if (child.get() != element_to_exclude) {
*major_extent += GetExtent(*child, horizontal);
*minor_extent = std::max(*minor_extent, GetExtent(*child, !horizontal));
}
}
}
}
bool LinearLayout::AdjustResizableElement(UiElement* element_to_resize) {
// Figure out how much space is available for the variable element.
float minimum_total = 0;
float minor = 0;
GetTotalExtent(element_to_resize, &minimum_total, &minor);
float extent = layout_length - minimum_total;
extent = std::max(extent, 0.f);
auto new_size = element_to_resize->size();
if (Horizontal())
new_size.set_width(extent);
else
new_size.set_height(extent);
if (element_to_resize->size() == new_size)
return false;
element_to_resize->SetSize(new_size.width(), new_size.height());
return true;
}
} // namespace vr
......@@ -18,12 +18,33 @@ class LinearLayout : public UiElement {
void set_margin(float margin) { margin_ = margin; }
void set_direction(Direction direction) { direction_ = direction; }
void set_layout_length(float extent) { layout_length = extent; }
// UiElement overrides.
bool SizeAndLayOut() override;
void LayOutChildren() override;
private:
bool Horizontal() const;
// Compute the total extents of all layout-enabled children, including margin.
// Optionally, an element to exclude may be specified, allowing the layout to
// compute how much space is left for that element.
void GetTotalExtent(const UiElement* element_to_exclude,
float* major_extent,
float* minor_extent) const;
// Sets the specified element to a size that ensures the overall layout totals
// its own specified extents.
bool AdjustResizableElement(UiElement* element_to_resize);
Direction direction_;
float margin_ = 0.0f;
// If non-zero, LinearLayout will look for an element tagged as allowing
// sizing by its parent, and set that element's size such that the total
// layout's length is attained.
float layout_length = 0.0f;
};
} // namespace vr
......
......@@ -140,4 +140,40 @@ TEST(LinearLayout, NestedLayouts) {
EXPECT_FLOAT_EQ(p_rect_c->y(), -10);
}
TEST(LinearLayout, SpecifiedMajorExtent) {
auto layout = std::make_unique<LinearLayout>(LinearLayout::kRight);
for (int i = 0; i < 3; i++) {
auto element = std::make_unique<UiElement>();
element->SetSize(1.f, 1.f);
layout->AddChild(std::move(element));
}
LinearLayout* p_layout = layout.get();
UiElement* p_resizable_child = layout->children()[1].get();
auto scene = std::make_unique<UiScene>();
scene->AddUiElement(kRoot, std::move(layout));
scene->OnBeginFrame(MicrosecondsToTicks(0), kStartHeadPose);
EXPECT_FLOAT_EQ(p_layout->size().width(), 3.f);
// Element grows to fit.
p_layout->set_layout_length(3.5f);
p_resizable_child->set_resizable_by_layout(true);
scene->OnBeginFrame(MicrosecondsToTicks(1), kStartHeadPose);
EXPECT_FLOAT_EQ(p_layout->size().width(), 3.5f);
EXPECT_FLOAT_EQ(p_resizable_child->size().width(), 1.5f);
// Element shrinks to fit.
p_layout->set_layout_length(2.5f);
scene->OnBeginFrame(MicrosecondsToTicks(0), kStartHeadPose);
EXPECT_FLOAT_EQ(p_layout->size().width(), 2.5f);
EXPECT_FLOAT_EQ(p_resizable_child->size().width(), 0.5f);
// Element shrinks to 0 if there's no size for it.
p_layout->set_layout_length(1.5f);
scene->OnBeginFrame(MicrosecondsToTicks(0), kStartHeadPose);
EXPECT_FLOAT_EQ(p_layout->size().width(), 2.0f);
EXPECT_FLOAT_EQ(p_resizable_child->size().width(), 0.f);
}
} // namespace vr
......@@ -674,6 +674,16 @@ bool UiElement::IsAnimatingProperty(TargetProperty property) const {
return animation_.IsAnimatingProperty(static_cast<int>(property));
}
bool UiElement::SizeAndLayOut() {
bool changed = false;
for (auto& child : children_) {
changed |= child->SizeAndLayOut();
}
changed |= PrepareToDraw();
DoLayOutChildren();
return changed;
}
void UiElement::DoLayOutChildren() {
LayOutChildren();
if (!bounds_contain_children_) {
......
......@@ -378,6 +378,10 @@ class UiElement : public cc::AnimationTarget {
void RemoveKeyframeModel(int keyframe_model_id);
bool IsAnimatingProperty(TargetProperty property) const;
// Recursive method that sizes and lays out element subtrees. This method may
// be overridden by elements that have custom layout requirements.
virtual bool SizeAndLayOut();
void DoLayOutChildren();
// Handles positioning adjustments for children. This will be overridden by
......@@ -454,6 +458,11 @@ class UiElement : public cc::AnimationTarget {
// that override element hover and click methods must manage their own sounds.
void SetSounds(SoundId hover, SoundId click, AudioDelegate* delegate);
bool resizable_by_layout() { return resizable_by_layout_; }
void set_resizable_by_layout(bool resizable) {
resizable_by_layout_ = resizable;
}
protected:
Animation& animation() { return animation_; }
......@@ -583,6 +592,9 @@ class UiElement : public cc::AnimationTarget {
SoundId hover_sound_id_ = kSoundNone;
SoundId click_sound_id_ = kSoundNone;
// Indicates that this element may be resized by parent layout elements.
bool resizable_by_layout_ = false;
DISALLOW_COPY_AND_ASSIGN(UiElement);
};
......
......@@ -95,9 +95,8 @@ bool UiScene::OnBeginFrame(const base::TimeTicks& current_time,
}
{
TRACE_EVENT0("gpu", "UiScene::OnBeginFrame.UpdateTexturesAndSizes");
TRACE_EVENT0("gpu", "UiScene::OnBeginFrame.UpdateLayout");
// Update textures and sizes.
// TODO(mthiesse): We should really only be updating the sizes here, and not
// actually redrawing the textures because we draw all of the textures as a
// second phase after OnBeginFrame, once we've processed input. For now this
......@@ -106,10 +105,13 @@ bool UiScene::OnBeginFrame(const base::TimeTicks& current_time,
// with their current state, and changing anything other than texture
// synchronously in response to input should be prohibited.
for (auto& element : *root_element_) {
if (element.PrepareToDraw())
scene_dirty = true;
element.set_update_phase(UiElement::kUpdatedTexturesAndSizes);
}
if (root_element_->SizeAndLayOut())
scene_dirty = true;
for (auto& element : *root_element_) {
element.set_update_phase(UiElement::kUpdatedLayout);
}
}
if (!scene_dirty) {
......@@ -121,19 +123,6 @@ bool UiScene::OnBeginFrame(const base::TimeTicks& current_time,
return false;
}
{
TRACE_EVENT0("gpu", "UiScene::OnBeginFrame.UpdateLayout");
// Update layout, which depends on size. Note that the layout phase changes
// the size of layout-type elements, as they adjust to fit the cumulative
// size of their children. This must be done in reverse order, such that
// children are correctly sized when laid out by their parent.
for (auto& element : base::Reversed(*root_element_)) {
element.DoLayOutChildren();
element.set_update_phase(UiElement::kUpdatedLayout);
}
}
{
TRACE_EVENT0("gpu", "UiScene::OnBeginFrame.UpdateWorldSpaceTransform");
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment