| // Copyright 2018 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/assistant/ui/logo_view/logo_view_impl.h" |
| |
| #include <algorithm> |
| |
| #include "ash/assistant/ui/logo_view/shape/shape.h" |
| #include "base/logging.h" |
| #include "chromeos/assistant/internal/logo_view/logo_model/dot.h" |
| #include "chromeos/assistant/internal/logo_view/logo_view_constants.h" |
| #include "ui/compositor/compositor.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(); |
| } |
| |
| int32_t GetLogoAlpha(const LogoViewImpl::Logo& logo) { |
| return logo.GetAlpha() * 255; |
| } |
| |
| } // 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(); |
| } |
| |
| const char* LogoViewImpl::GetClassName() const { |
| return "LogoViewImpl"; |
| } |
| |
| 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::kMoleculeWavy: |
| animator_state = StateModel::State::kMoleculeWavy; |
| 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() { |
| 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 (auto iter = logo_.dots().rbegin(); iter != logo_.dots().rend(); ++iter) |
| DrawDot(canvas, (*iter).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.setAlpha(GetLogoAlpha(logo_)); |
| 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.setAlpha(GetLogoAlpha(logo_)); |
| 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.setAlpha(GetLogoAlpha(logo_)); |
| 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 (is_visible) |
| state_animator_.StartAnimator(); |
| else |
| state_animator_.StopAnimator(); |
| } |
| |
| } // namespace ash |