blob: 9316ee55bca436a8df5d56aca70640b29ae7fa35 [file] [log] [blame]
// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ui/views/frame/multi_contents_view.h"
#include <algorithm>
#include <cstdlib>
#include "base/check_deref.h"
#include "base/feature_list.h"
#include "base/i18n/rtl.h"
#include "base/notreached.h"
#include "chrome/browser/actor/ui/actor_overlay_web_view.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser_element_identifiers.h"
#include "chrome/browser/ui/ui_features.h"
#include "chrome/browser/ui/views/frame/browser_view.h"
#include "chrome/browser/ui/views/frame/contents_container_view.h"
#include "chrome/browser/ui/views/frame/contents_rounded_corner.h"
#include "chrome/browser/ui/views/frame/contents_separator.h"
#include "chrome/browser/ui/views/frame/contents_web_view.h"
#include "chrome/browser/ui/views/frame/multi_contents_background_view.h"
#include "chrome/browser/ui/views/frame/multi_contents_drop_target_view.h"
#include "chrome/browser/ui/views/frame/multi_contents_resize_area.h"
#include "chrome/browser/ui/views/frame/multi_contents_view_delegate.h"
#include "chrome/browser/ui/views/frame/multi_contents_view_drop_target_controller.h"
#include "chrome/browser/ui/views/frame/multi_contents_view_mini_toolbar.h"
#include "chrome/browser/ui/views/frame/scrim_view.h"
#include "chrome/browser/ui/views/frame/top_container_background.h"
#include "chrome/browser/ui/views/new_tab_footer/footer_web_view.h"
#include "chrome/browser/ui/views/toolbar/toolbar_view.h"
#include "chrome/common/pref_names.h"
#include "components/prefs/pref_service.h"
#include "content/public/browser/web_contents.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/base/ozone_buildflags.h"
#include "ui/compositor/layer.h"
#include "ui/compositor/layer_type.h"
#include "ui/events/types/event_type.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/rounded_corners_f.h"
#include "ui/gfx/scoped_canvas.h"
#include "ui/ozone/public/ozone_platform.h"
#include "ui/views/view_class_properties.h"
void MultiContentsView::ContentsSeparators::Reset() {
top_separator = nullptr;
leading_separator = nullptr;
trailing_separator = nullptr;
top_leading_rounded_corner = nullptr;
top_trailing_rounded_corner = nullptr;
}
MultiContentsView::MultiContentsView(
BrowserView* browser_view,
std::unique_ptr<MultiContentsViewDelegate> delegate)
: browser_view_(browser_view),
delegate_(std::move(delegate)),
start_contents_view_inset_(
gfx::Insets(kSplitViewContentInset).set_top(0).set_right(0)),
end_contents_view_inset_(
gfx::Insets(kSplitViewContentInset).set_top(0).set_left(0)) {
SetLayoutManager(std::make_unique<views::DelegatingLayoutManager>(this));
SetProperty(views::kElementIdentifierKey, kMultiContentsViewElementId);
background_view_ =
AddChildView(std::make_unique<MultiContentsBackgroundView>(browser_view));
contents_container_views_.push_back(
AddChildView(std::make_unique<ContentsContainerView>(browser_view_)));
contents_container_views_[0]
->contents_view()
->set_is_primary_web_contents_for_window(true);
resize_area_ = AddChildView(std::make_unique<MultiContentsResizeArea>(this));
resize_area_->SetVisible(false);
contents_container_views_.push_back(
AddChildView(std::make_unique<ContentsContainerView>(browser_view_)));
contents_container_views_[1]->SetVisible(false);
drop_target_view_ =
AddChildView(std::make_unique<MultiContentsDropTargetView>());
drop_target_controller_ =
std::make_unique<MultiContentsViewDropTargetController>(
*drop_target_view_, *delegate_, g_browser_process->local_state());
contents_separators_.top_separator =
AddChildView(ContentsSeparator::CreateLayerBasedContentsSeparator());
contents_separators_.top_separator->SetProperty(
views::kElementIdentifierKey, kContentsSeparatorTopEdgeElementId);
contents_separators_.leading_separator =
AddChildView(ContentsSeparator::CreateLayerBasedContentsSeparator());
contents_separators_.leading_separator->SetProperty(
views::kElementIdentifierKey, kContentsSeparatorLeadingEdgeElementId);
contents_separators_.trailing_separator =
AddChildView(ContentsSeparator::CreateLayerBasedContentsSeparator());
contents_separators_.trailing_separator->SetProperty(
views::kElementIdentifierKey, kContentsSeparatorTrailingEdgeElementId);
contents_separators_.top_leading_rounded_corner =
AddChildView(std::make_unique<ContentsRoundedCorner>(
browser_view_, views::ShapeContextTokens::kContentSeparatorRadius,
base::BindRepeating([]() { return base::i18n::IsRTL(); })));
contents_separators_.top_leading_rounded_corner->SetProperty(
views::kElementIdentifierKey,
kContentsSeparatorLeadingTopCornerElementId);
contents_separators_.top_trailing_rounded_corner =
AddChildView(std::make_unique<ContentsRoundedCorner>(
browser_view_, views::ShapeContextTokens::kContentSeparatorRadius,
base::BindRepeating([]() { return !base::i18n::IsRTL(); })));
contents_separators_.top_trailing_rounded_corner->SetProperty(
views::kElementIdentifierKey,
kContentsSeparatorTrailingTopCornerElementId);
for (auto* contents_container_view : contents_container_views_) {
web_contents_focused_subscriptions_.push_back(
contents_container_view->contents_view()->AddWebContentsFocusedCallback(
base::BindRepeating(&MultiContentsView::OnWebContentsFocused,
base::Unretained(this))));
if (contents_container_view->new_tab_footer_view()) {
ntp_footer_focused_subscriptions_.push_back(
contents_container_view->new_tab_footer_view()
->AddWebContentsFocusedCallback(
base::BindRepeating(&MultiContentsView::OnNtpFooterFocused,
base::Unretained(this))));
}
if (contents_container_view->actor_overlay_web_view()) {
actor_overlay_focused_subscriptions_.push_back(
contents_container_view->actor_overlay_web_view()
->AddWebContentsFocusedCallback(
base::BindRepeating(&MultiContentsView::OnActorOverlayFocused,
base::Unretained(this))));
}
}
is_drag_drop_pref_enabled_ =
browser_view_->GetProfile()->GetPrefs()->GetBoolean(
prefs::kSplitViewDragAndDropEnabled);
pref_change_registrar_.Init(browser_view_->GetProfile()->GetPrefs());
pref_change_registrar_.Add(
prefs::kSplitViewDragAndDropEnabled,
base::BindRepeating(&MultiContentsView::OnDragAndDropPrefStateChange,
base::Unretained(this)));
}
MultiContentsView::~MultiContentsView() {
if (drop_target_controller_) {
drop_target_controller_.reset();
}
drop_target_view_ = nullptr;
resize_area_ = nullptr;
contents_separators_.Reset();
background_view_ = nullptr;
RemoveAllChildViews();
}
ContentsWebView* MultiContentsView::GetActiveContentsView() const {
return GetActiveContentsContainerView()->contents_view();
}
ContentsWebView* MultiContentsView::GetInactiveContentsView() const {
return GetInactiveContentsContainerView()->contents_view();
}
ContentsContainerView* MultiContentsView::GetActiveContentsContainerView()
const {
return contents_container_views_[active_index_];
}
ContentsContainerView* MultiContentsView::GetInactiveContentsContainerView()
const {
return contents_container_views_[GetInactiveIndex()];
}
const gfx::RoundedCornersF& MultiContentsView::background_radii() const {
return background_view_->GetRoundedCorners();
}
void MultiContentsView::SetBackgroundRadii(const gfx::RoundedCornersF& radii) {
background_view_->SetRoundedCorners(radii);
}
ContentsContainerView* MultiContentsView::GetContentsContainerViewFor(
content::WebContents* web_contents) const {
for (auto* container_view : contents_container_views_) {
if (container_view->contents_view()->web_contents() == web_contents) {
return container_view;
}
}
return nullptr;
}
gfx::Size MultiContentsView::GetContentsSize() const {
const int drop_target_width =
IsDragAndDropEnabled() ? drop_target_view_->GetPreferredWidth(width())
: 0;
const int separator_height =
contents_separators_.should_show_top
? contents_separators_.top_separator->GetPreferredSize().height()
: 0;
const int leading_separator_width =
contents_separators_.should_show_leading
? contents_separators_.leading_separator->GetPreferredSize().height()
: 0;
const int trailing_separator_width =
contents_separators_.should_show_trailing
? contents_separators_.trailing_separator->GetPreferredSize().height()
: 0;
return gfx::Size(width() - drop_target_width - leading_separator_width -
trailing_separator_width,
height() - separator_height);
}
bool MultiContentsView::IsInSplitView() const {
return resize_area_->GetVisible();
}
void MultiContentsView::SetWebContentsAtIndex(
content::WebContents* web_contents,
int index) {
CHECK(index >= 0 && index < 2);
contents_container_views_[index]->contents_view()->SetWebContents(
web_contents);
if (index == 1 && !contents_container_views_[1]->GetVisible()) {
contents_container_views_[1]->SetVisible(true);
resize_area_->SetVisible(true);
UpdateContentsBorderAndOverlay();
}
}
void MultiContentsView::ShowSplitView(double ratio) {
if (!contents_container_views_[1]->GetVisible()) {
// If split view is not visible, set the `start_ratio_` and update the view
// visibility.
start_ratio_ = ratio;
contents_container_views_[1]->SetVisible(true);
resize_area_->SetVisible(true);
UpdateContentsBorderAndOverlay();
} else if (start_ratio_ != ratio) {
// If the split view is visible but ratio is changed, update the split
// ratio.
UpdateSplitRatio(ratio);
}
// Split view is visible and ratio is not changed, do nothing.
}
void MultiContentsView::CloseSplitView() {
if (!IsInSplitView()) {
return;
}
if (active_index_ != 0) {
ContentsContainerView* start_view = contents_container_views_[0];
ContentsContainerView* active_view =
contents_container_views_[active_index_];
// Move the active WebContents so that the first ContentsContainerView in
// contents_container_views_ can always be visible.
std::iter_swap(contents_container_views_.begin(),
contents_container_views_.begin() + active_index_);
// Reorder the child views so that focus order will be consistent with
// contents_container_views_.
size_t start_view_child_index = GetIndexOf(start_view).value();
size_t active_view_child_index = GetIndexOf(active_view).value();
ReorderChildView(start_view, active_view_child_index);
ReorderChildView(active_view, start_view_child_index);
active_index_ = 0;
}
contents_container_views_[1]->contents_view()->SetWebContents(nullptr);
contents_container_views_[1]->SetVisible(false);
resize_area_->SetVisible(false);
UpdateContentsBorderAndOverlay();
}
void MultiContentsView::SetActiveIndex(int index) {
// Index should never be less than 0 or equal to or greater than the total
// number of contents views.
CHECK(index >= 0 && index < 2);
// We will only activate a visible contents view.
CHECK(contents_container_views_[index]->GetVisible());
active_index_ = index;
GetActiveContentsView()->set_is_primary_web_contents_for_window(true);
GetInactiveContentsView()->set_is_primary_web_contents_for_window(false);
UpdateContentsBorderAndOverlay();
}
void MultiContentsView::UpdateSplitRatio(double ratio) {
if (start_ratio_ == ratio) {
return;
}
start_ratio_ = ratio;
InvalidateLayout();
}
void MultiContentsView::SetHighlightActiveContentsView(bool is_highlighted) {
if (active_contents_view_highlighted_ != is_highlighted) {
active_contents_view_highlighted_ = is_highlighted;
UpdateContentsBorderAndOverlay();
}
}
void MultiContentsView::ExecuteOnEachVisibleContentsView(
base::RepeatingCallback<void(ContentsWebView*)> callback) {
for (auto* contents_container_view : contents_container_views_) {
if (contents_container_view->GetVisible()) {
callback.Run(contents_container_view->contents_view());
}
}
}
void MultiContentsView::OnSwap() {
CHECK(IsInSplitView());
delegate_->ReverseWebContents();
}
int MultiContentsView::GetMinViewWidth() const {
if (!IsInSplitView()) {
return 0;
}
const int min_percentage =
kMinWebContentsWidthPercentage * browser_view_->GetBounds().width();
const int min_fixed_value = min_contents_width_for_testing_.has_value()
? min_contents_width_for_testing_.value()
: kMinWebContentsWidth;
return std::min(min_fixed_value, min_percentage);
}
std::vector<views::View*> MultiContentsView::GetAccessiblePanes() {
std::vector<views::View*> accessible_panes;
for (auto* contents_container_view : contents_container_views_) {
auto contents_accessible_panes =
contents_container_view->GetAccessiblePanes();
accessible_panes.insert(accessible_panes.end(),
contents_accessible_panes.begin(),
contents_accessible_panes.end());
}
return accessible_panes;
}
void MultiContentsView::OnResize(int resize_amount, bool done_resizing) {
if (!initial_start_width_on_resize_.has_value()) {
initial_start_width_on_resize_ =
std::make_optional(contents_container_views_[0]->size().width());
}
double total_width = contents_container_views_[0]->size().width() +
contents_container_views_[0]->GetInsets().width() +
contents_container_views_[1]->size().width() +
contents_container_views_[1]->GetInsets().width();
double end_width = (initial_start_width_on_resize_.value() +
contents_container_views_[0]->GetInsets().width() +
static_cast<double>(resize_amount));
// If end_width is within the snap point widths, update to the snap point.
delegate_->ResizeWebContents(
CalculateRatioWithSnapPoints(end_width, total_width), done_resizing);
if (done_resizing) {
initial_start_width_on_resize_ = std::nullopt;
}
}
double MultiContentsView::CalculateRatioWithSnapPoints(
double end_width,
double total_width) const {
for (const double& snap_point : snap_points_) {
double dp_snap_point = snap_point * total_width;
if (std::abs(dp_snap_point - end_width) <
features::kSideBySideSnapDistance.Get()) {
return snap_point;
}
}
return end_width / total_width;
}
void MultiContentsView::OnThemeChanged() {
views::View::OnThemeChanged();
UpdateContentsBorderAndOverlay();
}
int MultiContentsView::GetInactiveIndex() const {
return active_index_ == 0 ? 1 : 0;
}
void MultiContentsView::OnWebContentsFocused(views::WebView* web_view) {
if (IsInSplitView()) {
// Check whether the widget is visible as otherwise during browser hide,
// inactive web contents gets focus. See crbug.com/419335827
if (GetInactiveContentsView()->web_contents() == web_view->web_contents() &&
GetWidget()->IsVisible()) {
delegate_->WebContentsFocused(web_view->web_contents());
}
}
}
void MultiContentsView::OnActorOverlayFocused(views::WebView* web_view) {
if (IsInSplitView() && GetWidget()->IsVisible()) {
for (auto* contents_container_view : contents_container_views_) {
if (contents_container_view->actor_overlay_web_view() &&
contents_container_view->actor_overlay_web_view() == web_view &&
GetInactiveContentsView() ==
contents_container_view->contents_view()) {
return delegate_->WebContentsFocused(
GetInactiveContentsView()->web_contents());
}
}
}
}
void MultiContentsView::OnNtpFooterFocused(views::WebView* web_view) {
if (IsInSplitView() && GetWidget()->IsVisible()) {
for (auto* contents_container_view : contents_container_views_) {
if (contents_container_view->new_tab_footer_view() &&
contents_container_view->new_tab_footer_view() == web_view &&
GetInactiveContentsView() ==
contents_container_view->contents_view()) {
return delegate_->WebContentsFocused(
GetInactiveContentsView()->web_contents());
}
}
}
}
// TODO(crbug.com/397777917): Consider using FlexSpecification weights and
// interior margins instead of a custom layout once this bug is resolved.
views::ProposedLayout MultiContentsView::CalculateProposedLayout(
const views::SizeBounds& size_bounds) const {
views::ProposedLayout layouts;
if (!size_bounds.is_fully_bounded()) {
return layouts;
}
const int width = size_bounds.width().value();
const int height = size_bounds.height().value();
gfx::Rect available_space = gfx::Rect(width, height);
const bool show_background =
drop_target_view_->GetVisible() || IsInSplitView();
layouts.child_layouts.emplace_back(background_view_.get(), show_background,
available_space);
if (IsDragAndDropEnabled()) {
available_space =
CalculateDropTargetLayout(available_space, layouts.child_layouts);
}
available_space =
CalculateSeparatorLayouts(available_space, layouts.child_layouts);
ViewWidths widths = GetViewWidths(available_space);
gfx::Rect start_rect(available_space.origin(),
gfx::Size(widths.start_width, available_space.height()));
gfx::Rect resize_rect(
start_rect.top_right(),
gfx::Size(widths.resize_width, available_space.height()));
gfx::Rect end_rect(resize_rect.top_right(),
gfx::Size(widths.end_width, available_space.height()));
if (IsInSplitView()) {
start_rect.Inset(start_contents_view_inset_);
end_rect.Inset(end_contents_view_inset_);
}
layouts.child_layouts.emplace_back(contents_container_views_[0],
contents_container_views_[0]->GetVisible(),
start_rect);
layouts.child_layouts.emplace_back(resize_area_.get(),
resize_area_->GetVisible(), resize_rect);
layouts.child_layouts.emplace_back(contents_container_views_[1],
contents_container_views_[1]->GetVisible(),
end_rect);
layouts.host_size = gfx::Size(width, height);
return layouts;
}
gfx::Rect MultiContentsView::CalculateDropTargetLayout(
const gfx::Rect& available_space,
std::vector<views::ChildLayout>& child_layouts) const {
CHECK(IsDragAndDropEnabled());
if (!drop_target_view_->GetVisible()) {
child_layouts.emplace_back(drop_target_view_.get(), false, gfx::Rect());
return available_space;
}
const int drop_target_width =
drop_target_view_->GetPreferredWidth(available_space.width());
const int drop_target_x = (drop_target_view_->side() ==
MultiContentsDropTargetView::DropSide::START)
? available_space.x()
: available_space.right() - drop_target_width;
const int remaining_space_x =
available_space.x() + ((drop_target_view_->side() ==
MultiContentsDropTargetView::DropSide::START)
? drop_target_width
: 0);
child_layouts.emplace_back(
drop_target_view_.get(), true,
gfx::Rect(drop_target_x, available_space.y(), drop_target_width,
available_space.height()));
return gfx::Rect(remaining_space_x, available_space.y(),
available_space.width() - drop_target_width,
available_space.height());
}
gfx::Rect MultiContentsView::CalculateSeparatorLayouts(
const gfx::Rect& available_space,
std::vector<views::ChildLayout>& child_layouts) const {
if (IsInSplitView()) {
child_layouts.emplace_back(contents_separators_.top_separator.get(), false,
gfx::Rect());
child_layouts.emplace_back(contents_separators_.leading_separator.get(),
false, gfx::Rect());
child_layouts.emplace_back(contents_separators_.trailing_separator.get(),
false, gfx::Rect());
child_layouts.emplace_back(
contents_separators_.top_leading_rounded_corner.get(), false,
gfx::Rect());
child_layouts.emplace_back(
contents_separators_.top_trailing_rounded_corner.get(), false,
gfx::Rect());
return available_space;
}
const int width = available_space.width();
const int height = available_space.height();
const int separator_height =
contents_separators_.should_show_top
? contents_separators_.top_separator->GetPreferredSize().height()
: 0;
child_layouts.emplace_back(
contents_separators_.top_separator.get(),
contents_separators_.should_show_top,
gfx::Rect(available_space.origin(), {width, separator_height}));
const bool should_show_leading =
contents_separators_.should_show_leading ||
(drop_target_view_->side() ==
MultiContentsDropTargetView::DropSide::START);
const int leading_separator_width =
should_show_leading
? contents_separators_.leading_separator->GetPreferredSize().width()
: 0;
child_layouts.emplace_back(
contents_separators_.leading_separator.get(), should_show_leading,
gfx::Rect(available_space.origin(), {leading_separator_width, height}));
const bool should_show_trailing =
contents_separators_.should_show_trailing ||
(drop_target_view_->side() == MultiContentsDropTargetView::DropSide::END);
const int trailing_separator_width =
should_show_trailing
? contents_separators_.trailing_separator->GetPreferredSize().width()
: 0;
child_layouts.emplace_back(
contents_separators_.trailing_separator.get(), should_show_trailing,
gfx::Rect(available_space.right() - trailing_separator_width,
available_space.y(), trailing_separator_width, height));
child_layouts.emplace_back(
contents_separators_.top_leading_rounded_corner.get(),
should_show_leading && contents_separators_.should_show_top,
gfx::Rect(
available_space.origin(),
contents_separators_.top_leading_rounded_corner->GetPreferredSize()));
child_layouts.emplace_back(
contents_separators_.top_trailing_rounded_corner.get(),
should_show_trailing && contents_separators_.should_show_top,
gfx::Rect({available_space.right() -
contents_separators_.top_trailing_rounded_corner
->GetPreferredSize()
.width(),
available_space.y()},
contents_separators_.top_trailing_rounded_corner
->GetPreferredSize()));
return gfx::Rect(available_space.x() + leading_separator_width,
available_space.y() + separator_height,
width - trailing_separator_width - leading_separator_width,
height - separator_height);
}
MultiContentsView::ViewWidths MultiContentsView::GetViewWidths(
gfx::Rect available_space) const {
ViewWidths widths;
if (IsInSplitView()) {
CHECK(contents_container_views_[0]->GetVisible() &&
contents_container_views_[1]->GetVisible());
widths.resize_width = resize_area_->GetPreferredSize().width();
widths.start_width =
start_ratio_ * (available_space.width() - widths.resize_width);
widths.end_width =
available_space.width() - widths.start_width - widths.resize_width;
} else {
CHECK(!contents_container_views_[1]->GetVisible());
widths.start_width = available_space.width();
}
return ClampToMinWidth(widths);
}
MultiContentsView::ViewWidths MultiContentsView::ClampToMinWidth(
ViewWidths widths) const {
if (!IsInSplitView()) {
// Don't clamp if in a single-view state, where other views should be 0
// width.
return widths;
}
const int min_width = GetMinViewWidth();
if (widths.start_width < min_width) {
const double diff = min_width - widths.start_width;
widths.start_width += diff;
widths.end_width -= diff;
} else if (widths.end_width < min_width) {
const double diff = min_width - widths.end_width;
widths.end_width += diff;
widths.start_width -= diff;
}
return widths;
}
void MultiContentsView::UpdateContentsBorderAndOverlay() {
for (auto* contents_container_view : contents_container_views_) {
const bool is_active =
contents_container_view->contents_view() == GetActiveContentsView();
contents_container_view->UpdateBorderAndOverlay(
IsInSplitView(), is_active,
is_active && active_contents_view_highlighted_);
}
}
MultiContentsViewDropTargetController&
MultiContentsView::drop_target_controller() const {
CHECK(IsDragAndDropEnabled());
return *drop_target_controller_;
}
bool MultiContentsView::IsDragAndDropEnabled() const {
// Split view drag and drop is only supported on normal browser types.
return browser_view_->GetIsNormalType() && is_drag_drop_pref_enabled_;
}
void MultiContentsView::OnDragAndDropPrefStateChange() {
is_drag_drop_pref_enabled_ =
browser_view_->GetProfile()->GetPrefs()->GetBoolean(
prefs::kSplitViewDragAndDropEnabled);
InvalidateLayout();
}
void MultiContentsView::SetShouldShowTopSeparator(bool should_show) {
if (contents_separators_.should_show_top == should_show) {
return;
}
contents_separators_.should_show_top = should_show;
start_contents_view_inset_.set_top(
should_show ? 0 : MultiContentsView::kSplitViewContentInset);
end_contents_view_inset_.set_top(
should_show ? 0 : MultiContentsView::kSplitViewContentInset);
InvalidateLayout();
}
void MultiContentsView::SetShouldShowLeadingSeparator(bool should_show) {
if (contents_separators_.should_show_leading == should_show) {
return;
}
contents_separators_.should_show_leading = should_show;
start_contents_view_inset_.set_left(
should_show ? 0 : MultiContentsView::kSplitViewContentInset);
InvalidateLayout();
}
void MultiContentsView::SetShouldShowTrailingSeparator(bool should_show) {
if (contents_separators_.should_show_trailing == should_show) {
return;
}
contents_separators_.should_show_trailing = should_show;
end_contents_view_inset_.set_right(
should_show ? 0 : MultiContentsView::kSplitViewContentInset);
InvalidateLayout();
}
BEGIN_METADATA(MultiContentsView)
END_METADATA