Home | History | Annotate | Download | only in controls
      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