Home | History | Annotate | Download | only in views
      1 // Copyright 2013 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 "apps/ui/views/shell_window_frame_view.h"
      6 
      7 #include "apps/ui/native_app_window.h"
      8 #include "base/strings/utf_string_conversions.h"
      9 #include "extensions/common/draggable_region.h"
     10 #include "grit/theme_resources.h"
     11 #include "grit/ui_strings.h"  // Accessibility names
     12 #include "third_party/skia/include/core/SkPaint.h"
     13 #include "ui/base/hit_test.h"
     14 #include "ui/base/l10n/l10n_util.h"
     15 #include "ui/base/resource/resource_bundle.h"
     16 #include "ui/gfx/canvas.h"
     17 #include "ui/gfx/image/image.h"
     18 #include "ui/gfx/path.h"
     19 #include "ui/views/controls/button/image_button.h"
     20 #include "ui/views/layout/grid_layout.h"
     21 #include "ui/views/views_delegate.h"
     22 #include "ui/views/widget/widget.h"
     23 #include "ui/views/widget/widget_delegate.h"
     24 
     25 #if defined(USE_AURA)
     26 #include "ui/aura/env.h"
     27 #include "ui/aura/window.h"
     28 #endif
     29 
     30 namespace {
     31 // Height of the chrome-style caption, in pixels.
     32 const int kCaptionHeight = 25;
     33 }  // namespace
     34 
     35 namespace apps {
     36 
     37 const char ShellWindowFrameView::kViewClassName[] =
     38     "browser/ui/views/extensions/ShellWindowFrameView";
     39 
     40 ShellWindowFrameView::ShellWindowFrameView(NativeAppWindow* window)
     41     : window_(window),
     42       frame_(NULL),
     43       close_button_(NULL),
     44       maximize_button_(NULL),
     45       restore_button_(NULL),
     46       minimize_button_(NULL),
     47       resize_inside_bounds_size_(0),
     48       resize_area_corner_size_(0) {
     49 }
     50 
     51 ShellWindowFrameView::~ShellWindowFrameView() {
     52 }
     53 
     54 void ShellWindowFrameView::Init(views::Widget* frame,
     55                                 int resize_inside_bounds_size,
     56                                 int resize_outside_bounds_size,
     57                                 int resize_outside_scale_for_touch,
     58                                 int resize_area_corner_size) {
     59   frame_ = frame;
     60   resize_inside_bounds_size_ = resize_inside_bounds_size;
     61   resize_area_corner_size_ = resize_area_corner_size;
     62 
     63   if (!window_->IsFrameless()) {
     64     ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
     65     close_button_ = new views::ImageButton(this);
     66     close_button_->SetImage(views::CustomButton::STATE_NORMAL,
     67         rb.GetNativeImageNamed(IDR_APP_WINDOW_CLOSE).ToImageSkia());
     68     close_button_->SetImage(views::CustomButton::STATE_HOVERED,
     69         rb.GetNativeImageNamed(IDR_APP_WINDOW_CLOSE_H).ToImageSkia());
     70     close_button_->SetImage(views::CustomButton::STATE_PRESSED,
     71         rb.GetNativeImageNamed(IDR_APP_WINDOW_CLOSE_P).ToImageSkia());
     72     close_button_->SetAccessibleName(
     73         l10n_util::GetStringUTF16(IDS_APP_ACCNAME_CLOSE));
     74     AddChildView(close_button_);
     75     maximize_button_ = new views::ImageButton(this);
     76     maximize_button_->SetImage(views::CustomButton::STATE_NORMAL,
     77         rb.GetNativeImageNamed(IDR_APP_WINDOW_MAXIMIZE).ToImageSkia());
     78     maximize_button_->SetImage(views::CustomButton::STATE_HOVERED,
     79         rb.GetNativeImageNamed(IDR_APP_WINDOW_MAXIMIZE_H).ToImageSkia());
     80     maximize_button_->SetImage(views::CustomButton::STATE_PRESSED,
     81         rb.GetNativeImageNamed(IDR_APP_WINDOW_MAXIMIZE_P).ToImageSkia());
     82     maximize_button_->SetImage(views::CustomButton::STATE_DISABLED,
     83         rb.GetNativeImageNamed(IDR_APP_WINDOW_MAXIMIZE_D).ToImageSkia());
     84     maximize_button_->SetAccessibleName(
     85         l10n_util::GetStringUTF16(IDS_APP_ACCNAME_MAXIMIZE));
     86     AddChildView(maximize_button_);
     87     restore_button_ = new views::ImageButton(this);
     88     restore_button_->SetImage(views::CustomButton::STATE_NORMAL,
     89         rb.GetNativeImageNamed(IDR_APP_WINDOW_RESTORE).ToImageSkia());
     90     restore_button_->SetImage(views::CustomButton::STATE_HOVERED,
     91         rb.GetNativeImageNamed(IDR_APP_WINDOW_RESTORE_H).ToImageSkia());
     92     restore_button_->SetImage(views::CustomButton::STATE_PRESSED,
     93         rb.GetNativeImageNamed(IDR_APP_WINDOW_RESTORE_P).ToImageSkia());
     94     restore_button_->SetAccessibleName(
     95         l10n_util::GetStringUTF16(IDS_APP_ACCNAME_RESTORE));
     96     AddChildView(restore_button_);
     97     minimize_button_ = new views::ImageButton(this);
     98     minimize_button_->SetImage(views::CustomButton::STATE_NORMAL,
     99         rb.GetNativeImageNamed(IDR_APP_WINDOW_MINIMIZE).ToImageSkia());
    100     minimize_button_->SetImage(views::CustomButton::STATE_HOVERED,
    101         rb.GetNativeImageNamed(IDR_APP_WINDOW_MINIMIZE_H).ToImageSkia());
    102     minimize_button_->SetImage(views::CustomButton::STATE_PRESSED,
    103         rb.GetNativeImageNamed(IDR_APP_WINDOW_MINIMIZE_P).ToImageSkia());
    104     minimize_button_->SetAccessibleName(
    105         l10n_util::GetStringUTF16(IDS_APP_ACCNAME_MINIMIZE));
    106     AddChildView(minimize_button_);
    107   }
    108 
    109 #if defined(USE_AURA)
    110   aura::Window* window = frame->GetNativeWindow();
    111   // Some Aura implementations (Ash) allow resize handles outside the window.
    112   if (resize_outside_bounds_size > 0) {
    113     gfx::Insets mouse_insets = gfx::Insets(-resize_outside_bounds_size,
    114                                            -resize_outside_bounds_size,
    115                                            -resize_outside_bounds_size,
    116                                            -resize_outside_bounds_size);
    117     gfx::Insets touch_insets =
    118         mouse_insets.Scale(resize_outside_scale_for_touch);
    119     // Ensure we get resize cursors for a few pixels outside our bounds.
    120     window->SetHitTestBoundsOverrideOuter(mouse_insets, touch_insets);
    121   }
    122   // Ensure we get resize cursors just inside our bounds as well.
    123   // TODO(jeremya): do we need to update these when in fullscreen/maximized?
    124   window->set_hit_test_bounds_override_inner(
    125       gfx::Insets(resize_inside_bounds_size_, resize_inside_bounds_size_,
    126                   resize_inside_bounds_size_, resize_inside_bounds_size_));
    127 #endif
    128 }
    129 
    130 // views::NonClientFrameView implementation.
    131 
    132 gfx::Rect ShellWindowFrameView::GetBoundsForClientView() const {
    133   if (window_->IsFrameless() || frame_->IsFullscreen())
    134     return bounds();
    135   return gfx::Rect(0, kCaptionHeight, width(),
    136       std::max(0, height() - kCaptionHeight));
    137 }
    138 
    139 gfx::Rect ShellWindowFrameView::GetWindowBoundsForClientBounds(
    140       const gfx::Rect& client_bounds) const {
    141   if (window_->IsFrameless()) {
    142     gfx::Rect window_bounds = client_bounds;
    143     // Enforce minimum size (1, 1) in case that client_bounds is passed with
    144     // empty size. This could occur when the frameless window is being
    145     // initialized.
    146     if (window_bounds.IsEmpty()) {
    147       window_bounds.set_width(1);
    148       window_bounds.set_height(1);
    149     }
    150     return window_bounds;
    151   }
    152 
    153   int closeButtonOffsetX =
    154       (kCaptionHeight - close_button_->height()) / 2;
    155   int header_width = close_button_->width() + closeButtonOffsetX * 2;
    156   return gfx::Rect(client_bounds.x(),
    157                    std::max(0, client_bounds.y() - kCaptionHeight),
    158                    std::max(header_width, client_bounds.width()),
    159                    client_bounds.height() + kCaptionHeight);
    160 }
    161 
    162 int ShellWindowFrameView::NonClientHitTest(const gfx::Point& point) {
    163   if (frame_->IsFullscreen())
    164     return HTCLIENT;
    165 
    166   gfx::Rect expanded_bounds = bounds();
    167 #if defined(USE_AURA)
    168   // Some Aura implementations (Ash) optionally allow resize handles just
    169   // outside the window bounds.
    170   aura::Window* window = frame_->GetNativeWindow();
    171   if (aura::Env::GetInstance()->is_touch_down())
    172     expanded_bounds.Inset(window->hit_test_bounds_override_outer_touch());
    173   else
    174     expanded_bounds.Inset(window->hit_test_bounds_override_outer_mouse());
    175 #endif
    176   // Points outside the (possibly expanded) bounds can be discarded.
    177   if (!expanded_bounds.Contains(point))
    178     return HTNOWHERE;
    179 
    180   // Check the frame first, as we allow a small area overlapping the contents
    181   // to be used for resize handles.
    182   bool can_ever_resize = frame_->widget_delegate() ?
    183       frame_->widget_delegate()->CanResize() :
    184       false;
    185   // Don't allow overlapping resize handles when the window is maximized or
    186   // fullscreen, as it can't be resized in those states.
    187   int resize_border =
    188       (frame_->IsMaximized() || frame_->IsFullscreen()) ? 0 :
    189       resize_inside_bounds_size_;
    190   int frame_component = GetHTComponentForFrame(point,
    191                                                resize_border,
    192                                                resize_border,
    193                                                resize_area_corner_size_,
    194                                                resize_area_corner_size_,
    195                                                can_ever_resize);
    196   if (frame_component != HTNOWHERE)
    197     return frame_component;
    198 
    199   // Check for possible draggable region in the client area for the frameless
    200   // window.
    201   if (window_->IsFrameless()) {
    202     SkRegion* draggable_region = window_->GetDraggableRegion();
    203     if (draggable_region && draggable_region->contains(point.x(), point.y()))
    204       return HTCAPTION;
    205   }
    206 
    207   int client_component = frame_->client_view()->NonClientHitTest(point);
    208   if (client_component != HTNOWHERE)
    209     return client_component;
    210 
    211   // Then see if the point is within any of the window controls.
    212   if (close_button_ && close_button_->visible() &&
    213       close_button_->GetMirroredBounds().Contains(point)) {
    214     return HTCLOSE;
    215   }
    216   if ((maximize_button_ && maximize_button_->visible() &&
    217        maximize_button_->GetMirroredBounds().Contains(point)) ||
    218       (restore_button_ && restore_button_->visible() &&
    219        restore_button_->GetMirroredBounds().Contains(point))) {
    220     return HTMAXBUTTON;
    221   }
    222   if (minimize_button_ && minimize_button_->visible() &&
    223       minimize_button_->GetMirroredBounds().Contains(point)) {
    224     return HTMINBUTTON;
    225   }
    226 
    227   // Caption is a safe default.
    228   return HTCAPTION;
    229 }
    230 
    231 void ShellWindowFrameView::GetWindowMask(const gfx::Size& size,
    232                                          gfx::Path* window_mask) {
    233   // We got nothing to say about no window mask.
    234 }
    235 
    236 // views::View implementation.
    237 
    238 gfx::Size ShellWindowFrameView::GetPreferredSize() {
    239   gfx::Size pref = frame_->client_view()->GetPreferredSize();
    240   gfx::Rect bounds(0, 0, pref.width(), pref.height());
    241   return frame_->non_client_view()->GetWindowBoundsForClientBounds(
    242       bounds).size();
    243 }
    244 
    245 void ShellWindowFrameView::Layout() {
    246   if (window_->IsFrameless())
    247     return;
    248   gfx::Size close_size = close_button_->GetPreferredSize();
    249   const int kButtonOffsetY = 0;
    250   const int kButtonSpacing = 1;
    251   const int kRightMargin = 3;
    252 
    253   close_button_->SetBounds(
    254       width() - kRightMargin - close_size.width(),
    255       kButtonOffsetY,
    256       close_size.width(),
    257       close_size.height());
    258 
    259   bool can_ever_resize = frame_->widget_delegate() ?
    260       frame_->widget_delegate()->CanResize() :
    261       false;
    262   maximize_button_->SetEnabled(can_ever_resize);
    263   gfx::Size maximize_size = maximize_button_->GetPreferredSize();
    264   maximize_button_->SetBounds(
    265       close_button_->x() - kButtonSpacing - maximize_size.width(),
    266       kButtonOffsetY,
    267       maximize_size.width(),
    268       maximize_size.height());
    269   gfx::Size restore_size = restore_button_->GetPreferredSize();
    270   restore_button_->SetBounds(
    271       close_button_->x() - kButtonSpacing - restore_size.width(),
    272       kButtonOffsetY,
    273       restore_size.width(),
    274       restore_size.height());
    275 
    276   bool maximized = frame_->IsMaximized();
    277   maximize_button_->SetVisible(!maximized);
    278   restore_button_->SetVisible(maximized);
    279   if (maximized)
    280     maximize_button_->SetState(views::CustomButton::STATE_NORMAL);
    281   else
    282     restore_button_->SetState(views::CustomButton::STATE_NORMAL);
    283 
    284   gfx::Size minimize_size = minimize_button_->GetPreferredSize();
    285   minimize_button_->SetBounds(
    286       maximize_button_->x() - kButtonSpacing - minimize_size.width(),
    287       kButtonOffsetY,
    288       minimize_size.width(),
    289       minimize_size.height());
    290 }
    291 
    292 void ShellWindowFrameView::OnPaint(gfx::Canvas* canvas) {
    293   if (window_->IsFrameless())
    294     return;
    295 
    296   ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
    297   if (ShouldPaintAsActive()) {
    298     close_button_->SetImage(views::CustomButton::STATE_NORMAL,
    299         rb.GetNativeImageNamed(IDR_APP_WINDOW_CLOSE).ToImageSkia());
    300   } else {
    301     close_button_->SetImage(views::CustomButton::STATE_NORMAL,
    302         rb.GetNativeImageNamed(IDR_APP_WINDOW_CLOSE_U).ToImageSkia());
    303   }
    304 
    305   // TODO(jeremya): different look for inactive?
    306   SkPaint paint;
    307   paint.setAntiAlias(false);
    308   paint.setStyle(SkPaint::kFill_Style);
    309   paint.setColor(SK_ColorWHITE);
    310   gfx::Path path;
    311   const int radius = frame_->IsMaximized() ? 0 : 1;
    312   path.moveTo(0, radius);
    313   path.lineTo(radius, 0);
    314   path.lineTo(width() - radius - 1, 0);
    315   path.lineTo(width(), radius + 1);
    316   path.lineTo(width(), kCaptionHeight);
    317   path.lineTo(0, kCaptionHeight);
    318   path.close();
    319   canvas->DrawPath(path, paint);
    320 }
    321 
    322 const char* ShellWindowFrameView::GetClassName() const {
    323   return kViewClassName;
    324 }
    325 
    326 gfx::Size ShellWindowFrameView::GetMinimumSize() {
    327   gfx::Size min_size = frame_->client_view()->GetMinimumSize();
    328   if (window_->IsFrameless())
    329     return min_size;
    330 
    331   // Ensure we can display the top of the caption area.
    332   gfx::Rect client_bounds = GetBoundsForClientView();
    333   min_size.Enlarge(0, client_bounds.y());
    334   // Ensure we have enough space for the window icon and buttons.  We allow
    335   // the title string to collapse to zero width.
    336   int closeButtonOffsetX =
    337       (kCaptionHeight - close_button_->height()) / 2;
    338   int header_width = close_button_->width() + closeButtonOffsetX * 2;
    339   if (header_width > min_size.width())
    340     min_size.set_width(header_width);
    341   return min_size;
    342 }
    343 
    344 gfx::Size ShellWindowFrameView::GetMaximumSize() {
    345   gfx::Size max_size = frame_->client_view()->GetMaximumSize();
    346 
    347   // Add to the client maximum size the height of any title bar and borders.
    348   gfx::Size client_size = GetBoundsForClientView().size();
    349   if (max_size.width())
    350     max_size.Enlarge(width() - client_size.width(), 0);
    351   if (max_size.height())
    352     max_size.Enlarge(0, height() - client_size.height());
    353 
    354   return max_size;
    355 }
    356 
    357 // views::ButtonListener implementation.
    358 
    359 void ShellWindowFrameView::ButtonPressed(views::Button* sender,
    360                                          const ui::Event& event) {
    361   DCHECK(!window_->IsFrameless());
    362   if (sender == close_button_)
    363     frame_->Close();
    364   else if (sender == maximize_button_)
    365     frame_->Maximize();
    366   else if (sender == restore_button_)
    367     frame_->Restore();
    368   else if (sender == minimize_button_)
    369     frame_->Minimize();
    370 }
    371 
    372 }  // namespace apps
    373