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