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 |