blob: f9ff66c6863ccecc7494aedb09de9cf737fda7af [file] [log] [blame]
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef UI_VIEWS_LAYOUT_LAYOUT_MANAGER_BASE_H_
#define UI_VIEWS_LAYOUT_LAYOUT_MANAGER_BASE_H_
#include <map>
#include <memory>
#include <optional>
#include <set>
#include <utility>
#include <vector>
#include "base/dcheck_is_on.h"
#include "base/memory/raw_ptr.h"
#include "base/scoped_multi_source_observation.h"
#include "base/types/pass_key.h"
#include "ui/gfx/geometry/insets.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/size.h"
#include "ui/views/layout/layout_manager.h"
#include "ui/views/layout/layout_types.h"
#include "ui/views/layout/proposed_layout.h"
#include "ui/views/view_observer.h"
#include "ui/views/views_export.h"
namespace views {
class View;
// Base class for layout managers that can do layout calculation separately
// from layout application. Derived classes must implement
// CalculateProposedLayout(). Used in interpolating and animating layouts.
class VIEWS_EXPORT LayoutManagerBase : public LayoutManager,
public ViewObserver {
public:
using PassKeyType = base::NonCopyablePassKey<LayoutManagerBase>;
LayoutManagerBase(const LayoutManagerBase&) = delete;
LayoutManagerBase& operator=(const LayoutManagerBase&) = delete;
~LayoutManagerBase() override;
View* host_view() { return host_view_; }
const View* host_view() const { return host_view_; }
// Fetches a proposed layout for a host view with size |host_size|. If the
// result had already been calculated, a cached value may be returned.
ProposedLayout GetProposedLayout(const gfx::Size& host_size) const;
// Fetches a proposed layout for a host view with `size_bounds`. This function
// does not require caching because it is generally used in combination with
// other LayoutManager.
ProposedLayout GetProposedLayout(const SizeBounds& size_bounds,
PassKeyType) const;
// LayoutManager:
gfx::Size GetPreferredSize(const View* host) const override;
gfx::Size GetPreferredSize(const View* host,
const SizeBounds& available_size) const override;
gfx::Size GetMinimumSize(const View* host) const override;
int GetPreferredHeightForWidth(const View* host, int width) const override;
SizeBounds GetAvailableSize(const View* host,
const View* view) const override;
void Layout(View* host) final;
// ViewObserver:
void OnViewPropertyChanged(View* observed_view,
const void* key,
int64_t old_value) final;
// Returns whether the specified child view can be visible. To be able to be
// visible, |child| must be a child of the host view, and must have been
// visible when it was added or most recently had SetVisible(true) called on
// it by non-layout code.
bool CanBeVisible(const View* child) const;
protected:
LayoutManagerBase();
PassKeyType PassKey() const { return PassKeyType(); }
// LayoutManager:
std::vector<raw_ptr<View, VectorExperimental>> GetChildViewsInPaintOrder(
const View* host) const override;
// Direct cache control for subclasses that want to override default caching
// behavior. Use at your own risk.
std::optional<gfx::Size> cached_minimum_size() const {
return cached_minimum_size_;
}
void set_cached_minimum_size(
const std::optional<gfx::Size>& minimum_size) const {
cached_minimum_size_ = minimum_size;
}
const std::optional<gfx::Size>& cached_preferred_size() const {
return cached_preferred_size_;
}
void set_cached_preferred_size(
const std::optional<gfx::Size>& preferred_size) const {
cached_preferred_size_ = preferred_size;
}
const std::optional<gfx::Size>& cached_height_for_width() const {
return cached_height_for_width_;
}
void set_cached_height_for_width(
const std::optional<gfx::Size>& height_for_width) const {
cached_height_for_width_ = height_for_width;
}
const std::optional<gfx::Size>& cached_layout_size() const {
return cached_layout_size_;
}
void set_cached_layout_size(
const std::optional<gfx::Size>& layout_size) const {
cached_layout_size_ = layout_size;
}
const ProposedLayout& cached_layout() const { return cached_layout_; }
void set_cached_layout(const ProposedLayout& layout) const {
cached_layout_ = layout;
}
// Returns the size available to the host view from its parent.
SizeBounds GetAvailableHostSize() const;
// Returns true if the specified view is a child of the host view and is not
// ignored. Views hidden by external code are only included if
// |include_hidden| is set.
bool IsChildIncludedInLayout(const View* child,
bool include_hidden = false) const;
// Creates a proposed layout for the host view, including bounds and
// visibility for all children currently included in the layout.
virtual ProposedLayout CalculateProposedLayout(
const SizeBounds& size_bounds) const = 0;
// Does the actual work of laying out the host view and its children.
// Default implementation is just getting the proposed layout for the host
// size and then applying it.
virtual void LayoutImpl();
// Applies |layout| to the children of the host view.
void ApplyLayout(const ProposedLayout& layout);
// Invalidates the host view (if present).
//
// If |mark_layouts_changed| is true, OnLayoutChanged() will also be called
// for each layout associated with the host, as if the host were invalidated
// by external code. If there is no host (yet), the behavior is simulated by
// invalidating the root layout manager - see GetRootLayoutManager() below.
void InvalidateHost(bool mark_layouts_changed);
// The following methods are called on this layout and any owned layouts when
// e.g. InvalidateLayout(), Installed(), etc. are called, in order to do any
// additional layout-specific work required. Returns whether the host view
// must be invalidated as a result of the update. All of these call
// OnLayoutChanged() by default (see below).
virtual bool OnChildViewIncludedInLayoutSet(View* child_view, bool included);
virtual bool OnViewAdded(View* host, View* view);
virtual bool OnViewRemoved(View* host, View* view);
virtual bool OnViewVisibilitySet(View* host, View* view, bool visible);
// Called when the layout is installed in a host view. Default is a no-op.
virtual void OnInstalled(View* host);
// Called whenever the layout manager is invalidated, or when the layout may
// have changed as the result of an operation. Default behavior is to clear
// all cached data.
virtual void OnLayoutChanged();
// Adds an owned layout. The primary layout propagates events (installation,
// view addition, etc.) to all owned layouts. Subclasses of LayoutManagerBase
// that need to compose or transform the output of one or more embedded
// layouts should use the |owned_layouts| system.
template <class T>
T* AddOwnedLayout(std::unique_ptr<T> owned_layout) {
T* layout = owned_layout.get();
AddOwnedLayoutInternal(std::move(owned_layout));
return layout;
}
size_t num_owned_layouts() const { return owned_layouts_.size(); }
LayoutManagerBase* owned_layout(size_t index) {
return owned_layouts_[index].get();
}
const LayoutManagerBase* owned_layout(size_t index) const {
return owned_layouts_[index].get();
}
private:
friend class ManualLayoutUtil;
friend class LayoutManagerBaseAvailableSizeTest;
// Holds bookkeeping data used to determine inclusion of children in the
// layout.
struct ChildInfo {
bool can_be_visible = true;
bool included_in_layout = true;
};
// LayoutManager:
void InvalidateLayout() final;
void Installed(View* host) final;
void ViewAdded(View* host, View* view) final;
void ViewRemoved(View* host, View* view) final;
void ViewVisibilitySet(View* host,
View* view,
bool old_visibility,
bool new_visibility) final;
void AddOwnedLayoutInternal(std::unique_ptr<LayoutManagerBase> owned_layout);
// Gets the top layout in the ownership chain that includes this layout.
LayoutManagerBase* GetRootLayoutManager();
// Do the work of propagating events to owned layouts. Returns true if the
// host view must be invalidated.
bool PropagateChildViewIncludedInLayout(View* child_view, bool included);
bool PropagateViewAdded(View* host, View* view);
bool PropagateViewRemoved(View* host, View* view);
bool PropagateViewVisibilitySet(View* host, View* view, bool visible);
void PropagateInstalled(View* host);
void PropagateInvalidateLayout();
raw_ptr<View> host_view_ = nullptr;
// Monitors child views so we will be notified if their "ignored by layout"
// state changes. This should only ever be observing anything for the root
// layout manager, which in turn will propagate changes to owned layout
// managers as needed.
base::ScopedMultiSourceObservation<View, ViewObserver> view_observations_{
this};
std::map<const View*, ChildInfo> child_infos_;
std::vector<std::unique_ptr<LayoutManagerBase>> owned_layouts_;
raw_ptr<LayoutManagerBase> parent_layout_ = nullptr;
// Used to suspend invalidation while processing signals from the host view,
// or while invalidating the host view without invalidating the layout.
bool suppress_invalidate_ = false;
// Used during layout to determine if available size has changed for children;
// when it changes, children are always laid out regardless of visibility or
// whether their bounds have changed.
SizeBounds cached_available_size_;
#if (DCHECK_IS_ON())
// Used to prevent GetProposedLayout() from being re-entrant.
mutable bool calculating_layout_ = false;
#endif
// Do some really simple caching because layout generation can cost as much
// as 1ms or more for complex views.
mutable std::optional<gfx::Size> cached_minimum_size_;
mutable std::optional<gfx::Size> cached_preferred_size_;
mutable std::optional<gfx::Size> cached_height_for_width_;
mutable std::optional<gfx::Size> cached_layout_size_;
mutable ProposedLayout cached_layout_;
};
// Provides methods for doing additional, manual manipulation of a
// `LayoutManagerBase` and its managed Views inside its host View's
// layout implementation, ideally before `LayoutManager::Layout()` is invoked.
//
// In most cases, the layout manager should do all of the layout. However, in
// some cases, specific children of the host may be explicitly manipulated; for
// example, to conditionally show a button which (if visible) should be included
// in the layout.
//
// All of the direct manipulation functions on `LayoutManagerBase` and `View`,
// such as `View::SetVisible()` and
// `LayoutManagerBase::SetChildIncludedInLayout()`, cause cascades of layout
// invalidation up the Views tree, so are not appropriate to be used inside of a
// `Layout()` override. In the case that manual layout manipulation is required
// alongside the use of a layout manager, a `ManualLayoutUtil` should be used
// instead of callin those other methods directly.
//
// This class should only be instantiated and used inside the `Layout()` method
// of a `View` or derived class, before `LayoutManager::Layout()` is invoked.
class VIEWS_EXPORT ManualLayoutUtil {
public:
explicit ManualLayoutUtil(LayoutManagerBase* layout_manager);
~ManualLayoutUtil();
ManualLayoutUtil(const ManualLayoutUtil&) = delete;
void operator=(const ManualLayoutUtil&) = delete;
// Includes, or excludes and hides, `child_view`.
//
// Example:
// ```
// MyView::Layout(PassKey) {
// // Only include `foo_button_` in the layout if the feature is enabled;
// // otherwise hide it.
// ManualLayoutUtil layout_util(flex_layout_.get());
// layout_util.SetViewHidden(foo_button_, !foo_enabled);
//
// // Do the standard Views layout, which invokes the layout manager.
// LayoutSuperclass<View>(this);
// }
// ```
//
// Note that if instead the code had read
// `foo_button_.SetVisible(foo_enabled)`, the current view and every view up
// the hierarchy would be invalidated, which could result in a layout loop.
void SetViewHidden(View* child_view, bool hidden);
// This is implementation for the method below; the exclusion is ended when
// the TemporaryExclusion goes out of scope.
using TemporaryExclusionData =
std::pair<const raw_ptr<ManualLayoutUtil>, const raw_ptr<View>>;
struct VIEWS_EXPORT TemporaryExclusionDeleter {
void operator()(TemporaryExclusionData*) const;
};
using TemporaryExclusion =
std::unique_ptr<TemporaryExclusionData,
ManualLayoutUtil::TemporaryExclusionDeleter>;
// Temporarily removes `child_view` from the layout. Use during manual layout
// to see how the layout would look without the child view. When the return
// value goes out of scope, the exclusion is undone.
[[nodiscard]] TemporaryExclusion TemporarilyExcludeFromLayout(
View* child_view);
private:
void EndTemporaryExclusion(View* child_view);
const raw_ptr<LayoutManagerBase> layout_manager_;
};
} // namespace views
#endif // UI_VIEWS_LAYOUT_LAYOUT_MANAGER_BASE_H_