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