| Index: ui/views/layout/align_layout_state.cc
|
| diff --git a/ui/views/layout/align_layout_state.cc b/ui/views/layout/align_layout_state.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..8c8eaf2830a862046dc10dc5ce3ecbd122a4ea08
|
| --- /dev/null
|
| +++ b/ui/views/layout/align_layout_state.cc
|
| @@ -0,0 +1,347 @@
|
| +// Copyright (c) 2011 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 "ui/views/layout/align_layout_state.h"
|
| +
|
| +#include "base/logging.h"
|
| +
|
| +namespace views {
|
| +
|
| +// This special value represents the absence of the align attribute.
|
| +static constexpr Align Align_None =
|
| + static_cast<Align>(static_cast<int>(Align::Content) + 1);
|
| +
|
| +AlignLayoutState::AnchorState::AnchorState()
|
| + : anchors({Anchor::Top, Anchor::Left}), anchor_basis(0, 0) {}
|
| +
|
| +AlignLayoutState::AnchorState::AnchorState(Anchors anchors)
|
| + : anchors(anchors), anchor_basis(0, 0) {}
|
| +
|
| +AlignLayoutState::AnchorState::AnchorState(const AnchorState& state)
|
| + : anchors(state.anchors), anchor_basis(state.anchor_basis) {}
|
| +
|
| +AlignLayoutState::AnchorState::~AnchorState() {}
|
| +
|
| +AlignLayoutState::AlignLayoutState() {}
|
| +
|
| +AlignLayoutState::~AlignLayoutState() {}
|
| +
|
| +void AlignLayoutState::AlignView(View* view, Align align) {
|
| + AlignMap::iterator pos = align_map_.find(view);
|
| + if (pos == align_map_.end())
|
| + align_map_.emplace(std::make_pair(view, align));
|
| + else
|
| + pos->second = align;
|
| + UpdateAnchorAlign(view, align);
|
| +}
|
| +
|
| +void AlignLayoutState::AnchorView(View* view, Anchors anchors) {
|
| + AnchorMap::iterator pos = anchor_map_.find(view);
|
| + if (pos == anchor_map_.end())
|
| + pos = anchor_map_.emplace(std::make_pair(view, AnchorState(anchors))).first;
|
| + else
|
| + pos->second.anchors = anchors;
|
| + UpdateAnchorBasis(view);
|
| +}
|
| +
|
| +void AlignLayoutState::AnchorView(
|
| + View* view,
|
| + const std::initializer_list<Anchor>& anchors) {
|
| + AnchorView(view, Anchors(anchors));
|
| +}
|
| +
|
| +void AlignLayoutState::AlignViews(View* host,
|
| + views::Align align,
|
| + gfx::Rect& contents) {
|
| + View::Views align_list;
|
| + for (int i = 0; i < host->child_count(); ++i) {
|
| + Align child_align;
|
| + View* child = host->child_at(i);
|
| + if (child->visible() && FindAlign(child, &child_align)
|
| + ? child_align == align
|
| + : align == Align_None) {
|
| + View::Views::iterator j = align_list.begin();
|
| + while (j < align_list.end() && !ShouldInsert(child, *j, align))
|
| + j++;
|
| + align_list.insert(j, child);
|
| + }
|
| + }
|
| + for (View* child : align_list) {
|
| + PlaceView(child, align, contents);
|
| + UpdateAnchorBasis(child);
|
| + }
|
| +}
|
| +
|
| +bool AlignLayoutState::FindAlign(View* view, Align* align) {
|
| + AlignMap::iterator pos = align_map_.find(view);
|
| + if (pos != align_map_.end()) {
|
| + if (align)
|
| + *align = pos->second;
|
| + return true;
|
| + }
|
| + return false;
|
| +}
|
| +
|
| +bool AlignLayoutState::FindAlign(const View* view, Align* align) const {
|
| + return const_cast<AlignLayoutState*>(this)->FindAlign(const_cast<View*>(view),
|
| + align);
|
| +}
|
| +
|
| +bool AlignLayoutState::FindAnchorState(View* view, AnchorState** state) {
|
| + AnchorMap::iterator pos = anchor_map_.find(view);
|
| + if (pos != anchor_map_.end()) {
|
| + if (state)
|
| + *state = &pos->second;
|
| + return true;
|
| + }
|
| + return false;
|
| +}
|
| +
|
| +void AlignLayoutState::InternalUpdateAnchorBasis(View* view,
|
| + AnchorMap::iterator pos) {
|
| + AnchorState& state = pos->second;
|
| + gfx::Point center_point = view->bounds().CenterPoint();
|
| + if (state.anchors.Has(Anchor::Right))
|
| + if (state.anchors.Has(Anchor::Left))
|
| + state.anchor_basis.set_x(view->width());
|
| + else
|
| + state.anchor_basis.set_x(view->x());
|
| + else
|
| + state.anchor_basis.set_x(center_point.x());
|
| + if (state.anchors.Has(Anchor::Bottom))
|
| + if (state.anchors.Has(Anchor::Top))
|
| + state.anchor_basis.set_y(view->height());
|
| + else
|
| + state.anchor_basis.set_y(view->y());
|
| + else
|
| + state.anchor_basis.set_y(center_point.y());
|
| +}
|
| +
|
| +void AlignLayoutState::Installed(View* host) {
|
| + last_host_size_ = host->size();
|
| +}
|
| +
|
| +void AlignLayoutState::Layout(View* host) {
|
| + static const Align alignstyle[] = {
|
| + views::Align::Top, views::Align::Bottom, views::Align::Left,
|
| + views::Align::Right, views::Align::Content, Align_None};
|
| + if (ShouldAlign(host)) {
|
| + gfx::Rect contents = host->GetContentsBounds();
|
| + for (std::size_t i = 0; i < sizeof(alignstyle) / sizeof(alignstyle[0]); i++)
|
| + AlignViews(host, alignstyle[i], contents);
|
| + last_host_size_ = host->size();
|
| + }
|
| +}
|
| +
|
| +void AlignLayoutState::NoAlignView(View* view) {
|
| + AlignMap::iterator pos = align_map_.find(view);
|
| + if (pos != align_map_.end())
|
| + align_map_.erase(pos);
|
| +}
|
| +
|
| +void AlignLayoutState::NoAnchorView(View* view) {
|
| + AnchorMap::iterator pos = anchor_map_.find(view);
|
| + if (pos != anchor_map_.end())
|
| + anchor_map_.erase(pos);
|
| +}
|
| +
|
| +gfx::Size AlignLayoutState::GetPreferredSize(const View* host) const {
|
| + return gfx::Size(0, GetPreferredHeightForWidth(host, 0));
|
| +}
|
| +
|
| +int AlignLayoutState::GetPreferredHeightForWidth(const View* host,
|
| + int width) const {
|
| + int height = 0;
|
| + for (int i = 0; i < host->child_count(); ++i) {
|
| + Align align;
|
| + const View* child = host->child_at(i);
|
| + if (child->visible() && FindAlign(child, &align) &&
|
| + (align == views::Align::Top || align == views::Align::Bottom ||
|
| + align == views::Align::Content)) {
|
| + height += child->GetPreferredSize().height();
|
| + }
|
| + }
|
| + return height;
|
| +}
|
| +
|
| +static int MulDiv(int Number, int Numerator, int Denominator) {
|
| + return static_cast<int>((static_cast<int64_t>(Number) * Numerator) /
|
| + Denominator);
|
| +}
|
| +
|
| +void AlignLayoutState::PlaceView(View* view,
|
| + views::Align align,
|
| + gfx::Rect& contents) {
|
| + AnchorState state;
|
| + AnchorState* anchor_state = &state;
|
| + FindAnchorState(view, &anchor_state);
|
| + if (align == Align_None ||
|
| + anchor_state->anchors != AnchorContent::AnchorAlign(align)) {
|
| + if (!last_host_size_.IsEmpty()) {
|
| + int new_left = view->x();
|
| + int new_top = view->y();
|
| + int new_width = view->width();
|
| + int new_height = view->height();
|
| + Anchors anchors = anchor_state->anchors;
|
| + gfx::Size parent_size = view->parent()->GetContentsBounds().size();
|
| + if (anchors.Has(Anchor::Right))
|
| + if (anchors.Has(Anchor::Left))
|
| + new_width = parent_size.width() -
|
| + (last_host_size_.width() -
|
| + anchor_state->anchor_basis.x());
|
| + else
|
| + new_left = parent_size.width() -
|
| + (last_host_size_.width() - anchor_state->anchor_basis.x());
|
| + else if (!anchors.Has(Anchor::Left))
|
| + new_left = MulDiv(anchor_state->anchor_basis.x(), parent_size.width(),
|
| + last_host_size_.width()) -
|
| + new_width / 2;
|
| + if (anchors.Has(Anchor::Bottom))
|
| + if (anchors.Has(Anchor::Top))
|
| + new_height = parent_size.height() - (last_host_size_.height() -
|
| + anchor_state->anchor_basis.y());
|
| + else
|
| + new_top = parent_size.height() -
|
| + (last_host_size_.height() - anchor_state->anchor_basis.y());
|
| + else if (!anchors.Has(Anchor::Top))
|
| + new_top = MulDiv(anchor_state->anchor_basis.y(), parent_size.height(),
|
| + last_host_size_.height()) -
|
| + new_height / 2;
|
| + view->SetBounds(new_left, new_top, new_width, new_height);
|
| + }
|
| + if (align == Align_None)
|
| + return;
|
| + }
|
| + gfx::Size preferred_size = view->GetPreferredSize();
|
| + if (preferred_size.IsEmpty()) {
|
| + // TODO(robliao): Remove this once views has a way to handle preferred size
|
| + // correctly.
|
| + preferred_size = view->size();
|
| + }
|
| + int new_width = contents.size().width();
|
| + if (new_width < 0 || align == views::Align::Left ||
|
| + align == views::Align::Right) {
|
| + new_width = preferred_size.width();
|
| + }
|
| +
|
| + int new_height = contents.size().height();
|
| + if (new_height < 0 || align == views::Align::Top ||
|
| + align == views::Align::Bottom)
|
| + new_height = preferred_size.height();
|
| + int new_left = contents.x();
|
| + int new_top = contents.y();
|
| + switch (align) {
|
| + case views::Align::Top: {
|
| + contents.Inset(0, new_height, 0, 0);
|
| + break;
|
| + }
|
| + case views::Align::Bottom: {
|
| + contents.Inset(0, 0, 0, new_height);
|
| + new_top = contents.bottom();
|
| + break;
|
| + }
|
| + case views::Align::Left: {
|
| + contents.Inset(new_width, 0, 0, 0);
|
| + break;
|
| + }
|
| + case views::Align::Right: {
|
| + contents.Inset(0, 0, new_width, 0);
|
| + new_left = contents.right();
|
| + // Fall through
|
| + }
|
| + default:
|
| + break;
|
| + }
|
| + view->SetBounds(new_left, new_top, new_width, new_height);
|
| + // If the view's bounds are constrained in some other manner, this
|
| + // will ensure the content rect is adjusted based on the actual
|
| + // size of the view.
|
| + if (view->width() != new_width || view->height() != new_height) {
|
| + switch (align) {
|
| + case views::Align::Top: {
|
| + contents.set_y(contents.y() - (new_height - view->height()));
|
| + break;
|
| + }
|
| + case views::Align::Bottom: {
|
| + contents.set_height(contents.height() + (new_height - view->height()));
|
| + break;
|
| + }
|
| + case views::Align::Left: {
|
| + contents.set_x(contents.x() - (new_width - view->width()));
|
| + break;
|
| + }
|
| + case views::Align::Right: {
|
| + contents.set_width(contents.width() + (new_width - view->width()));
|
| + break;
|
| + }
|
| + case views::Align::Content: {
|
| + contents.set_width(contents.width() + (new_width - view->width()));
|
| + contents.set_height(contents.height() + (new_height - view->width()));
|
| + }
|
| + }
|
| + }
|
| +}
|
| +
|
| +void AlignLayoutState::SetViewBounds(View* view,
|
| + int x,
|
| + int y,
|
| + int width,
|
| + int height) {
|
| + DCHECK(view);
|
| + view->SetBounds(x, y, width, height);
|
| + UpdateAnchorBasis(view);
|
| +}
|
| +
|
| +bool AlignLayoutState::ShouldAlign(View* host) {
|
| + for (int i = 0; i < host->child_count(); ++i) {
|
| + View* view = host->child_at(i);
|
| + if (view->visible() &&
|
| + (FindAlign(view, nullptr) || FindAnchorState(view, nullptr)))
|
| + return true;
|
| + }
|
| + return false;
|
| +}
|
| +
|
| +bool AlignLayoutState::ShouldInsert(View* child1,
|
| + View* child2,
|
| + Align align) {
|
| + switch (align) {
|
| + case views::Align::Top:
|
| + return child1->y() < child2->y();
|
| + case views::Align::Bottom:
|
| + return child1->bounds().bottom() >= child2->bounds().bottom();
|
| + case views::Align::Left:
|
| + return child1->x() < child2->x();
|
| + case views::Align::Right:
|
| + return child1->bounds().right() >= child2->bounds().right();
|
| + default:
|
| + return false;
|
| + }
|
| +}
|
| +
|
| +void AlignLayoutState::UpdateAnchorAlign(View* view, Align align) {
|
| + AnchorMap::iterator pos = anchor_map_.find(view);
|
| + if (pos != anchor_map_.end()) {
|
| + pos->second.anchors = AnchorContent::AnchorAlign(align);
|
| + InternalUpdateAnchorBasis(view, pos);
|
| + }
|
| +}
|
| +
|
| +void AlignLayoutState::UpdateAnchorBasis(View* view) {
|
| + AnchorMap::iterator pos = anchor_map_.find(view);
|
| + if (pos != anchor_map_.end() &&
|
| + pos->second.anchors != Anchors({Anchor::Left, Anchor::Top}))
|
| + InternalUpdateAnchorBasis(view, pos);
|
| +}
|
| +
|
| +void AlignLayoutState::ViewAdded(View* host, View* view) {
|
| + UpdateAnchorBasis(view);
|
| +}
|
| +
|
| +void AlignLayoutState::ViewRemoved(View* host, View* view) {
|
| + NoAlignView(view);
|
| + NoAnchorView(view);
|
| +}
|
| +
|
| +} // namespace views
|
|
|