blob: 6634b00bdc299dcd625cc80aa204f9861eeae6fc [file] [log] [blame]
// Copyright 2018 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/assistant/ui/main_stage/assistant_main_stage.h"
#include <memory>
#include "ash/assistant/assistant_controller.h"
#include "ash/assistant/assistant_interaction_controller.h"
#include "ash/assistant/ui/main_stage/assistant_query_view.h"
#include "ash/assistant/ui/main_stage/suggestion_container_view.h"
#include "ash/assistant/ui/main_stage/ui_element_container_view.h"
#include "ui/views/layout/box_layout.h"
#include "ui/views/layout/fill_layout.h"
#include "ui/views/layout/layout_manager.h"
namespace ash {
namespace {
// StackLayout -----------------------------------------------------------------
// A layout manager which lays out its views atop each other. This differs from
// FillLayout in that we respect the preferred size of views during layout. In
// contrast, FillLayout will cause its views to match the bounds of the host.
class StackLayout : public views::LayoutManager {
public:
StackLayout() = default;
~StackLayout() override = default;
gfx::Size GetPreferredSize(const views::View* host) const override {
gfx::Size preferred_size;
for (int i = 0; i < host->child_count(); ++i)
preferred_size.SetToMax(host->child_at(i)->GetPreferredSize());
return preferred_size;
}
int GetPreferredHeightForWidth(const views::View* host,
int width) const override {
int preferred_height = 0;
for (int i = 0; i < host->child_count(); ++i) {
preferred_height = std::max(host->child_at(i)->GetHeightForWidth(width),
preferred_height);
}
return preferred_height;
}
void Layout(views::View* host) override {
const int host_width = host->GetContentsBounds().width();
for (int i = 0; i < host->child_count(); ++i) {
views::View* child = host->child_at(i);
int child_width = std::min(child->GetPreferredSize().width(), host_width);
int child_height = child->GetHeightForWidth(child_width);
// Children are horizontally centered, top aligned.
child->SetBounds(/*x=*/(host_width - child_width) / 2, /*y=*/0,
child_width, child_height);
}
}
private:
DISALLOW_COPY_AND_ASSIGN(StackLayout);
};
} // namespace
// AssistantMainStage ----------------------------------------------------------
AssistantMainStage::AssistantMainStage(
AssistantController* assistant_controller)
: assistant_controller_(assistant_controller) {
InitLayout(assistant_controller);
// The view hierarchy will be destructed before Shell, which owns
// AssistantController, so AssistantController is guaranteed to outlive the
// AssistantMainStage.
assistant_controller_->interaction_controller()->AddModelObserver(this);
}
AssistantMainStage::~AssistantMainStage() {
assistant_controller_->interaction_controller()->RemoveModelObserver(this);
}
void AssistantMainStage::ChildPreferredSizeChanged(views::View* child) {
PreferredSizeChanged();
}
void AssistantMainStage::ChildVisibilityChanged(views::View* child) {
PreferredSizeChanged();
}
void AssistantMainStage::OnViewBoundsChanged(views::View* view) {
if (view == committed_query_view_) {
UpdateCommittedQueryViewSpacer();
} else if (view == pending_query_view_) {
// The pending query should be bottom aligned in its parent until it is
// committed at which point it is animated to the top.
const int top_offset =
query_layout_container_->height() - pending_query_view_->height();
gfx::Transform transform;
transform.Translate(0, top_offset);
pending_query_view_->layer()->SetTransform(transform);
}
}
void AssistantMainStage::OnViewPreferredSizeChanged(views::View* view) {
PreferredSizeChanged();
}
void AssistantMainStage::OnViewVisibilityChanged(views::View* view) {
PreferredSizeChanged();
}
void AssistantMainStage::InitLayout(AssistantController* assistant_controller) {
SetLayoutManager(std::make_unique<views::FillLayout>());
InitContentLayoutContainer(assistant_controller);
InitQueryLayoutContainer(assistant_controller);
}
void AssistantMainStage::InitContentLayoutContainer(
AssistantController* assistant_controller) {
// Note that we will observe children of |content_layout_container| to handle
// preferred size and visibility change events in AssistantMainStage. This is
// necessary because |content_layout_container| may not change size in
// response to these events, necessitating an explicit layout pass.
views::View* content_layout_container = new views::View();
views::BoxLayout* layout_manager = content_layout_container->SetLayoutManager(
std::make_unique<views::BoxLayout>(
views::BoxLayout::Orientation::kVertical));
// Committed query spacer.
// Note: This view reserves layout space for |committed_query_view_|,
// dynamically mirroring its preferred size and visibility.
committed_query_view_spacer_ = new views::View();
committed_query_view_spacer_->AddObserver(this);
content_layout_container->AddChildView(committed_query_view_spacer_);
// UI element container.
ui_element_container_ = new UiElementContainerView(assistant_controller);
ui_element_container_->AddObserver(this);
content_layout_container->AddChildView(ui_element_container_);
layout_manager->SetFlexForView(ui_element_container_, 1);
// Suggestion container.
suggestion_container_ = new SuggestionContainerView(assistant_controller);
suggestion_container_->AddObserver(this);
// The suggestion container will be animated on its own layer.
suggestion_container_->SetPaintToLayer();
suggestion_container_->layer()->SetFillsBoundsOpaquely(false);
content_layout_container->AddChildView(suggestion_container_);
AddChildView(content_layout_container);
}
void AssistantMainStage::InitQueryLayoutContainer(
AssistantController* assistant_controller) {
// Note that we will observe children of |query_layout_container_| to handle
// preferred size and visibility change events in AssistantMainStage. This is
// necessary because |query_layout_container_| may not change size in response
// to these events, thereby requiring an explicit layout pass.
query_layout_container_ = new views::View();
query_layout_container_->set_can_process_events_within_subtree(false);
query_layout_container_->SetLayoutManager(std::make_unique<StackLayout>());
AddChildView(query_layout_container_);
}
// TODO(dmblack): Animate transformation.
void AssistantMainStage::OnCommittedQueryChanged(const AssistantQuery& query) {
// Clean up any previous committed query.
OnCommittedQueryCleared();
committed_query_view_ = pending_query_view_;
pending_query_view_ = nullptr;
// Update the view and move it to the top of its parent.
committed_query_view_->SetQuery(query);
committed_query_view_->layer()->SetTransform(gfx::Transform());
UpdateCommittedQueryViewSpacer();
UpdateSuggestionContainer();
}
void AssistantMainStage::OnCommittedQueryCleared() {
if (!committed_query_view_)
return;
query_layout_container_->RemoveChildView(committed_query_view_);
delete committed_query_view_;
committed_query_view_ = nullptr;
UpdateCommittedQueryViewSpacer();
}
void AssistantMainStage::OnPendingQueryChanged(const AssistantQuery& query) {
if (!pending_query_view_) {
pending_query_view_ = new AssistantQueryView();
pending_query_view_->AddObserver(this);
// The query view will be animated on its own layer.
pending_query_view_->SetPaintToLayer();
pending_query_view_->layer()->SetFillsBoundsOpaquely(false);
query_layout_container_->AddChildView(pending_query_view_);
UpdateSuggestionContainer();
}
pending_query_view_->SetQuery(query);
}
void AssistantMainStage::OnPendingQueryCleared() {
if (pending_query_view_) {
query_layout_container_->RemoveChildView(pending_query_view_);
delete pending_query_view_;
pending_query_view_ = nullptr;
}
UpdateSuggestionContainer();
}
void AssistantMainStage::UpdateCommittedQueryViewSpacer() {
// The spacer reserves room in the layout for the committed query view, so
// it should match its size.
committed_query_view_spacer_->SetPreferredSize(
committed_query_view_ ? committed_query_view_->size() : gfx::Size());
}
// TODO(dmblack): Animate visibility changes.
void AssistantMainStage::UpdateSuggestionContainer() {
// The suggestion container is only visible when the pending query is not.
// When it is not visible, it should not process events.
bool visible = pending_query_view_ == nullptr;
suggestion_container_->layer()->SetOpacity(visible ? 1.f : 0.f);
suggestion_container_->set_can_process_events_within_subtree(visible ? true
: false);
}
} // namespace ash