Home | History | Annotate | Download | only in ui
      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 "chrome/browser/chromeos/ui/focus_ring_layer.h"
      6 
      7 #include "ash/system/tray/actionable_view.h"
      8 #include "ash/system/tray/tray_background_view.h"
      9 #include "ash/system/tray/tray_popup_header_button.h"
     10 #include "base/bind.h"
     11 #include "ui/aura/window.h"
     12 #include "ui/compositor/layer.h"
     13 #include "ui/gfx/canvas.h"
     14 #include "ui/gfx/image/canvas_image_source.h"
     15 #include "ui/gfx/shadow_value.h"
     16 #include "ui/gfx/skia_util.h"
     17 #include "ui/views/controls/button/label_button.h"
     18 #include "ui/views/painter.h"
     19 #include "ui/views/view.h"
     20 #include "ui/views/widget/widget.h"
     21 
     22 namespace chromeos {
     23 
     24 namespace {
     25 
     26 const int kShadowRadius = 23;
     27 const int kCenterBlockSize = 2 * kShadowRadius;
     28 const int kFocusRingImageSize = kShadowRadius * 2 + kCenterBlockSize;
     29 const SkColor kShadowColor = SkColorSetRGB(77, 144, 254);
     30 
     31 // FocusRingImageSource generates a base image that could be used by
     32 // ImagePainter to paint a focus ring around a rect. The base image is a
     33 // transparent square block of kCenterBlockSize pixels with blue halo around.
     34 class FocusRingImageSource : public gfx::CanvasImageSource {
     35  public:
     36   FocusRingImageSource()
     37       : CanvasImageSource(gfx::Size(kFocusRingImageSize, kFocusRingImageSize),
     38                           false) {
     39     shadows_.push_back(
     40         gfx::ShadowValue(gfx::Point(), kShadowRadius, kShadowColor));
     41   }
     42 
     43   virtual void Draw(gfx::Canvas* canvas) OVERRIDE {
     44     SkPaint paint;
     45     paint.setColor(kShadowColor);
     46 
     47     skia::RefPtr<SkDrawLooper> looper = CreateShadowDrawLooper(shadows_);
     48     paint.setLooper(looper.get());
     49 
     50     const gfx::Rect rect(kShadowRadius, kShadowRadius,
     51                          kCenterBlockSize, kCenterBlockSize);
     52     canvas->DrawRect(rect, paint);
     53     canvas->FillRect(rect, SK_ColorTRANSPARENT, SkXfermode::kSrc_Mode);
     54   }
     55 
     56  private:
     57   gfx::ShadowValues shadows_;
     58 
     59   DISALLOW_COPY_AND_ASSIGN(FocusRingImageSource);
     60 };
     61 
     62 }  // namespace
     63 
     64 FocusRingLayer::FocusRingLayer() {
     65   gfx::ImageSkia ring_image(
     66       new FocusRingImageSource,
     67       gfx::Size(kFocusRingImageSize, kFocusRingImageSize));
     68   ring_painter_.reset(views::Painter::CreateImagePainter(
     69       ring_image,
     70       gfx::Insets(kShadowRadius, kShadowRadius, kShadowRadius, kShadowRadius)));
     71 }
     72 
     73 FocusRingLayer::~FocusRingLayer() {}
     74 
     75 void FocusRingLayer::SetForView(views::View* view) {
     76   if (!view ||
     77       !view->GetWidget() ||
     78       !view->GetWidget()->GetNativeWindow() ||
     79       !view->GetWidget()->GetNativeWindow()->layer()) {
     80     layer_.reset();
     81     return;
     82   }
     83 
     84   if (!layer_) {
     85     layer_.reset(new ui::Layer(ui::LAYER_TEXTURED));
     86     layer_->set_name("FocusRing");
     87     layer_->set_delegate(this);
     88     layer_->SetFillsBoundsOpaquely(false);
     89   }
     90 
     91   // Puts the focus ring layer as a sibling layer of the widget layer so that
     92   // it does not clip at the widget's boundary.
     93   ui::Layer* widget_layer = view->GetWidget()->GetNativeWindow()->layer();
     94   widget_layer->parent()->Add(layer_.get());
     95 
     96   // Workarounds that attempts to pick a better bounds.
     97   gfx::Rect view_bounds = view->GetContentsBounds();
     98   if (view->GetClassName() == views::LabelButton::kViewClassName) {
     99     view_bounds = view->GetLocalBounds();
    100     view_bounds.Inset(2, 2, 2, 2);
    101   }
    102 
    103   // Workarounds for system tray items that has a customized OnPaintFocusBorder.
    104   // The insets here must be consistent with the ones used in OnPaintFocusBorder
    105   // and DrawBorder.
    106   if (view->GetClassName() ==
    107              ash::internal::ActionableView::kViewClassName) {
    108     view_bounds = view->GetLocalBounds();
    109     view_bounds.Inset(1, 1, 3, 3);
    110   } else if (view->GetClassName() ==
    111              ash::internal::TrayBackgroundView::kViewClassName) {
    112     view_bounds.Inset(1, 1, 3, 3);
    113   } else if (view->GetClassName() ==
    114              ash::internal::TrayPopupHeaderButton::kViewClassName) {
    115     view_bounds = view->GetLocalBounds();
    116     view_bounds.Inset(2, 1, 2, 2);
    117   }
    118 
    119   // Note the bounds calculation below assumes no transformation and ignores
    120   // animations.
    121   const gfx::Rect widget_bounds = widget_layer->GetTargetBounds();
    122   gfx::Rect bounds = view->ConvertRectToWidget(view_bounds);
    123   bounds.Offset(widget_bounds.OffsetFromOrigin());
    124   bounds.Inset(-kShadowRadius, -kShadowRadius, -kShadowRadius, -kShadowRadius);
    125   layer_->SetBounds(bounds);
    126 }
    127 
    128 void FocusRingLayer::OnPaintLayer(gfx::Canvas* canvas) {
    129   ring_painter_->Paint(canvas, layer_->bounds().size());
    130 }
    131 
    132 void FocusRingLayer::OnDeviceScaleFactorChanged(float device_scale_factor) {
    133 }
    134 
    135 base::Closure FocusRingLayer::PrepareForLayerBoundsChange() {
    136   return base::Bind(&base::DoNothing);
    137 }
    138 
    139 }  // namespace chromeos
    140