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 "ash/sticky_keys/sticky_keys_overlay.h"
6
7#include "ash/shell.h"
8#include "ash/shell_window_ids.h"
9#include "ash/sticky_keys/sticky_keys_controller.h"
10#include "base/strings/string_util.h"
11#include "base/strings/utf_string_conversions.h"
12#include "grit/ash_strings.h"
13#include "ui/base/l10n/l10n_util.h"
14#include "ui/base/resource/resource_bundle.h"
15#include "ui/compositor/scoped_layer_animation_settings.h"
16#include "ui/gfx/canvas.h"
17#include "ui/gfx/font_list.h"
18#include "ui/views/border.h"
19#include "ui/views/controls/label.h"
20#include "ui/views/layout/box_layout.h"
21#include "ui/views/view.h"
22#include "ui/views/widget/widget.h"
23#include "ui/views/widget/widget_delegate.h"
24
25namespace ash {
26
27namespace {
28
29// Horizontal offset of the overlay from the top left of the screen.
30const int kHorizontalOverlayOffset = 18;
31
32// Vertical offset of the overlay from the top left of the screen.
33const int kVerticalOverlayOffset = 18;
34
35// Font style used for modifier key labels.
36const ui::ResourceBundle::FontStyle kKeyLabelFontStyle =
37    ui::ResourceBundle::LargeFont;
38
39// Duration of slide animation when overlay is shown or hidden.
40const int kSlideAnimationDurationMs = 100;
41
42}
43
44///////////////////////////////////////////////////////////////////////////////
45//  StickyKeyOverlayLabel
46class StickyKeyOverlayLabel : public views::Label {
47 public:
48  explicit StickyKeyOverlayLabel(const std::string& key_name);
49
50  virtual ~StickyKeyOverlayLabel();
51
52  StickyKeyState state() const { return state_; }
53
54  void SetKeyState(StickyKeyState state);
55
56 private:
57  StickyKeyState state_;
58
59  DISALLOW_COPY_AND_ASSIGN(StickyKeyOverlayLabel);
60};
61
62StickyKeyOverlayLabel::StickyKeyOverlayLabel(const std::string& key_name)
63    : state_(STICKY_KEY_STATE_DISABLED) {
64  ui::ResourceBundle* rb = &ui::ResourceBundle::GetSharedInstance();
65
66  SetText(base::UTF8ToUTF16(key_name));
67  SetHorizontalAlignment(gfx::ALIGN_LEFT);
68  SetFontList(rb->GetFontList(kKeyLabelFontStyle));
69  SetAutoColorReadabilityEnabled(false);
70  SetFocusable(false);
71  SetEnabledColor(SkColorSetARGB(0x80, 0xFF, 0xFF, 0xFF));
72  SetDisabledColor(SkColorSetARGB(0x80, 0xFF, 0xFF, 0xFF));
73  SetSubpixelRenderingEnabled(false);
74}
75
76StickyKeyOverlayLabel::~StickyKeyOverlayLabel() {
77}
78
79void StickyKeyOverlayLabel::SetKeyState(StickyKeyState state) {
80  state_ = state;
81  SkColor label_color;
82  int style;
83  switch (state) {
84    case STICKY_KEY_STATE_ENABLED:
85      style = gfx::Font::NORMAL;
86      label_color = SkColorSetA(enabled_color(), 0xFF);
87      break;
88    case STICKY_KEY_STATE_LOCKED:
89      style = gfx::Font::UNDERLINE;
90      label_color = SkColorSetA(enabled_color(), 0xFF);
91      break;
92    default:
93      style = gfx::Font::NORMAL;
94      label_color = SkColorSetA(enabled_color(), 0x80);
95  }
96
97  SetEnabledColor(label_color);
98  SetDisabledColor(label_color);
99  SetFontList(font_list().DeriveWithStyle(style));
100}
101
102///////////////////////////////////////////////////////////////////////////////
103//  StickyKeysOverlayView
104class StickyKeysOverlayView : public views::WidgetDelegateView {
105 public:
106  StickyKeysOverlayView();
107
108  virtual ~StickyKeysOverlayView();
109
110  // views::WidgetDelegateView overrides:
111  virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE;
112
113  void SetKeyState(ui::EventFlags modifier, StickyKeyState state);
114
115  StickyKeyState GetKeyState(ui::EventFlags modifier);
116
117  void SetModifierVisible(ui::EventFlags modifier, bool visible);
118  bool GetModifierVisible(ui::EventFlags modifier);
119
120 private:
121  void AddKeyLabel(ui::EventFlags modifier, const std::string& key_label);
122
123  typedef std::map<ui::EventFlags, StickyKeyOverlayLabel*> ModifierLabelMap;
124  ModifierLabelMap modifier_label_map_;
125
126  DISALLOW_COPY_AND_ASSIGN(StickyKeysOverlayView);
127};
128
129StickyKeysOverlayView::StickyKeysOverlayView() {
130  const gfx::Font& font =
131      ui::ResourceBundle::GetSharedInstance().GetFont(kKeyLabelFontStyle);
132  int font_size = font.GetFontSize();
133  int font_padding = font.GetHeight() - font.GetBaseline();
134
135  // Text should have a margin of 0.5 times the font size on each side, so
136  // the spacing between two labels will be the same as the font size.
137  int horizontal_spacing = font_size / 2;
138  int vertical_spacing = font_size / 2 - font_padding;
139  int child_spacing = font_size - 2 * font_padding;
140
141  SetLayoutManager(new views::BoxLayout(views::BoxLayout::kVertical,
142                                        horizontal_spacing,
143                                        vertical_spacing,
144                                        child_spacing));
145  AddKeyLabel(ui::EF_CONTROL_DOWN,
146              l10n_util::GetStringUTF8(IDS_ASH_CONTROL_KEY));
147  AddKeyLabel(ui::EF_ALT_DOWN,
148              l10n_util::GetStringUTF8(IDS_ASH_ALT_KEY));
149  AddKeyLabel(ui::EF_SHIFT_DOWN,
150              l10n_util::GetStringUTF8(IDS_ASH_SHIFT_KEY));
151  AddKeyLabel(ui::EF_ALTGR_DOWN,
152              l10n_util::GetStringUTF8(IDS_ASH_ALTGR_KEY));
153  AddKeyLabel(ui::EF_MOD3_DOWN,
154              l10n_util::GetStringUTF8(IDS_ASH_MOD3_KEY));
155}
156
157StickyKeysOverlayView::~StickyKeysOverlayView() {}
158
159void StickyKeysOverlayView::OnPaint(gfx::Canvas* canvas) {
160  SkPaint paint;
161  paint.setStyle(SkPaint::kFill_Style);
162  paint.setColor(SkColorSetARGB(0xB3, 0x55, 0x55, 0x55));
163  canvas->DrawRoundRect(GetLocalBounds(), 2, paint);
164  views::WidgetDelegateView::OnPaint(canvas);
165}
166
167void StickyKeysOverlayView::SetKeyState(ui::EventFlags modifier,
168                                        StickyKeyState state) {
169  ModifierLabelMap::iterator it = modifier_label_map_.find(modifier);
170  DCHECK(it != modifier_label_map_.end());
171  if (it != modifier_label_map_.end()) {
172    StickyKeyOverlayLabel* label = it->second;
173    label->SetKeyState(state);
174  }
175}
176
177StickyKeyState StickyKeysOverlayView::GetKeyState(ui::EventFlags modifier) {
178  ModifierLabelMap::iterator it = modifier_label_map_.find(modifier);
179  DCHECK(it != modifier_label_map_.end());
180  return it->second->state();
181}
182
183void StickyKeysOverlayView::SetModifierVisible(ui::EventFlags modifier,
184                                               bool visible) {
185  ModifierLabelMap::iterator it = modifier_label_map_.find(modifier);
186  DCHECK(it != modifier_label_map_.end());
187  it->second->SetVisible(visible);
188}
189
190bool StickyKeysOverlayView::GetModifierVisible(ui::EventFlags modifier) {
191  ModifierLabelMap::iterator it = modifier_label_map_.find(modifier);
192  DCHECK(it != modifier_label_map_.end());
193  return it->second->visible();
194}
195
196void StickyKeysOverlayView::AddKeyLabel(ui::EventFlags modifier,
197                                        const std::string& key_label) {
198  StickyKeyOverlayLabel* label = new StickyKeyOverlayLabel(key_label);
199  AddChildView(label);
200  modifier_label_map_[modifier] = label;
201}
202
203///////////////////////////////////////////////////////////////////////////////
204//  StickyKeysOverlay
205StickyKeysOverlay::StickyKeysOverlay()
206    : is_visible_(false),
207      overlay_view_(new StickyKeysOverlayView),
208      widget_size_(overlay_view_->GetPreferredSize()) {
209  views::Widget::InitParams params;
210  params.type = views::Widget::InitParams::TYPE_POPUP;
211  params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW;
212  params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
213  params.accept_events = false;
214  params.keep_on_top = true;
215  params.remove_standard_frame = true;
216  params.delegate = overlay_view_;
217  params.bounds = CalculateOverlayBounds();
218  params.parent = Shell::GetContainer(Shell::GetTargetRootWindow(),
219                                      kShellWindowId_OverlayContainer);
220  overlay_widget_.reset(new views::Widget);
221  overlay_widget_->Init(params);
222  overlay_widget_->SetVisibilityChangedAnimationsEnabled(false);
223  overlay_widget_->SetContentsView(overlay_view_);
224  overlay_widget_->GetNativeView()->SetName("StickyKeysOverlay");
225}
226
227StickyKeysOverlay::~StickyKeysOverlay() {}
228
229void StickyKeysOverlay::Show(bool visible) {
230  if (is_visible_ == visible)
231    return;
232
233  is_visible_ = visible;
234  if (is_visible_)
235    overlay_widget_->Show();
236  overlay_widget_->SetBounds(CalculateOverlayBounds());
237
238  ui::LayerAnimator* animator = overlay_widget_->GetLayer()->GetAnimator();
239  animator->AddObserver(this);
240
241  // Ensure transform is correct before beginning animation.
242  if (!animator->is_animating()) {
243    int sign = is_visible_ ? -1 : 1;
244    gfx::Transform transform;
245    transform.Translate(
246        sign * (widget_size_.width() + kHorizontalOverlayOffset), 0);
247    overlay_widget_->GetLayer()->SetTransform(transform);
248  }
249
250  ui::ScopedLayerAnimationSettings settings(animator);
251  settings.SetPreemptionStrategy(
252      ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
253  settings.SetTweenType(visible ? gfx::Tween::EASE_OUT : gfx::Tween::EASE_IN);
254  settings.SetTransitionDuration(
255      base::TimeDelta::FromMilliseconds(kSlideAnimationDurationMs));
256
257  overlay_widget_->GetLayer()->SetTransform(gfx::Transform());
258}
259
260void StickyKeysOverlay::SetModifierVisible(ui::EventFlags modifier,
261                                           bool visible) {
262  overlay_view_->SetModifierVisible(modifier, visible);
263  widget_size_ = overlay_view_->GetPreferredSize();
264}
265
266bool StickyKeysOverlay::GetModifierVisible(ui::EventFlags modifier) {
267  return overlay_view_->GetModifierVisible(modifier);
268}
269
270void StickyKeysOverlay::SetModifierKeyState(ui::EventFlags modifier,
271                                            StickyKeyState state) {
272  overlay_view_->SetKeyState(modifier, state);
273}
274
275StickyKeyState StickyKeysOverlay::GetModifierKeyState(
276    ui::EventFlags modifier) {
277  return overlay_view_->GetKeyState(modifier);
278}
279
280gfx::Rect StickyKeysOverlay::CalculateOverlayBounds() {
281  int x = is_visible_ ? kHorizontalOverlayOffset : -widget_size_.width();
282  return gfx::Rect(gfx::Point(x, kVerticalOverlayOffset), widget_size_);
283}
284
285void StickyKeysOverlay::OnLayerAnimationEnded(
286    ui::LayerAnimationSequence* sequence) {
287  ui::LayerAnimator* animator = overlay_widget_->GetLayer()->GetAnimator();
288  if (animator)
289    animator->RemoveObserver(this);
290  if (!is_visible_)
291    overlay_widget_->Hide();
292}
293
294void StickyKeysOverlay::OnLayerAnimationAborted(
295    ui::LayerAnimationSequence* sequence) {
296  ui::LayerAnimator* animator = overlay_widget_->GetLayer()->GetAnimator();
297  if (animator)
298    animator->RemoveObserver(this);
299}
300
301void StickyKeysOverlay::OnLayerAnimationScheduled(
302    ui::LayerAnimationSequence* sequence) {
303}
304
305}  // namespace ash
306