• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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/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"
14 
15 namespace views {
16 
17 // static
18 const char SingleSplitView::kViewClassName[] = "SingleSplitView";
19 
20 // Size of the divider in pixels.
21 static const int kDividerSize = 4;
22 
SingleSplitView(View * leading,View * trailing,Orientation orientation,SingleSplitViewListener * listener)23 SingleSplitView::SingleSplitView(View* leading,
24                                  View* trailing,
25                                  Orientation orientation,
26                                  SingleSplitViewListener* listener)
27     : is_horizontal_(orientation == HORIZONTAL_SPLIT),
28       divider_offset_(-1),
29       resize_leading_on_bounds_change_(true),
30       resize_disabled_(false),
31       listener_(listener) {
32   AddChildView(leading);
33   AddChildView(trailing);
34 #if defined(OS_WIN)
35   set_background(
36       views::Background::CreateSolidBackground(
37           skia::COLORREFToSkColor(GetSysColor(COLOR_3DFACE))));
38 #endif
39 }
40 
Layout()41 void SingleSplitView::Layout() {
42   gfx::Rect leading_bounds;
43   gfx::Rect trailing_bounds;
44   CalculateChildrenBounds(bounds(), &leading_bounds, &trailing_bounds);
45 
46   if (has_children()) {
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);
52     }
53   }
54 
55   // Invoke super's implementation so that the children are layed out.
56   View::Layout();
57 }
58 
GetClassName() const59 const char* SingleSplitView::GetClassName() const {
60   return kViewClassName;
61 }
62 
GetAccessibleState(ui::AXViewState * state)63 void SingleSplitView::GetAccessibleState(ui::AXViewState* state) {
64   state->role = ui::AX_ROLE_GROUP;
65   state->name = accessible_name_;
66 }
67 
GetPreferredSize() const68 gfx::Size SingleSplitView::GetPreferredSize() const {
69   int width = 0;
70   int height = 0;
71   for (int i = 0; i < 2 && i < child_count(); ++i) {
72     const View* view = child_at(i);
73     gfx::Size pref = view->GetPreferredSize();
74     if (is_horizontal_) {
75       width += pref.width();
76       height = std::max(height, pref.height());
77     } else {
78       width = std::max(width, pref.width());
79       height += pref.height();
80     }
81   }
82   if (is_horizontal_)
83     width += GetDividerSize();
84   else
85     height += GetDividerSize();
86   return gfx::Size(width, height);
87 }
88 
GetCursor(const ui::MouseEvent & event)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();
94 }
95 
GetDividerSize() const96 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;
100 }
101 
CalculateChildrenBounds(const gfx::Rect & bounds,gfx::Rect * leading_bounds,gfx::Rect * trailing_bounds) const102 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();
108 
109   if (!is_leading_visible && !is_trailing_visible) {
110     *leading_bounds = gfx::Rect();
111     *trailing_bounds = gfx::Rect();
112     return;
113   }
114 
115   int divider_at;
116 
117   if (!is_trailing_visible) {
118     divider_at = GetPrimaryAxisSize(bounds.width(), bounds.height());
119   } else if (!is_leading_visible) {
120     divider_at = 0;
121   } else {
122     divider_at =
123         CalculateDividerOffset(divider_offset_, this->bounds(), bounds);
124     divider_at = NormalizeDividerOffset(divider_at, bounds);
125   }
126 
127   int divider_size = GetDividerSize();
128 
129   if (is_horizontal_) {
130     *leading_bounds = gfx::Rect(0, 0, divider_at, bounds.height());
131     *trailing_bounds =
132         gfx::Rect(divider_at + divider_size, 0,
133                   std::max(0, bounds.width() - divider_at - divider_size),
134                   bounds.height());
135   } else {
136     *leading_bounds = gfx::Rect(0, 0, bounds.width(), divider_at);
137     *trailing_bounds =
138         gfx::Rect(0, divider_at + divider_size, bounds.width(),
139                   std::max(0, bounds.height() - divider_at - divider_size));
140   }
141 }
142 
SetAccessibleName(const base::string16 & name)143 void SingleSplitView::SetAccessibleName(const base::string16& name) {
144   accessible_name_ = name;
145 }
146 
OnMousePressed(const ui::MouseEvent & event)147 bool SingleSplitView::OnMousePressed(const ui::MouseEvent& event) {
148   if (!IsPointInDivider(event.location()))
149     return false;
150   drag_info_.initial_mouse_offset = GetPrimaryAxisSize(event.x(), event.y());
151   drag_info_.initial_divider_offset =
152       NormalizeDividerOffset(divider_offset_, bounds());
153   return true;
154 }
155 
OnMouseDragged(const ui::MouseEvent & event)156 bool SingleSplitView::OnMouseDragged(const ui::MouseEvent& event) {
157   if (child_count() < 2)
158     return false;
159 
160   int delta_offset = GetPrimaryAxisSize(event.x(), event.y()) -
161       drag_info_.initial_mouse_offset;
162   if (is_horizontal_ && base::i18n::IsRTL())
163     delta_offset *= -1;
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);
168 
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);
174 
175   if (new_size != divider_offset_) {
176     set_divider_offset(new_size);
177     if (!listener_ || listener_->SplitHandleMoved(this))
178       Layout();
179   }
180   return true;
181 }
182 
OnMouseCaptureLost()183 void SingleSplitView::OnMouseCaptureLost() {
184   if (child_count() < 2)
185     return;
186 
187   if (drag_info_.initial_divider_offset != divider_offset_) {
188     set_divider_offset(drag_info_.initial_divider_offset);
189     if (!listener_ || listener_->SplitHandleMoved(this))
190       Layout();
191   }
192 }
193 
OnBoundsChanged(const gfx::Rect & previous_bounds)194 void SingleSplitView::OnBoundsChanged(const gfx::Rect& previous_bounds) {
195   divider_offset_ = CalculateDividerOffset(divider_offset_, previous_bounds,
196                                            bounds());
197 }
198 
IsPointInDivider(const gfx::Point & p)199 bool SingleSplitView::IsPointInDivider(const gfx::Point& p) {
200   if (resize_disabled_)
201     return false;
202 
203   if (child_count() < 2)
204     return false;
205 
206   if (!child_at(0)->visible() || !child_at(1)->visible())
207     return false;
208 
209   int divider_relative_offset;
210   if (is_horizontal_) {
211     divider_relative_offset =
212         p.x() - child_at(base::i18n::IsRTL() ? 1 : 0)->width();
213   } else {
214     divider_relative_offset = p.y() - child_at(0)->height();
215   }
216   return (divider_relative_offset >= 0 &&
217       divider_relative_offset < GetDividerSize());
218 }
219 
CalculateDividerOffset(int divider_offset,const gfx::Rect & previous_bounds,const gfx::Rect & new_bounds) const220 int SingleSplitView::CalculateDividerOffset(
221     int divider_offset,
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
227     // window restore.
228     bool is_minimize_or_restore =
229         previous_bounds.height() == 0 || new_bounds.height() == 0;
230     if (!is_minimize_or_restore) {
231       if (is_horizontal_)
232         divider_offset += new_bounds.width() - previous_bounds.width();
233       else
234         divider_offset += new_bounds.height() - previous_bounds.height();
235 
236       if (divider_offset < 0)
237         divider_offset = GetDividerSize();
238     }
239   }
240   return divider_offset;
241 }
242 
NormalizeDividerOffset(int divider_offset,const gfx::Rect & bounds) const243 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));
251 }
252 
253 }  // namespace views
254