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   // 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