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 SchedulePaint(); 58 59 // Invoke super's implementation so that the children are layed out. 60 View::Layout(); 61 } 62 63 const char* SingleSplitView::GetClassName() const { 64 return kViewClassName; 65 } 66 67 void SingleSplitView::GetAccessibleState(ui::AccessibleViewState* state) { 68 state->role = ui::AccessibilityTypes::ROLE_GROUPING; 69 state->name = accessible_name_; 70 } 71 72 gfx::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 += GetDividerSize(); 88 else 89 height += GetDividerSize(); 90 return gfx::Size(width, height); 91 } 92 93 gfx::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 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; 110 } 111 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(); 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 153 void SingleSplitView::SetAccessibleName(const string16& name) { 154 accessible_name_ = name; 155 } 156 157 bool 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 166 bool 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 193 void 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 204 void SingleSplitView::OnBoundsChanged(const gfx::Rect& previous_bounds) { 205 divider_offset_ = CalculateDividerOffset(divider_offset_, previous_bounds, 206 bounds()); 207 } 208 209 bool SingleSplitView::IsPointInDivider(const gfx::Point& p) { 210 if (resize_disabled_) 211 return false; 212 213 if (child_count() < 2) 214 return false; 215 216 if (!child_at(0)->visible() || !child_at(1)->visible()) 217 return false; 218 219 int divider_relative_offset; 220 if (is_horizontal_) { 221 divider_relative_offset = 222 p.x() - child_at(base::i18n::IsRTL() ? 1 : 0)->width(); 223 } else { 224 divider_relative_offset = p.y() - child_at(0)->height(); 225 } 226 return (divider_relative_offset >= 0 && 227 divider_relative_offset < GetDividerSize()); 228 } 229 230 int SingleSplitView::CalculateDividerOffset( 231 int divider_offset, 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 237 // window restore. 238 bool is_minimize_or_restore = 239 previous_bounds.height() == 0 || new_bounds.height() == 0; 240 if (!is_minimize_or_restore) { 241 if (is_horizontal_) 242 divider_offset += new_bounds.width() - previous_bounds.width(); 243 else 244 divider_offset += new_bounds.height() - previous_bounds.height(); 245 246 if (divider_offset < 0) 247 divider_offset = GetDividerSize(); 248 } 249 } 250 return divider_offset; 251 } 252 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)); 261 } 262 263 } // namespace views 264