single_split_view.cc revision 2a99a7e74a7f215066514fe81d2bfa6639d9eddd
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.
4
5#include "ui/views/controls/single_split_view.h"
6
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"
12
13#if defined(USE_AURA)
14#include "ui/base/cursor/cursor.h"
15#endif
16
17namespace views {
18
19// static
20const char SingleSplitView::kViewClassName[] =
21    "ui/views/controls/SingleSplitView";
22
23// Size of the divider in pixels.
24static const int kDividerSize = 4;
25
26SingleSplitView::SingleSplitView(View* leading,
27                                 View* trailing,
28                                 Orientation orientation,
29                                 SingleSplitViewListener* listener)
30    : is_horizontal_(orientation == HORIZONTAL_SPLIT),
31      divider_offset_(-1),
32      resize_leading_on_bounds_change_(true),
33      listener_(listener) {
34  AddChildView(leading);
35  AddChildView(trailing);
36#if defined(OS_WIN)
37  set_background(
38      views::Background::CreateSolidBackground(
39          skia::COLORREFToSkColor(GetSysColor(COLOR_3DFACE))));
40#endif
41}
42
43void SingleSplitView::Layout() {
44  gfx::Rect leading_bounds;
45  gfx::Rect trailing_bounds;
46  CalculateChildrenBounds(bounds(), &leading_bounds, &trailing_bounds);
47
48  if (has_children()) {
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);
54    }
55  }
56
57  SchedulePaint();
58
59  // Invoke super's implementation so that the children are layed out.
60  View::Layout();
61}
62
63std::string SingleSplitView::GetClassName() const {
64  return kViewClassName;
65}
66
67void SingleSplitView::GetAccessibleState(ui::AccessibleViewState* state) {
68  state->role = ui::AccessibilityTypes::ROLE_GROUPING;
69  state->name = accessible_name_;
70}
71
72gfx::Size SingleSplitView::GetPreferredSize() {
73  int width = 0;
74  int height = 0;
75  for (int i = 0; i < 2 && i < child_count(); ++i) {
76    View* view = child_at(i);
77    gfx::Size pref = view->GetPreferredSize();
78    if (is_horizontal_) {
79      width += pref.width();
80      height = std::max(height, pref.height());
81    } else {
82      width = std::max(width, pref.width());
83      height += pref.height();
84    }
85  }
86  if (is_horizontal_)
87    width += kDividerSize;
88  else
89    height += kDividerSize;
90  return gfx::Size(width, height);
91}
92
93gfx::NativeCursor SingleSplitView::GetCursor(const ui::MouseEvent& event) {
94  if (!IsPointInDivider(event.location()))
95    return gfx::kNullCursor;
96#if defined(USE_AURA)
97  return is_horizontal_ ?
98      ui::kCursorEastWestResize : ui::kCursorNorthSouthResize;
99#elif defined(OS_WIN)
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;
103#endif
104}
105
106int SingleSplitView::GetDividerSize() const {
107  bool both_visible = child_count() > 1 && child_at(0)->visible() &&
108      child_at(1)->visible();
109  return both_visible ? kDividerSize : 0;
110}
111
112void 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();
118
119  if (!is_leading_visible && !is_trailing_visible) {
120    *leading_bounds = gfx::Rect();
121    *trailing_bounds = gfx::Rect();
122    return;
123  }
124
125  int divider_at;
126
127  if (!is_trailing_visible) {
128    divider_at = GetPrimaryAxisSize(bounds.width(), bounds.height());
129  } else if (!is_leading_visible) {
130    divider_at = 0;
131  } else {
132    divider_at =
133        CalculateDividerOffset(divider_offset_, this->bounds(), bounds);
134    divider_at = NormalizeDividerOffset(divider_at, bounds);
135  }
136
137  int divider_size = GetDividerSize();
138
139  if (is_horizontal_) {
140    *leading_bounds = gfx::Rect(0, 0, divider_at, bounds.height());
141    *trailing_bounds =
142        gfx::Rect(divider_at + divider_size, 0,
143                  std::max(0, bounds.width() - divider_at - divider_size),
144                  bounds.height());
145  } else {
146    *leading_bounds = gfx::Rect(0, 0, bounds.width(), divider_at);
147    *trailing_bounds =
148        gfx::Rect(0, divider_at + divider_size, bounds.width(),
149                  std::max(0, bounds.height() - divider_at - divider_size));
150  }
151}
152
153void SingleSplitView::SetAccessibleName(const string16& name) {
154  accessible_name_ = name;
155}
156
157bool SingleSplitView::OnMousePressed(const ui::MouseEvent& event) {
158  if (!IsPointInDivider(event.location()))
159    return false;
160  drag_info_.initial_mouse_offset = GetPrimaryAxisSize(event.x(), event.y());
161  drag_info_.initial_divider_offset =
162      NormalizeDividerOffset(divider_offset_, bounds());
163  return true;
164}
165
166bool SingleSplitView::OnMouseDragged(const ui::MouseEvent& event) {
167  if (child_count() < 2)
168    return false;
169
170  int delta_offset = GetPrimaryAxisSize(event.x(), event.y()) -
171      drag_info_.initial_mouse_offset;
172  if (is_horizontal_ && base::i18n::IsRTL())
173    delta_offset *= -1;
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);
178
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);
184
185  if (new_size != divider_offset_) {
186    set_divider_offset(new_size);
187    if (!listener_ || listener_->SplitHandleMoved(this))
188      Layout();
189  }
190  return true;
191}
192
193void SingleSplitView::OnMouseCaptureLost() {
194  if (child_count() < 2)
195    return;
196
197  if (drag_info_.initial_divider_offset != divider_offset_) {
198    set_divider_offset(drag_info_.initial_divider_offset);
199    if (!listener_ || listener_->SplitHandleMoved(this))
200      Layout();
201  }
202}
203
204void SingleSplitView::OnBoundsChanged(const gfx::Rect& previous_bounds) {
205  divider_offset_ = CalculateDividerOffset(divider_offset_, previous_bounds,
206                                           bounds());
207}
208
209bool SingleSplitView::IsPointInDivider(const gfx::Point& p) {
210  if (child_count() < 2)
211    return false;
212
213  if (!child_at(0)->visible() || !child_at(1)->visible())
214    return false;
215
216  int divider_relative_offset;
217  if (is_horizontal_) {
218    divider_relative_offset =
219        p.x() - child_at(base::i18n::IsRTL() ? 1 : 0)->width();
220  } else {
221    divider_relative_offset = p.y() - child_at(0)->height();
222  }
223  return (divider_relative_offset >= 0 &&
224      divider_relative_offset < kDividerSize);
225}
226
227int SingleSplitView::CalculateDividerOffset(
228    int divider_offset,
229    const gfx::Rect& previous_bounds,
230    const gfx::Rect& new_bounds) const {
231  if (resize_leading_on_bounds_change_ && divider_offset != -1) {
232    // We do not update divider_offset on minimize (to zero) and on restore
233    // (to largest value). As a result we get back to the original value upon
234    // window restore.
235    bool is_minimize_or_restore =
236        previous_bounds.height() == 0 || new_bounds.height() == 0;
237    if (!is_minimize_or_restore) {
238      if (is_horizontal_)
239        divider_offset += new_bounds.width() - previous_bounds.width();
240      else
241        divider_offset += new_bounds.height() - previous_bounds.height();
242
243      if (divider_offset < 0)
244        divider_offset = kDividerSize;
245    }
246  }
247  return divider_offset;
248}
249
250int SingleSplitView::NormalizeDividerOffset(int divider_offset,
251                                            const gfx::Rect& bounds) const {
252  int primary_axis_size = GetPrimaryAxisSize(bounds.width(), bounds.height());
253  if (divider_offset < 0)
254    // primary_axis_size may < kDividerSize during initial layout.
255    return std::max(0, (primary_axis_size - kDividerSize) / 2);
256  return std::min(divider_offset,
257                  std::max(primary_axis_size - kDividerSize, 0));
258}
259
260}  // namespace views
261