Home | History | Annotate | Download | only in views
      1 // Copyright 2014 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/app_window_frame_view.h"
      6 
      7 #include "base/strings/utf_string_conversions.h"
      8 #include "extensions/browser/app_window/native_app_window.h"
      9 #include "extensions/common/draggable_region.h"
     10 #include "grit/theme_resources.h"
     11 #include "third_party/skia/include/core/SkPaint.h"
     12 #include "third_party/skia/include/core/SkRegion.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/color_utils.h"
     18 #include "ui/gfx/image/image.h"
     19 #include "ui/gfx/path.h"
     20 #include "ui/strings/grit/ui_strings.h"  // Accessibility names
     21 #include "ui/views/controls/button/image_button.h"
     22 #include "ui/views/layout/grid_layout.h"
     23 #include "ui/views/views_delegate.h"
     24 #include "ui/views/widget/widget.h"
     25 #include "ui/views/widget/widget_delegate.h"
     26 
     27 namespace {
     28 
     29 const int kDefaultResizeInsideBoundsSize = 5;
     30 const int kDefaultResizeAreaCornerSize = 16;
     31 const int kCaptionHeight = 25;
     32 
     33 }  // namespace
     34 
     35 namespace apps {
     36 
     37 const char AppWindowFrameView::kViewClassName[] =
     38     "browser/ui/views/extensions/AppWindowFrameView";
     39 
     40 AppWindowFrameView::AppWindowFrameView(views::Widget* widget,
     41                                        extensions::NativeAppWindow* window,
     42                                        bool draw_frame,
     43                                        const SkColor& active_frame_color,
     44                                        const SkColor& inactive_frame_color)
     45     : widget_(widget),
     46       window_(window),
     47       draw_frame_(draw_frame),
     48       active_frame_color_(active_frame_color),
     49       inactive_frame_color_(inactive_frame_color),
     50       close_button_(NULL),
     51       maximize_button_(NULL),
     52       restore_button_(NULL),
     53       minimize_button_(NULL),
     54       resize_inside_bounds_size_(kDefaultResizeInsideBoundsSize),
     55       resize_outside_bounds_size_(0),
     56       resize_area_corner_size_(kDefaultResizeAreaCornerSize) {
     57 }
     58 
     59 AppWindowFrameView::~AppWindowFrameView() {}
     60 
     61 void AppWindowFrameView::Init() {
     62   if (draw_frame_) {
     63     ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
     64     close_button_ = new views::ImageButton(this);
     65     close_button_->SetImage(
     66         views::CustomButton::STATE_NORMAL,
     67         rb.GetNativeImageNamed(IDR_APP_WINDOW_CLOSE).ToImageSkia());
     68     close_button_->SetImage(
     69         views::CustomButton::STATE_HOVERED,
     70         rb.GetNativeImageNamed(IDR_APP_WINDOW_CLOSE_H).ToImageSkia());
     71     close_button_->SetImage(
     72         views::CustomButton::STATE_PRESSED,
     73         rb.GetNativeImageNamed(IDR_APP_WINDOW_CLOSE_P).ToImageSkia());
     74     close_button_->SetAccessibleName(
     75         l10n_util::GetStringUTF16(IDS_APP_ACCNAME_CLOSE));
     76     AddChildView(close_button_);
     77     // STATE_NORMAL images are set in SetButtonImagesForFrame, not here.
     78     maximize_button_ = new views::ImageButton(this);
     79     maximize_button_->SetImage(
     80         views::CustomButton::STATE_HOVERED,
     81         rb.GetNativeImageNamed(IDR_APP_WINDOW_MAXIMIZE_H).ToImageSkia());
     82     maximize_button_->SetImage(
     83         views::CustomButton::STATE_PRESSED,
     84         rb.GetNativeImageNamed(IDR_APP_WINDOW_MAXIMIZE_P).ToImageSkia());
     85     maximize_button_->SetImage(
     86         views::CustomButton::STATE_DISABLED,
     87         rb.GetNativeImageNamed(IDR_APP_WINDOW_MAXIMIZE_D).ToImageSkia());
     88     maximize_button_->SetAccessibleName(
     89         l10n_util::GetStringUTF16(IDS_APP_ACCNAME_MAXIMIZE));
     90     AddChildView(maximize_button_);
     91     restore_button_ = new views::ImageButton(this);
     92     restore_button_->SetImage(
     93         views::CustomButton::STATE_HOVERED,
     94         rb.GetNativeImageNamed(IDR_APP_WINDOW_RESTORE_H).ToImageSkia());
     95     restore_button_->SetImage(
     96         views::CustomButton::STATE_PRESSED,
     97         rb.GetNativeImageNamed(IDR_APP_WINDOW_RESTORE_P).ToImageSkia());
     98     restore_button_->SetAccessibleName(
     99         l10n_util::GetStringUTF16(IDS_APP_ACCNAME_RESTORE));
    100     AddChildView(restore_button_);
    101     minimize_button_ = new views::ImageButton(this);
    102     minimize_button_->SetImage(
    103         views::CustomButton::STATE_HOVERED,
    104         rb.GetNativeImageNamed(IDR_APP_WINDOW_MINIMIZE_H).ToImageSkia());
    105     minimize_button_->SetImage(
    106         views::CustomButton::STATE_PRESSED,
    107         rb.GetNativeImageNamed(IDR_APP_WINDOW_MINIMIZE_P).ToImageSkia());
    108     minimize_button_->SetAccessibleName(
    109         l10n_util::GetStringUTF16(IDS_APP_ACCNAME_MINIMIZE));
    110     AddChildView(minimize_button_);
    111 
    112     SetButtonImagesForFrame();
    113   }
    114 }
    115 
    116 void AppWindowFrameView::SetResizeSizes(int resize_inside_bounds_size,
    117                                         int resize_outside_bounds_size,
    118                                         int resize_area_corner_size) {
    119   resize_inside_bounds_size_ = resize_inside_bounds_size;
    120   resize_outside_bounds_size_ = resize_outside_bounds_size;
    121   resize_area_corner_size_ = resize_area_corner_size;
    122 }
    123 
    124 // views::NonClientFrameView implementation.
    125 
    126 gfx::Rect AppWindowFrameView::GetBoundsForClientView() const {
    127   if (!draw_frame_ || widget_->IsFullscreen())
    128     return bounds();
    129   return gfx::Rect(
    130       0, kCaptionHeight, width(), std::max(0, height() - kCaptionHeight));
    131 }
    132 
    133 gfx::Rect AppWindowFrameView::GetWindowBoundsForClientBounds(
    134     const gfx::Rect& client_bounds) const {
    135   gfx::Rect window_bounds = client_bounds;
    136 #if defined(OS_LINUX) && !defined(OS_CHROMEOS)
    137   // Get the difference between the widget's client area bounds and window
    138   // bounds, and grow |window_bounds| by that amount.
    139   gfx::Insets native_frame_insets =
    140       widget_->GetClientAreaBoundsInScreen().InsetsFrom(
    141           widget_->GetWindowBoundsInScreen());
    142   window_bounds.Inset(native_frame_insets);
    143 #endif
    144   if (!draw_frame_) {
    145     // Enforce minimum size (1, 1) in case that client_bounds is passed with
    146     // empty size. This could occur when the frameless window is being
    147     // initialized.
    148     if (window_bounds.IsEmpty()) {
    149       window_bounds.set_width(1);
    150       window_bounds.set_height(1);
    151     }
    152     return window_bounds;
    153   }
    154 
    155   int closeButtonOffsetX = (kCaptionHeight - close_button_->height()) / 2;
    156   int header_width = close_button_->width() + closeButtonOffsetX * 2;
    157   return gfx::Rect(window_bounds.x(),
    158                    window_bounds.y() - kCaptionHeight,
    159                    std::max(header_width, window_bounds.width()),
    160                    window_bounds.height() + kCaptionHeight);
    161 }
    162 
    163 int AppWindowFrameView::NonClientHitTest(const gfx::Point& point) {
    164   if (widget_->IsFullscreen())
    165     return HTCLIENT;
    166 
    167   gfx::Rect expanded_bounds = bounds();
    168   if (resize_outside_bounds_size_) {
    169     expanded_bounds.Inset(gfx::Insets(-resize_outside_bounds_size_,
    170                                       -resize_outside_bounds_size_,
    171                                       -resize_outside_bounds_size_,
    172                                       -resize_outside_bounds_size_));
    173   }
    174   // Points outside the (possibly expanded) bounds can be discarded.
    175   if (!expanded_bounds.Contains(point))
    176     return HTNOWHERE;
    177 
    178   // Check the frame first, as we allow a small area overlapping the contents
    179   // to be used for resize handles.
    180   bool can_ever_resize = widget_->widget_delegate()
    181                              ? widget_->widget_delegate()->CanResize()
    182                              : false;
    183   // Don't allow overlapping resize handles when the window is maximized or
    184   // fullscreen, as it can't be resized in those states.
    185   int resize_border = (widget_->IsMaximized() || widget_->IsFullscreen())
    186                           ? 0
    187                           : resize_inside_bounds_size_;
    188   int frame_component = GetHTComponentForFrame(point,
    189                                                resize_border,
    190                                                resize_border,
    191                                                resize_area_corner_size_,
    192                                                resize_area_corner_size_,
    193                                                can_ever_resize);
    194   if (frame_component != HTNOWHERE)
    195     return frame_component;
    196 
    197   // Check for possible draggable region in the client area for the frameless
    198   // window.
    199   SkRegion* draggable_region = window_->GetDraggableRegion();
    200   if (draggable_region && draggable_region->contains(point.x(), point.y()))
    201     return HTCAPTION;
    202 
    203   int client_component = widget_->client_view()->NonClientHitTest(point);
    204   if (client_component != HTNOWHERE)
    205     return client_component;
    206 
    207   // Then see if the point is within any of the window controls.
    208   if (close_button_ && close_button_->visible() &&
    209       close_button_->GetMirroredBounds().Contains(point)) {
    210     return HTCLOSE;
    211   }
    212   if ((maximize_button_ && maximize_button_->visible() &&
    213        maximize_button_->GetMirroredBounds().Contains(point)) ||
    214       (restore_button_ && restore_button_->visible() &&
    215        restore_button_->GetMirroredBounds().Contains(point))) {
    216     return HTMAXBUTTON;
    217   }
    218   if (minimize_button_ && minimize_button_->visible() &&
    219       minimize_button_->GetMirroredBounds().Contains(point)) {
    220     return HTMINBUTTON;
    221   }
    222 
    223   // Caption is a safe default.
    224   return HTCAPTION;
    225 }
    226 
    227 void AppWindowFrameView::GetWindowMask(const gfx::Size& size,
    228                                        gfx::Path* window_mask) {
    229   // We got nothing to say about no window mask.
    230 }
    231 
    232 void AppWindowFrameView::SizeConstraintsChanged() {
    233   if (draw_frame_) {
    234     maximize_button_->SetEnabled(widget_->widget_delegate() &&
    235                                  widget_->widget_delegate()->CanMaximize());
    236   }
    237 }
    238 
    239 gfx::Size AppWindowFrameView::GetPreferredSize() const {
    240   gfx::Size pref = widget_->client_view()->GetPreferredSize();
    241   gfx::Rect bounds(0, 0, pref.width(), pref.height());
    242   return widget_->non_client_view()
    243       ->GetWindowBoundsForClientBounds(bounds)
    244       .size();
    245 }
    246 
    247 void AppWindowFrameView::Layout() {
    248   if (!draw_frame_)
    249     return;
    250   gfx::Size close_size = close_button_->GetPreferredSize();
    251   const int kButtonOffsetY = 0;
    252   const int kButtonSpacing = 1;
    253   const int kRightMargin = 3;
    254 
    255   close_button_->SetBounds(width() - kRightMargin - close_size.width(),
    256                            kButtonOffsetY,
    257                            close_size.width(),
    258                            close_size.height());
    259 
    260   maximize_button_->SetEnabled(widget_->widget_delegate() &&
    261                                widget_->widget_delegate()->CanMaximize());
    262   gfx::Size maximize_size = maximize_button_->GetPreferredSize();
    263   maximize_button_->SetBounds(
    264       close_button_->x() - kButtonSpacing - maximize_size.width(),
    265       kButtonOffsetY,
    266       maximize_size.width(),
    267       maximize_size.height());
    268   gfx::Size restore_size = restore_button_->GetPreferredSize();
    269   restore_button_->SetBounds(
    270       close_button_->x() - kButtonSpacing - restore_size.width(),
    271       kButtonOffsetY,
    272       restore_size.width(),
    273       restore_size.height());
    274 
    275   bool maximized = widget_->IsMaximized();
    276   maximize_button_->SetVisible(!maximized);
    277   restore_button_->SetVisible(maximized);
    278   if (maximized)
    279     maximize_button_->SetState(views::CustomButton::STATE_NORMAL);
    280   else
    281     restore_button_->SetState(views::CustomButton::STATE_NORMAL);
    282 
    283   gfx::Size minimize_size = minimize_button_->GetPreferredSize();
    284   minimize_button_->SetState(views::CustomButton::STATE_NORMAL);
    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 AppWindowFrameView::OnPaint(gfx::Canvas* canvas) {
    293   if (!draw_frame_)
    294     return;
    295 
    296   ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
    297   if (ShouldPaintAsActive()) {
    298     close_button_->SetImage(
    299         views::CustomButton::STATE_NORMAL,
    300         rb.GetNativeImageNamed(IDR_APP_WINDOW_CLOSE).ToImageSkia());
    301   } else {
    302     close_button_->SetImage(
    303         views::CustomButton::STATE_NORMAL,
    304         rb.GetNativeImageNamed(IDR_APP_WINDOW_CLOSE_U).ToImageSkia());
    305   }
    306 
    307   SetButtonImagesForFrame();
    308   // TODO(benwells): different look for inactive by default.
    309   SkPaint paint;
    310   paint.setAntiAlias(false);
    311   paint.setStyle(SkPaint::kFill_Style);
    312   paint.setColor(CurrentFrameColor());
    313   gfx::Path path;
    314   path.moveTo(0, 0);
    315   path.lineTo(width(), 0);
    316   path.lineTo(width(), kCaptionHeight);
    317   path.lineTo(0, kCaptionHeight);
    318   path.close();
    319   canvas->DrawPath(path, paint);
    320 }
    321 
    322 const char* AppWindowFrameView::GetClassName() const { return kViewClassName; }
    323 
    324 gfx::Size AppWindowFrameView::GetMinimumSize() const {
    325   gfx::Size min_size = widget_->client_view()->GetMinimumSize();
    326   if (!draw_frame_) {
    327     min_size.SetToMax(gfx::Size(1, 1));
    328     return min_size;
    329   }
    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 = (kCaptionHeight - close_button_->height()) / 2;
    337   int header_width = close_button_->width() + closeButtonOffsetX * 2;
    338   if (header_width > min_size.width())
    339     min_size.set_width(header_width);
    340   return min_size;
    341 }
    342 
    343 gfx::Size AppWindowFrameView::GetMaximumSize() const {
    344   gfx::Size max_size = widget_->client_view()->GetMaximumSize();
    345 
    346   // Add to the client maximum size the height of any title bar and borders.
    347   gfx::Size client_size = GetBoundsForClientView().size();
    348   if (max_size.width())
    349     max_size.Enlarge(width() - client_size.width(), 0);
    350   if (max_size.height())
    351     max_size.Enlarge(0, height() - client_size.height());
    352 
    353   return max_size;
    354 }
    355 
    356 void AppWindowFrameView::ButtonPressed(views::Button* sender,
    357                                        const ui::Event& event) {
    358   DCHECK(draw_frame_);
    359   if (sender == close_button_)
    360     widget_->Close();
    361   else if (sender == maximize_button_)
    362     widget_->Maximize();
    363   else if (sender == restore_button_)
    364     widget_->Restore();
    365   else if (sender == minimize_button_)
    366     widget_->Minimize();
    367 }
    368 
    369 SkColor AppWindowFrameView::CurrentFrameColor() {
    370   return widget_->IsActive() ? active_frame_color_ : inactive_frame_color_;
    371 }
    372 
    373 void AppWindowFrameView::SetButtonImagesForFrame() {
    374   DCHECK(draw_frame_);
    375 
    376   // If the frame is dark, we should use the light images so they have
    377   // some contrast.
    378   unsigned char frame_luma =
    379       color_utils::GetLuminanceForColor(CurrentFrameColor());
    380   const unsigned char kLuminanceThreshold = 100;
    381   bool use_light = frame_luma < kLuminanceThreshold;
    382 
    383   ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
    384   if (use_light) {
    385     maximize_button_->SetImage(
    386         views::CustomButton::STATE_NORMAL,
    387         rb.GetNativeImageNamed(IDR_APP_WINDOW_MAXIMIZE_L).ToImageSkia());
    388     restore_button_->SetImage(
    389         views::CustomButton::STATE_NORMAL,
    390         rb.GetNativeImageNamed(IDR_APP_WINDOW_RESTORE_L).ToImageSkia());
    391     minimize_button_->SetImage(
    392         views::CustomButton::STATE_NORMAL,
    393         rb.GetNativeImageNamed(IDR_APP_WINDOW_MINIMIZE_L).ToImageSkia());
    394   } else {
    395     maximize_button_->SetImage(
    396         views::CustomButton::STATE_NORMAL,
    397         rb.GetNativeImageNamed(IDR_APP_WINDOW_MAXIMIZE).ToImageSkia());
    398     restore_button_->SetImage(
    399         views::CustomButton::STATE_NORMAL,
    400         rb.GetNativeImageNamed(IDR_APP_WINDOW_RESTORE).ToImageSkia());
    401     minimize_button_->SetImage(
    402         views::CustomButton::STATE_NORMAL,
    403         rb.GetNativeImageNamed(IDR_APP_WINDOW_MINIMIZE).ToImageSkia());
    404   }
    405 }
    406 
    407 }  // namespace apps
    408