1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "ui/views/controls/single_split_view.h"
7 #include "skia/ext/skia_utils_win.h"
8 #include "ui/accessibility/ax_view_state.h"
9 #include "ui/base/cursor/cursor.h"
10 #include "ui/gfx/canvas.h"
11 #include "ui/views/background.h"
12 #include "ui/views/controls/single_split_view_listener.h"
13 #include "ui/views/native_cursor.h"
18 const char SingleSplitView::kViewClassName
[] = "SingleSplitView";
20 // Size of the divider in pixels.
21 static const int kDividerSize
= 4;
23 SingleSplitView::SingleSplitView(View
* leading
,
25 Orientation orientation
,
26 SingleSplitViewListener
* listener
)
27 : is_horizontal_(orientation
== HORIZONTAL_SPLIT
),
29 resize_leading_on_bounds_change_(true),
30 resize_disabled_(false),
32 AddChildView(leading
);
33 AddChildView(trailing
);
36 views::Background::CreateSolidBackground(
37 skia::COLORREFToSkColor(GetSysColor(COLOR_3DFACE
))));
41 void SingleSplitView::Layout() {
42 gfx::Rect leading_bounds
;
43 gfx::Rect trailing_bounds
;
44 CalculateChildrenBounds(bounds(), &leading_bounds
, &trailing_bounds
);
47 if (child_at(0)->visible())
48 child_at(0)->SetBoundsRect(leading_bounds
);
49 if (child_count() > 1) {
50 if (child_at(1)->visible())
51 child_at(1)->SetBoundsRect(trailing_bounds
);
55 // Invoke super's implementation so that the children are layed out.
59 const char* SingleSplitView::GetClassName() const {
60 return kViewClassName
;
63 void SingleSplitView::GetAccessibleState(ui::AXViewState
* state
) {
64 state
->role
= ui::AX_ROLE_GROUP
;
65 state
->name
= accessible_name_
;
68 gfx::Size
SingleSplitView::GetPreferredSize() const {
71 for (int i
= 0; i
< 2 && i
< child_count(); ++i
) {
72 const View
* view
= child_at(i
);
73 gfx::Size pref
= view
->GetPreferredSize();
75 width
+= pref
.width();
76 height
= std::max(height
, pref
.height());
78 width
= std::max(width
, pref
.width());
79 height
+= pref
.height();
83 width
+= GetDividerSize();
85 height
+= GetDividerSize();
86 return gfx::Size(width
, height
);
89 gfx::NativeCursor
SingleSplitView::GetCursor(const ui::MouseEvent
& event
) {
90 if (!IsPointInDivider(event
.location()))
91 return gfx::kNullCursor
;
92 return is_horizontal_
? GetNativeEastWestResizeCursor()
93 : GetNativeNorthSouthResizeCursor();
96 int SingleSplitView::GetDividerSize() const {
97 bool both_visible
= child_count() > 1 && child_at(0)->visible() &&
98 child_at(1)->visible();
99 return both_visible
&& !resize_disabled_
? kDividerSize
: 0;
102 void SingleSplitView::CalculateChildrenBounds(
103 const gfx::Rect
& bounds
,
104 gfx::Rect
* leading_bounds
,
105 gfx::Rect
* trailing_bounds
) const {
106 bool is_leading_visible
= has_children() && child_at(0)->visible();
107 bool is_trailing_visible
= child_count() > 1 && child_at(1)->visible();
109 if (!is_leading_visible
&& !is_trailing_visible
) {
110 *leading_bounds
= gfx::Rect();
111 *trailing_bounds
= gfx::Rect();
117 if (!is_trailing_visible
) {
118 divider_at
= GetPrimaryAxisSize(bounds
.width(), bounds
.height());
119 } else if (!is_leading_visible
) {
123 CalculateDividerOffset(divider_offset_
, this->bounds(), bounds
);
124 divider_at
= NormalizeDividerOffset(divider_at
, bounds
);
127 int divider_size
= GetDividerSize();
129 if (is_horizontal_
) {
130 *leading_bounds
= gfx::Rect(0, 0, divider_at
, bounds
.height());
132 gfx::Rect(divider_at
+ divider_size
, 0,
133 std::max(0, bounds
.width() - divider_at
- divider_size
),
136 *leading_bounds
= gfx::Rect(0, 0, bounds
.width(), divider_at
);
138 gfx::Rect(0, divider_at
+ divider_size
, bounds
.width(),
139 std::max(0, bounds
.height() - divider_at
- divider_size
));
143 void SingleSplitView::SetAccessibleName(const base::string16
& name
) {
144 accessible_name_
= name
;
147 bool SingleSplitView::OnMousePressed(const ui::MouseEvent
& event
) {
148 if (!IsPointInDivider(event
.location()))
150 drag_info_
.initial_mouse_offset
= GetPrimaryAxisSize(event
.x(), event
.y());
151 drag_info_
.initial_divider_offset
=
152 NormalizeDividerOffset(divider_offset_
, bounds());
156 bool SingleSplitView::OnMouseDragged(const ui::MouseEvent
& event
) {
157 if (child_count() < 2)
160 int delta_offset
= GetPrimaryAxisSize(event
.x(), event
.y()) -
161 drag_info_
.initial_mouse_offset
;
162 if (is_horizontal_
&& base::i18n::IsRTL())
164 // Honor the first child's minimum size when resizing.
165 gfx::Size min
= child_at(0)->GetMinimumSize();
166 int new_size
= std::max(GetPrimaryAxisSize(min
.width(), min
.height()),
167 drag_info_
.initial_divider_offset
+ delta_offset
);
169 // Honor the second child's minimum size, and don't let the view
170 // get bigger than our width.
171 min
= child_at(1)->GetMinimumSize();
172 new_size
= std::min(GetPrimaryAxisSize() - kDividerSize
-
173 GetPrimaryAxisSize(min
.width(), min
.height()), new_size
);
175 if (new_size
!= divider_offset_
) {
176 set_divider_offset(new_size
);
177 if (!listener_
|| listener_
->SplitHandleMoved(this))
183 void SingleSplitView::OnMouseCaptureLost() {
184 if (child_count() < 2)
187 if (drag_info_
.initial_divider_offset
!= divider_offset_
) {
188 set_divider_offset(drag_info_
.initial_divider_offset
);
189 if (!listener_
|| listener_
->SplitHandleMoved(this))
194 void SingleSplitView::OnBoundsChanged(const gfx::Rect
& previous_bounds
) {
195 divider_offset_
= CalculateDividerOffset(divider_offset_
, previous_bounds
,
199 bool SingleSplitView::IsPointInDivider(const gfx::Point
& p
) {
200 if (resize_disabled_
)
203 if (child_count() < 2)
206 if (!child_at(0)->visible() || !child_at(1)->visible())
209 int divider_relative_offset
;
210 if (is_horizontal_
) {
211 divider_relative_offset
=
212 p
.x() - child_at(base::i18n::IsRTL() ? 1 : 0)->width();
214 divider_relative_offset
= p
.y() - child_at(0)->height();
216 return (divider_relative_offset
>= 0 &&
217 divider_relative_offset
< GetDividerSize());
220 int SingleSplitView::CalculateDividerOffset(
222 const gfx::Rect
& previous_bounds
,
223 const gfx::Rect
& new_bounds
) const {
224 if (resize_leading_on_bounds_change_
&& divider_offset
!= -1) {
225 // We do not update divider_offset on minimize (to zero) and on restore
226 // (to largest value). As a result we get back to the original value upon
228 bool is_minimize_or_restore
=
229 previous_bounds
.height() == 0 || new_bounds
.height() == 0;
230 if (!is_minimize_or_restore
) {
232 divider_offset
+= new_bounds
.width() - previous_bounds
.width();
234 divider_offset
+= new_bounds
.height() - previous_bounds
.height();
236 if (divider_offset
< 0)
237 divider_offset
= GetDividerSize();
240 return divider_offset
;
243 int SingleSplitView::NormalizeDividerOffset(int divider_offset
,
244 const gfx::Rect
& bounds
) const {
245 int primary_axis_size
= GetPrimaryAxisSize(bounds
.width(), bounds
.height());
246 if (divider_offset
< 0)
247 // primary_axis_size may < GetDividerSize during initial layout.
248 return std::max(0, (primary_axis_size
- GetDividerSize()) / 2);
249 return std::min(divider_offset
,
250 std::max(primary_axis_size
- GetDividerSize(), 0));