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