text: // Copyright 2014 The Chromium Authors. All rights reserved.
  // Use of this source code is governed by a BSD-style license that can be
  // found in the LICENSE file.
  
  #include "ash/accessibility/sticky_keys/sticky_keys_overlay.h"
  
  #include <memory>
  
  #include "ash/accessibility/sticky_keys/sticky_keys_controller.h"
  #include "ash/public/cpp/shell_window_ids.h"
  #include "ash/shell.h"
  #include "ash/strings/grit/ash_strings.h"
  #include "base/strings/string_util.h"
  #include "base/strings/utf_string_conversions.h"
  #include "ui/base/l10n/l10n_util.h"
  #include "ui/base/resource/resource_bundle.h"
  #include "ui/compositor/layer.h"
  #include "ui/compositor/scoped_layer_animation_settings.h"
  #include "ui/gfx/canvas.h"
  #include "ui/gfx/font_list.h"
  #include "ui/gfx/geometry/insets.h"
  #include "ui/views/border.h"
  #include "ui/views/controls/label.h"
  #include "ui/views/layout/box_layout.h"
  #include "ui/views/view.h"
  #include "ui/views/widget/widget.h"
  #include "ui/views/widget/widget_delegate.h"
  
  namespace ash {
  
  namespace {
  
  // Horizontal offset of the overlay from the top left of the screen.
  const int kHorizontalOverlayOffset = 18;
  
  // Vertical offset of the overlay from the top left of the screen.
  const int kVerticalOverlayOffset = 18;
  
  // Font style used for modifier key labels.
  const ui::ResourceBundle::FontStyle kKeyLabelFontStyle =
      ui::ResourceBundle::LargeFont;
  
  // Duration of slide animation when overlay is shown or hidden.
  const int kSlideAnimationDurationMs = 100;
  
  ///////////////////////////////////////////////////////////////////////////////
  //  StickyKeyOverlayLabel
  class StickyKeyOverlayLabel : public views::Label {
   public:
    explicit StickyKeyOverlayLabel(const std::string& key_name);
  
    ~StickyKeyOverlayLabel() override;
  
    StickyKeyState state() const { return state_; }
  
    void SetKeyState(StickyKeyState state);
  
   private:
    StickyKeyState state_;
  
    DISALLOW_COPY_AND_ASSIGN(StickyKeyOverlayLabel);
  };
  
  StickyKeyOverlayLabel::StickyKeyOverlayLabel(const std::string& key_name)
      : state_(STICKY_KEY_STATE_DISABLED) {
    ui::ResourceBundle* rb = &ui::ResourceBundle::GetSharedInstance();
  
    SetText(base::UTF8ToUTF16(key_name));
    SetHorizontalAlignment(gfx::ALIGN_LEFT);
    SetFontList(rb->GetFontList(kKeyLabelFontStyle));
    SetAutoColorReadabilityEnabled(false);
    SetEnabledColor(SkColorSetARGB(0x80, 0xFF, 0xFF, 0xFF));
    SetSubpixelRenderingEnabled(false);
  }
  
  StickyKeyOverlayLabel::~StickyKeyOverlayLabel() = default;
  
  void StickyKeyOverlayLabel::SetKeyState(StickyKeyState state) {
    state_ = state;
    SkColor label_color;
    int style;
    switch (state) {
      case STICKY_KEY_STATE_ENABLED:
        style = gfx::Font::NORMAL;
        label_color = SkColorSetA(GetEnabledColor(), 0xFF);
        break;
      case STICKY_KEY_STATE_LOCKED:
        style = gfx::Font::UNDERLINE;
        label_color = SkColorSetA(GetEnabledColor(), 0xFF);
        break;
      default:
        style = gfx::Font::NORMAL;
        label_color = SkColorSetA(GetEnabledColor(), 0x80);
    }
  
    SetEnabledColor(label_color);
    SetFontList(font_list().DeriveWithStyle(style));
  }
  
  }  // namespace
  
  ///////////////////////////////////////////////////////////////////////////////
  //  StickyKeysOverlayView
  class StickyKeysOverlayView : public views::View {
   public:
    StickyKeysOverlayView();
    ~StickyKeysOverlayView() override;
  
    // views::View overrides:
    void OnPaint(gfx::Canvas* canvas) override;
  
    void SetKeyState(ui::EventFlags modifier, StickyKeyState state);
  
    StickyKeyState GetKeyState(ui::EventFlags modifier);
  
    void SetModifierVisible(ui::EventFlags modifier, bool visible);
    bool GetModifierVisible(ui::EventFlags modifier);
  
   private:
    void AddKeyLabel(ui::EventFlags modifier, const std::string& key_label);
  
    typedef std::map<ui::EventFlags, StickyKeyOverlayLabel*> ModifierLabelMap;
    ModifierLabelMap modifier_label_map_;
  
    DISALLOW_COPY_AND_ASSIGN(StickyKeysOverlayView);
  };
  
  StickyKeysOverlayView::StickyKeysOverlayView() {
    const gfx::Font& font =
        ui::ResourceBundle::GetSharedInstance().GetFont(kKeyLabelFontStyle);
    int font_size = font.GetFontSize();
    int font_padding = font.GetHeight() - font.GetBaseline();
  
    // Text should have a margin of 0.5 times the font size on each side, so
    // the spacing between two labels will be the same as the font size.
    int horizontal_spacing = font_size / 2;
    int vertical_spacing = font_size / 2 - font_padding;
    int child_spacing = font_size - 2 * font_padding;
  
    SetLayoutManager(std::make_unique<views::BoxLayout>(
        views::BoxLayout::Orientation::kVertical,
        gfx::Insets(vertical_spacing, horizontal_spacing), child_spacing));
    AddKeyLabel(ui::EF_CONTROL_DOWN,
                l10n_util::GetStringUTF8(IDS_ASH_CONTROL_KEY));
    AddKeyLabel(ui::EF_ALT_DOWN, l10n_util::GetStringUTF8(IDS_ASH_ALT_KEY));
    AddKeyLabel(ui::EF_SHIFT_DOWN, l10n_util::GetStringUTF8(IDS_ASH_SHIFT_KEY));
    AddKeyLabel(ui::EF_COMMAND_DOWN,
                l10n_util::GetStringUTF8(IDS_ASH_SEARCH_KEY));
    AddKeyLabel(ui::EF_ALTGR_DOWN, l10n_util::GetStringUTF8(IDS_ASH_ALTGR_KEY));
    AddKeyLabel(ui::EF_MOD3_DOWN, l10n_util::GetStringUTF8(IDS_ASH_MOD3_KEY));
  }
  
  StickyKeysOverlayView::~StickyKeysOverlayView() = default;
  
  void StickyKeysOverlayView::OnPaint(gfx::Canvas* canvas) {
    cc::PaintFlags flags;
    flags.setStyle(cc::PaintFlags::kFill_Style);
    flags.setColor(SkColorSetARGB(0xB3, 0x55, 0x55, 0x55));
    canvas->DrawRoundRect(GetLocalBounds(), 2, flags);
    views::View::OnPaint(canvas);
  }
  
  void StickyKeysOverlayView::SetKeyState(ui::EventFlags modifier,
                                          StickyKeyState state) {
    ModifierLabelMap::iterator it = modifier_label_map_.find(modifier);
    DCHECK(it != modifier_label_map_.end());
    if (it != modifier_label_map_.end()) {
      StickyKeyOverlayLabel* label = it->second;
      label->SetKeyState(state);
    }
  }
  
  StickyKeyState StickyKeysOverlayView::GetKeyState(ui::EventFlags modifier) {
    ModifierLabelMap::iterator it = modifier_label_map_.find(modifier);
    DCHECK(it != modifier_label_map_.end());
    return it->second->state();
  }
  
  void StickyKeysOverlayView::SetModifierVisible(ui::EventFlags modifier,
                                                 bool visible) {
    ModifierLabelMap::iterator it = modifier_label_map_.find(modifier);
    DCHECK(it != modifier_label_map_.end());
    it->second->SetVisible(visible);
  }
  
  bool StickyKeysOverlayView::GetModifierVisible(ui::EventFlags modifier) {
    ModifierLabelMap::iterator it = modifier_label_map_.find(modifier);
    DCHECK(it != modifier_label_map_.end());
    return it->second->GetVisible();
  }
  
  void StickyKeysOverlayView::AddKeyLabel(ui::EventFlags modifier,
                                          const std::string& key_label) {
    StickyKeyOverlayLabel* label = new StickyKeyOverlayLabel(key_label);
    AddChildView(label);
    modifier_label_map_[modifier] = label;
  }
  
  ///////////////////////////////////////////////////////////////////////////////
  //  StickyKeysOverlay
  StickyKeysOverlay::StickyKeysOverlay() {
    auto overlay_view = std::make_unique<StickyKeysOverlayView>();
    widget_size_ = overlay_view->GetPreferredSize();
  
    views::Widget::InitParams params;
    params.type = views::Widget::InitParams::TYPE_POPUP;
    params.opacity = views::Widget::InitParams::WindowOpacity::kTranslucent;
    params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
    params.accept_events = false;
    params.z_order = ui::ZOrderLevel::kFloatingUIElement;
    params.bounds = CalculateOverlayBounds();
    params.parent = Shell::GetContainer(Shell::GetRootWindowForNewWindows(),
                                        kShellWindowId_OverlayContainer);
    overlay_widget_ = std::make_unique<views::Widget>();
    overlay_widget_->Init(std::move(params));
    overlay_widget_->SetVisibilityChangedAnimationsEnabled(false);
    overlay_view_ = overlay_widget_->SetContentsView(std::move(overlay_view));
    overlay_widget_->GetNativeView()->SetName("StickyKeysOverlay");
  }
  
  StickyKeysOverlay::~StickyKeysOverlay() {
    // Remove ourself from the animator to avoid being re-entrantly called in
    // |overlay_widget_|'s destructor.
    StopObservingImplicitAnimations();
  }
  
  void StickyKeysOverlay::Show(bool visible) {
    if (is_visible_ == visible)
      return;
  
    is_visible_ = visible;
    if (is_visible_)
      overlay_widget_->Show();
    overlay_widget_->SetBounds(CalculateOverlayBounds());
  
    ui::LayerAnimator* animator = overlay_widget_->GetLayer()->GetAnimator();
  
    // Ensure transform is correct before beginning animation.
    if (!animator->is_animating()) {
      int sign = is_visible_ ? -1 : 1;
      gfx::Transform transform;
      transform.Translate(
          sign * (widget_size_.width() + kHorizontalOverlayOffset), 0);
      overlay_widget_->GetLayer()->SetTransform(transform);
    }
  
    ui::ScopedLayerAnimationSettings settings(animator);
    settings.AddObserver(this);
    settings.SetPreemptionStrategy(
        ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
    settings.SetTweenType(visible ? gfx::Tween::EASE_OUT : gfx::Tween::EASE_IN);
    settings.SetTransitionDuration(
        base::TimeDelta::FromMilliseconds(kSlideAnimationDurationMs));
  
    overlay_widget_->GetLayer()->SetTransform(gfx::Transform());
  }
  
  void StickyKeysOverlay::SetModifierVisible(ui::EventFlags modifier,
                                             bool visible) {
    overlay_view_->SetModifierVisible(modifier, visible);
    widget_size_ = overlay_view_->GetPreferredSize();
  }
  
  bool StickyKeysOverlay::GetModifierVisible(ui::EventFlags modifier) {
    return overlay_view_->GetModifierVisible(modifier);
  }
  
  void StickyKeysOverlay::SetModifierKeyState(ui::EventFlags modifier,
                                              StickyKeyState state) {
    overlay_view_->SetKeyState(modifier, state);
  }
  
  StickyKeyState StickyKeysOverlay::GetModifierKeyState(ui::EventFlags modifier) {
    return overlay_view_->GetKeyState(modifier);
  }
  
  views::Widget* StickyKeysOverlay::GetWidgetForTesting() {
    return overlay_widget_.get();
  }
  
  gfx::Rect StickyKeysOverlay::CalculateOverlayBounds() {
    int x = is_visible_ ? kHorizontalOverlayOffset : -widget_size_.width();
    return gfx::Rect(gfx::Point(x, kVerticalOverlayOffset), widget_size_);
  }
  
  void StickyKeysOverlay::OnImplicitAnimationsCompleted() {
    if (WasAnimationAbortedForProperty(ui::LayerAnimationElement::TRANSFORM))
      return;
    DCHECK(
        WasAnimationCompletedForProperty(ui::LayerAnimationElement::TRANSFORM));
    if (!is_visible_)
      overlay_widget_->Hide();
  }
  
  }  // namespace ash
  