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 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 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 59 const char* SingleSplitView::GetClassName() const { 60 return kViewClassName; 61 } 62 63 void SingleSplitView::GetAccessibleState(ui::AXViewState* state) { 64 state->role = ui::AX_ROLE_GROUP; 65 state->name = accessible_name_; 66 } 67 68 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 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 96 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 102 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 143 void SingleSplitView::SetAccessibleName(const base::string16& name) { 144 accessible_name_ = name; 145 } 146 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 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 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 194 void SingleSplitView::OnBoundsChanged(const gfx::Rect& previous_bounds) { 195 divider_offset_ = CalculateDividerOffset(divider_offset_, previous_bounds, 196 bounds()); 197 } 198 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 220 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 243 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