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/base/accessibility/accessible_view_state.h"
9 #include "ui/gfx/canvas.h"
10 #include "ui/views/background.h"
11 #include "ui/views/controls/single_split_view_listener.h"
14 #include "ui/base/cursor/cursor.h"
20 const char SingleSplitView::kViewClassName[] = "SingleSplitView";
22 // Size of the divider in pixels.
23 static const int kDividerSize = 4;
25 SingleSplitView::SingleSplitView(View* leading,
27 Orientation orientation,
28 SingleSplitViewListener* listener)
29 : is_horizontal_(orientation == HORIZONTAL_SPLIT),
31 resize_leading_on_bounds_change_(true),
32 resize_disabled_(false),
34 AddChildView(leading);
35 AddChildView(trailing);
38 views::Background::CreateSolidBackground(
39 skia::COLORREFToSkColor(GetSysColor(COLOR_3DFACE))));
43 void SingleSplitView::Layout() {
44 gfx::Rect leading_bounds;
45 gfx::Rect trailing_bounds;
46 CalculateChildrenBounds(bounds(), &leading_bounds, &trailing_bounds);
49 if (child_at(0)->visible())
50 child_at(0)->SetBoundsRect(leading_bounds);
51 if (child_count() > 1) {
52 if (child_at(1)->visible())
53 child_at(1)->SetBoundsRect(trailing_bounds);
59 // Invoke super's implementation so that the children are layed out.
63 const char* SingleSplitView::GetClassName() const {
64 return kViewClassName;
67 void SingleSplitView::GetAccessibleState(ui::AccessibleViewState* state) {
68 state->role = ui::AccessibilityTypes::ROLE_GROUPING;
69 state->name = accessible_name_;
72 gfx::Size SingleSplitView::GetPreferredSize() {
75 for (int i = 0; i < 2 && i < child_count(); ++i) {
76 View* view = child_at(i);
77 gfx::Size pref = view->GetPreferredSize();
79 width += pref.width();
80 height = std::max(height, pref.height());
82 width = std::max(width, pref.width());
83 height += pref.height();
87 width += GetDividerSize();
89 height += GetDividerSize();
90 return gfx::Size(width, height);
93 gfx::NativeCursor SingleSplitView::GetCursor(const ui::MouseEvent& event) {
94 if (!IsPointInDivider(event.location()))
95 return gfx::kNullCursor;
97 return is_horizontal_ ?
98 ui::kCursorEastWestResize : ui::kCursorNorthSouthResize;
100 static HCURSOR we_resize_cursor = LoadCursor(NULL, IDC_SIZEWE);
101 static HCURSOR ns_resize_cursor = LoadCursor(NULL, IDC_SIZENS);
102 return is_horizontal_ ? we_resize_cursor : ns_resize_cursor;
106 int SingleSplitView::GetDividerSize() const {
107 bool both_visible = child_count() > 1 && child_at(0)->visible() &&
108 child_at(1)->visible();
109 return both_visible && !resize_disabled_ ? kDividerSize : 0;
112 void SingleSplitView::CalculateChildrenBounds(
113 const gfx::Rect& bounds,
114 gfx::Rect* leading_bounds,
115 gfx::Rect* trailing_bounds) const {
116 bool is_leading_visible = has_children() && child_at(0)->visible();
117 bool is_trailing_visible = child_count() > 1 && child_at(1)->visible();
119 if (!is_leading_visible && !is_trailing_visible) {
120 *leading_bounds = gfx::Rect();
121 *trailing_bounds = gfx::Rect();
127 if (!is_trailing_visible) {
128 divider_at = GetPrimaryAxisSize(bounds.width(), bounds.height());
129 } else if (!is_leading_visible) {
133 CalculateDividerOffset(divider_offset_, this->bounds(), bounds);
134 divider_at = NormalizeDividerOffset(divider_at, bounds);
137 int divider_size = GetDividerSize();
139 if (is_horizontal_) {
140 *leading_bounds = gfx::Rect(0, 0, divider_at, bounds.height());
142 gfx::Rect(divider_at + divider_size, 0,
143 std::max(0, bounds.width() - divider_at - divider_size),
146 *leading_bounds = gfx::Rect(0, 0, bounds.width(), divider_at);
148 gfx::Rect(0, divider_at + divider_size, bounds.width(),
149 std::max(0, bounds.height() - divider_at - divider_size));
153 void SingleSplitView::SetAccessibleName(const string16& name) {
154 accessible_name_ = name;
157 bool SingleSplitView::OnMousePressed(const ui::MouseEvent& event) {
158 if (!IsPointInDivider(event.location()))
160 drag_info_.initial_mouse_offset = GetPrimaryAxisSize(event.x(), event.y());
161 drag_info_.initial_divider_offset =
162 NormalizeDividerOffset(divider_offset_, bounds());
166 bool SingleSplitView::OnMouseDragged(const ui::MouseEvent& event) {
167 if (child_count() < 2)
170 int delta_offset = GetPrimaryAxisSize(event.x(), event.y()) -
171 drag_info_.initial_mouse_offset;
172 if (is_horizontal_ && base::i18n::IsRTL())
174 // Honor the first child's minimum size when resizing.
175 gfx::Size min = child_at(0)->GetMinimumSize();
176 int new_size = std::max(GetPrimaryAxisSize(min.width(), min.height()),
177 drag_info_.initial_divider_offset + delta_offset);
179 // Honor the second child's minimum size, and don't let the view
180 // get bigger than our width.
181 min = child_at(1)->GetMinimumSize();
182 new_size = std::min(GetPrimaryAxisSize() - kDividerSize -
183 GetPrimaryAxisSize(min.width(), min.height()), new_size);
185 if (new_size != divider_offset_) {
186 set_divider_offset(new_size);
187 if (!listener_ || listener_->SplitHandleMoved(this))
193 void SingleSplitView::OnMouseCaptureLost() {
194 if (child_count() < 2)
197 if (drag_info_.initial_divider_offset != divider_offset_) {
198 set_divider_offset(drag_info_.initial_divider_offset);
199 if (!listener_ || listener_->SplitHandleMoved(this))
204 void SingleSplitView::OnBoundsChanged(const gfx::Rect& previous_bounds) {
205 divider_offset_ = CalculateDividerOffset(divider_offset_, previous_bounds,
209 bool SingleSplitView::IsPointInDivider(const gfx::Point& p) {
210 if (resize_disabled_)
213 if (child_count() < 2)
216 if (!child_at(0)->visible() || !child_at(1)->visible())
219 int divider_relative_offset;
220 if (is_horizontal_) {
221 divider_relative_offset =
222 p.x() - child_at(base::i18n::IsRTL() ? 1 : 0)->width();
224 divider_relative_offset = p.y() - child_at(0)->height();
226 return (divider_relative_offset >= 0 &&
227 divider_relative_offset < GetDividerSize());
230 int SingleSplitView::CalculateDividerOffset(
232 const gfx::Rect& previous_bounds,
233 const gfx::Rect& new_bounds) const {
234 if (resize_leading_on_bounds_change_ && divider_offset != -1) {
235 // We do not update divider_offset on minimize (to zero) and on restore
236 // (to largest value). As a result we get back to the original value upon
238 bool is_minimize_or_restore =
239 previous_bounds.height() == 0 || new_bounds.height() == 0;
240 if (!is_minimize_or_restore) {
242 divider_offset += new_bounds.width() - previous_bounds.width();
244 divider_offset += new_bounds.height() - previous_bounds.height();
246 if (divider_offset < 0)
247 divider_offset = GetDividerSize();
250 return divider_offset;
253 int SingleSplitView::NormalizeDividerOffset(int divider_offset,
254 const gfx::Rect& bounds) const {
255 int primary_axis_size = GetPrimaryAxisSize(bounds.width(), bounds.height());
256 if (divider_offset < 0)
257 // primary_axis_size may < GetDividerSize during initial layout.
258 return std::max(0, (primary_axis_size - GetDividerSize()) / 2);
259 return std::min(divider_offset,
260 std::max(primary_axis_size - GetDividerSize(), 0));