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