910e62b5创建于 1月15日历史提交
// 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_