1
    2
    3
    4
    5
    6
    7
    8
    9
   10
   11
   12
   13
   14
   15
   16
   17
   18
   19
   20
   21
   22
   23
   24
   25
   26
   27
   28
   29
   30
   31
   32
   33
   34
   35
   36
   37
   38
   39
   40
   41
   42
   43
   44
   45
   46
   47
   48
   49
   50
   51
   52
   53
   54
   55
   56
   57
   58
   59
   60
   61
   62
   63
   64
   65
   66
   67
   68
   69
   70
   71
   72
   73
   74
   75
   76
   77
   78
   79
   80
   81
   82
   83
   84
   85
   86
   87
   88
   89
   90
   91
   92
   93
   94
   95
   96
   97
   98
   99
  100
  101
  102
  103
  104
  105
  106
  107
  108
  109
  110
  111
  112
  113
  114
  115
  116
  117
  118
  119
  120
  121
  122
  123
  124
  125
  126
  127
  128
  129
  130
  131
  132
  133
  134
  135
  136
  137
  138
  139
  140
  141
  142
  143
  144
  145
  146
  147
  148
  149
  150
  151
  152
  153
  154
  155
  156
  157
  158
  159
  160
  161
  162
  163
  164
  165
  166
  167
  168
  169
  170
  171
  172
  173
  174
  175
  176
  177
  178
  179
  180
  181
  182
  183
  184
  185
  186
  187
  188
  189
  190
  191
  192
  193
  194
  195
  196
  197
  198
  199
  200
  201
  202
  203
  204
  205
  206
  207
  208
  209
  210
  211
  212
  213
  214
  215
  216
  217
  218
  219
  220
  221
  222
  223
  224
  225
  226

ash / assistant / ui / logo_view / logo_view_impl.cc [blame]

// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ash/assistant/ui/logo_view/logo_view_impl.h"

#include <algorithm>

#include "ash/assistant/ui/logo_view/shape/shape.h"
#include "base/check.h"
#include "base/containers/adapters.h"
#include "base/notreached.h"
#include "chromeos/assistant/internal/logo_view/logo_model/dot.h"
#include "chromeos/assistant/internal/logo_view/logo_view_constants.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/compositor/compositor.h"
#include "ui/compositor/layer.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/geometry/insets.h"
#include "ui/gfx/geometry/rect_f.h"
#include "ui/gfx/geometry/vector2d.h"

namespace ash {

namespace {

int64_t TimeTicksToMs(const base::TimeTicks& timestamp) {
  return (timestamp - base::TimeTicks()).InMilliseconds();
}

}  // namespace

LogoViewImpl::LogoViewImpl()
    : mic_part_shape_(chromeos::assistant::kDotDefaultSize),
      state_animator_(&logo_, &state_model_, StateModel::State::kUndefined) {
  SetPaintToLayer();
  layer()->SetFillsBoundsOpaquely(false);

  state_animator_.SetStateAnimatorTimerDelegate(this);
  state_animator_.SetLogoInputValueProvider(StateModel::State::kUserSpeaks,
                                            &sound_level_input_value_provider_);
}

LogoViewImpl::~LogoViewImpl() {
  state_animator_.StopAnimator();
}

void LogoViewImpl::SetState(LogoView::State state, bool animate) {
  StateModel::State animator_state;
  switch (state) {
    case LogoView::State::kUndefined:
      animator_state = StateModel::State::kUndefined;
      break;
    case LogoView::State::kListening:
      animator_state = StateModel::State::kListening;
      break;
    case LogoView::State::kMic:
      animator_state = StateModel::State::kMic;
      break;
    case LogoView::State::kUserSpeaks:
      animator_state = StateModel::State::kUserSpeaks;
      break;
  }
  state_animator_.SwitchStateTo(animator_state, !animate);
}

void LogoViewImpl::SetSpeechLevel(float speech_level) {
  sound_level_input_value_provider_.SetSpeechLevel(speech_level);
}

int64_t LogoViewImpl::StartTimer() {
  // Remove animation observer from previous |StartTimer| if exists.
  StopTimer();

  ui::Compositor* compositor = layer()->GetCompositor();
  if (compositor && !compositor->HasAnimationObserver(this)) {
    animating_compositor_ = compositor;
    animating_compositor_->AddAnimationObserver(this);
  }
  return TimeTicksToMs(base::TimeTicks::Now());
}

void LogoViewImpl::StopTimer() {
  if (animating_compositor_ &&
      animating_compositor_->HasAnimationObserver(this)) {
    animating_compositor_->RemoveAnimationObserver(this);
  }
  animating_compositor_ = nullptr;
}

void LogoViewImpl::OnAnimationStep(base::TimeTicks timestamp) {
  const int64_t current_time_ms = TimeTicksToMs(timestamp);
  state_animator_.OnTimeUpdate(current_time_ms);
  SchedulePaint();
}

void LogoViewImpl::OnCompositingShuttingDown(ui::Compositor* compositor) {
  DCHECK(compositor);
  if (animating_compositor_ == compositor) {
    StopTimer();
  }
}

void LogoViewImpl::DrawDots(gfx::Canvas* canvas) {
  // TODO: The Green Mic parts seems overlapped on the Red Mic part. Draw dots
  // in reverse order so that the Red Mic part is on top of Green Mic parts. But
  // we need to find out why the Mic parts are overlapping in the first place.
  for (const auto& dot : base::Reversed(logo_.dots())) {
    DrawDot(canvas, dot.get());
  }
}

void LogoViewImpl::DrawDot(gfx::Canvas* canvas, Dot* dot) {
  const float radius = dot->GetRadius();
  const float angle = logo_.GetRotation() + dot->GetAngle();
  const float x = radius * std::cos(angle) + dot->GetOffsetX();
  const float y = radius * std::sin(angle) + dot->GetOffsetY();

  if (dot->IsMic()) {
    DrawMicPart(canvas, dot, x, y);
  } else if (dot->IsLetter()) {
    // TODO(b/79579731): Implement the letter animation.
    NOTIMPLEMENTED();
  } else if (dot->IsLine()) {
    DrawLine(canvas, dot, x, y);
  } else {
    DrawCircle(canvas, dot, x, y);
  }
}

void LogoViewImpl::DrawMicPart(gfx::Canvas* canvas,
                               Dot* dot,
                               float x,
                               float y) {
  const float progress = dot->GetMicMorph();
  mic_part_shape_.Reset();
  mic_part_shape_.ToMicPart(progress, dot->dot_color());
  mic_part_shape_.Transform(x, y, dots_scale_);
  DrawShape(canvas, &mic_part_shape_, dot->color());
}

void LogoViewImpl::DrawShape(gfx::Canvas* canvas, Shape* shape, SkColor color) {
  cc::PaintFlags paint_flags;
  paint_flags.setAntiAlias(true);
  paint_flags.setColor(color);
  paint_flags.setAlphaf(logo_.GetAlpha());
  paint_flags.setStyle(cc::PaintFlags::kStroke_Style);
  paint_flags.setStrokeCap(shape->cap());

  paint_flags.setStrokeWidth(shape->first_stroke_width());
  canvas->DrawPath(*shape->first_path(), paint_flags);

  paint_flags.setStrokeWidth(shape->second_stroke_width());
  canvas->DrawPath(*shape->second_path(), paint_flags);
}

void LogoViewImpl::DrawLine(gfx::Canvas* canvas, Dot* dot, float x, float y) {
  const float stroke_width = dot->GetSize() * dots_scale_;
  cc::PaintFlags paint_flags;
  paint_flags.setAntiAlias(true);
  paint_flags.setColor(dot->color());
  paint_flags.setAlphaf(logo_.GetAlpha());
  paint_flags.setStrokeWidth(stroke_width);
  paint_flags.setStyle(cc::PaintFlags::kStroke_Style);
  paint_flags.setStrokeCap(cc::PaintFlags::kRound_Cap);

  const float line_length = dot->GetLineLength();
  const float line_x = x * dots_scale_;
  const float line_top_y = (y - line_length) * dots_scale_;
  const float line_bottom_y = (y + line_length) * dots_scale_;
  canvas->DrawLine(gfx::PointF(line_x, line_top_y),
                   gfx::PointF(line_x, line_bottom_y), paint_flags);
}

void LogoViewImpl::DrawCircle(gfx::Canvas* canvas, Dot* dot, float x, float y) {
  const float radius = dot->GetSize() * dot->GetVisibility() / 2.0f;
  cc::PaintFlags paint_flags;
  paint_flags.setAntiAlias(true);
  paint_flags.setColor(dot->color());
  paint_flags.setAlphaf(logo_.GetAlpha());
  paint_flags.setStyle(cc::PaintFlags::kFill_Style);
  canvas->DrawCircle(gfx::PointF(x * dots_scale_, y * dots_scale_),
                     radius * dots_scale_, paint_flags);
}

void LogoViewImpl::OnPaint(gfx::Canvas* canvas) {
  View::OnPaint(canvas);

  canvas->Save();
  gfx::RectF content_bounds(GetContentsBounds());
  gfx::InsetsF insets(GetInsets());
  const float offset_x = insets.left() + content_bounds.width() / 2.0f;
  const float offset_y = insets.top() + content_bounds.height() / 2.0f;
  canvas->Translate(gfx::Vector2d(offset_x, offset_y));
  DrawDots(canvas);
  canvas->Restore();
}

void LogoViewImpl::OnBoundsChanged(const gfx::Rect& previous_bounds) {
  gfx::Rect content_bounds(GetContentsBounds());
  if (content_bounds.IsEmpty()) {
    return;
  }

  // Sets a scale such that an object of the specified width and height will
  // fill the view while keeping the aspect ratio if drawn at that scale.
  constexpr float kDefaultWidth = 28.0f;
  constexpr float kDefaultHeight = 25.0f;
  const float x_scale = content_bounds.width() / kDefaultWidth;
  const float y_scale = content_bounds.height() / kDefaultHeight;
  dots_scale_ = std::fmin(x_scale, y_scale);
}

void LogoViewImpl::VisibilityChanged(views::View* starting_from,
                                     bool is_visible) {
  if (IsDrawn()) {
    state_animator_.StartAnimator();
  } else {
    state_animator_.StopAnimator();
  }
}

BEGIN_METADATA(LogoViewImpl)
END_METADATA

}  // namespace ash