highlighter_view.cc 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279
  1. // Copyright 2017 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. #include "ash/highlighter/highlighter_view.h"
  5. #include <memory>
  6. #include "ash/highlighter/highlighter_gesture_util.h"
  7. #include "base/bind.h"
  8. #include "base/memory/ptr_util.h"
  9. #include "base/threading/thread_task_runner_handle.h"
  10. #include "base/timer/timer.h"
  11. #include "base/trace_event/trace_event.h"
  12. #include "third_party/skia/include/core/SkColor.h"
  13. #include "third_party/skia/include/core/SkTypes.h"
  14. #include "ui/aura/window.h"
  15. #include "ui/compositor/layer.h"
  16. #include "ui/compositor/scoped_layer_animation_settings.h"
  17. #include "ui/events/base_event_utils.h"
  18. #include "ui/gfx/canvas.h"
  19. #include "ui/views/widget/widget.h"
  20. namespace ash {
  21. namespace {
  22. // Variables for rendering the highlighter. Sizes in DIP.
  23. constexpr float kPenTipWidth = 4;
  24. constexpr float kPenTipHeight = 14;
  25. constexpr int kOutsetForAntialiasing = 1;
  26. constexpr float kStrokeScale = 1.2;
  27. constexpr int kStrokeFadeoutDelayMs = 100;
  28. constexpr int kStrokeFadeoutDurationMs = 500;
  29. constexpr int kStrokeScaleDurationMs = 300;
  30. gfx::Rect InflateDamageRect(const gfx::Rect& r) {
  31. gfx::Rect inflated = r;
  32. inflated.Inset(gfx::Insets::VH(
  33. -kOutsetForAntialiasing - static_cast<int>(kPenTipHeight / 2 + 1),
  34. -kOutsetForAntialiasing - static_cast<int>(kPenTipWidth / 2 + 1)));
  35. return inflated;
  36. }
  37. // A highlighter segment is a parallelogram with two vertical sides.
  38. // |p1| and |p2| are the center points of the vertical sides.
  39. // |height| is the height of a vertical side.
  40. void DrawSegment(gfx::Canvas& canvas,
  41. const gfx::PointF& p1,
  42. const gfx::PointF& p2,
  43. int height,
  44. const cc::PaintFlags& flags,
  45. float stroke_width) {
  46. const float y_offset = height / 2;
  47. SkPath frame;
  48. frame.moveTo(p1.x(), p1.y() - y_offset);
  49. frame.lineTo(p2.x(), p2.y() - y_offset);
  50. frame.lineTo(p2.x(), p2.y() + y_offset);
  51. frame.lineTo(p1.x(), p1.y() + y_offset);
  52. frame.close();
  53. SkPaint paint;
  54. paint.setStroke(true);
  55. paint.setStrokeWidth(stroke_width);
  56. paint.setStrokeJoin(SkPaint::kRound_Join);
  57. paint.setStrokeCap(SkPaint::kRound_Cap);
  58. SkPath fill;
  59. paint.getFillPath(frame, &fill);
  60. fill.addPath(frame);
  61. canvas.DrawPath(fill, flags);
  62. }
  63. } // namespace
  64. const gfx::SizeF HighlighterView::kPenTipSize(kPenTipWidth, kPenTipHeight);
  65. HighlighterView::HighlighterView(base::TimeDelta presentation_delay)
  66. : points_(base::TimeDelta()),
  67. predicted_points_(base::TimeDelta()),
  68. presentation_delay_(presentation_delay) {}
  69. HighlighterView::~HighlighterView() = default;
  70. // static
  71. views::UniqueWidgetPtr HighlighterView::Create(
  72. const base::TimeDelta presentation_delay,
  73. aura::Window* container) {
  74. return fast_ink::FastInkView::CreateWidgetWithContents(
  75. base::WrapUnique(new HighlighterView(presentation_delay)), container);
  76. }
  77. void HighlighterView::ChangeColor(SkColor color) {
  78. pen_color_ = color;
  79. if (!points_.IsEmpty())
  80. AddGap();
  81. }
  82. void HighlighterView::AddNewPoint(const gfx::PointF& point,
  83. const base::TimeTicks& time) {
  84. TRACE_EVENT1("ui", "HighlighterView::AddNewPoint", "point", point.ToString());
  85. TRACE_COUNTER1(
  86. "ui", "HighlighterPredictionError",
  87. predicted_points_.GetNumberOfPoints()
  88. ? std::round(
  89. (point - predicted_points_.GetOldest().location).Length())
  90. : 0);
  91. // The new segment needs to be drawn.
  92. if (!points_.IsEmpty()) {
  93. highlighter_damage_rect_.Union(InflateDamageRect(gfx::ToEnclosingRect(
  94. gfx::BoundingRect(points_.GetNewest().location, point))));
  95. }
  96. // Previous prediction needs to be erased.
  97. if (!predicted_points_.IsEmpty()) {
  98. highlighter_damage_rect_.Union(
  99. InflateDamageRect(predicted_points_.GetBoundingBox()));
  100. }
  101. points_.AddPoint(point, time, pen_color_);
  102. base::TimeTicks current_time = ui::EventTimeForNow();
  103. gfx::Rect screen_bounds = GetWidget()->GetNativeView()->GetBoundsInScreen();
  104. predicted_points_.Predict(points_, current_time, presentation_delay_,
  105. screen_bounds.size());
  106. // New prediction needs to be drawn.
  107. if (!predicted_points_.IsEmpty()) {
  108. highlighter_damage_rect_.Union(
  109. InflateDamageRect(predicted_points_.GetBoundingBox()));
  110. }
  111. ScheduleUpdateBuffer();
  112. }
  113. void HighlighterView::AddGap() {
  114. points_.AddGap();
  115. }
  116. void HighlighterView::Animate(const gfx::PointF& pivot,
  117. HighlighterGestureType gesture_type,
  118. base::OnceClosure done) {
  119. animation_timer_ = std::make_unique<base::OneShotTimer>();
  120. animation_timer_->Start(
  121. FROM_HERE, base::Milliseconds(kStrokeFadeoutDelayMs),
  122. base::BindOnce(&HighlighterView::FadeOut, base::Unretained(this), pivot,
  123. gesture_type, std::move(done)));
  124. }
  125. void HighlighterView::UndoLastStroke() {
  126. if (points_.IsEmpty())
  127. return;
  128. // Previous prediction needs to be erased.
  129. if (!predicted_points_.IsEmpty()) {
  130. highlighter_damage_rect_.Union(
  131. InflateDamageRect(predicted_points_.GetBoundingBox()));
  132. predicted_points_.Clear();
  133. }
  134. const gfx::Rect bounding_box = points_.UndoLastStroke();
  135. // Ensure deleting the points also erases them by expanding the damage
  136. // rectangle.
  137. highlighter_damage_rect_.Union(InflateDamageRect(bounding_box));
  138. ScheduleUpdateBuffer();
  139. }
  140. void HighlighterView::FadeOut(const gfx::PointF& pivot,
  141. HighlighterGestureType gesture_type,
  142. base::OnceClosure done) {
  143. ui::Layer* layer = GetWidget()->GetLayer();
  144. base::TimeDelta duration = base::Milliseconds(kStrokeFadeoutDurationMs);
  145. {
  146. ui::ScopedLayerAnimationSettings settings(layer->GetAnimator());
  147. settings.SetTransitionDuration(duration);
  148. settings.SetTweenType(gfx::Tween::LINEAR_OUT_SLOW_IN);
  149. layer->SetOpacity(0);
  150. }
  151. if (gesture_type != HighlighterGestureType::kHorizontalStroke) {
  152. ui::ScopedLayerAnimationSettings settings(layer->GetAnimator());
  153. settings.SetTransitionDuration(base::Milliseconds(kStrokeScaleDurationMs));
  154. settings.SetTweenType(gfx::Tween::LINEAR_OUT_SLOW_IN);
  155. const float scale = gesture_type == HighlighterGestureType::kClosedShape
  156. ? kStrokeScale
  157. : (1 / kStrokeScale);
  158. gfx::Transform transform;
  159. transform.Translate(pivot.x() * (1 - scale), pivot.y() * (1 - scale));
  160. transform.Scale(scale, scale);
  161. layer->SetTransform(transform);
  162. }
  163. animation_timer_ = std::make_unique<base::OneShotTimer>();
  164. animation_timer_->Start(FROM_HERE, duration, std::move(done));
  165. }
  166. void HighlighterView::ScheduleUpdateBuffer() {
  167. if (pending_update_buffer_)
  168. return;
  169. pending_update_buffer_ = true;
  170. base::ThreadTaskRunnerHandle::Get()->PostTask(
  171. FROM_HERE, base::BindOnce(&HighlighterView::UpdateBuffer,
  172. weak_ptr_factory_.GetWeakPtr()));
  173. }
  174. void HighlighterView::UpdateBuffer() {
  175. TRACE_EVENT1("ui", "FastInkView::UpdateBuffer", "damage",
  176. highlighter_damage_rect_.ToString());
  177. DCHECK(pending_update_buffer_);
  178. pending_update_buffer_ = false;
  179. {
  180. ScopedPaint paint(this, highlighter_damage_rect_);
  181. Draw(paint.canvas());
  182. }
  183. gfx::Rect screen_bounds = GetWidget()->GetNativeView()->GetBoundsInScreen();
  184. UpdateSurface(screen_bounds, highlighter_damage_rect_, /*auto_refresh=*/true);
  185. highlighter_damage_rect_ = gfx::Rect();
  186. }
  187. void HighlighterView::Draw(gfx::Canvas& canvas) {
  188. const int num_points =
  189. points_.GetNumberOfPoints() + predicted_points_.GetNumberOfPoints();
  190. if (num_points < 2)
  191. return;
  192. gfx::Rect clip_rect;
  193. if (!canvas.GetClipBounds(&clip_rect))
  194. return;
  195. cc::PaintFlags flags;
  196. flags.setStyle(cc::PaintFlags::kFill_Style);
  197. flags.setAntiAlias(true);
  198. flags.setBlendMode(SkBlendMode::kSrc);
  199. // Decrease the segment height by the outline stroke width,
  200. // so that the vertical cross-section of the drawn segment
  201. // is exactly kPenTipHeight.
  202. const int height = kPenTipHeight - kPenTipWidth;
  203. fast_ink::FastInkPoints::FastInkPoint previous_point;
  204. for (int i = 0; i < num_points; ++i) {
  205. fast_ink::FastInkPoints::FastInkPoint current_point;
  206. if (i < points_.GetNumberOfPoints()) {
  207. current_point = points_.points()[i];
  208. } else {
  209. current_point =
  210. predicted_points_.points()[i - points_.GetNumberOfPoints()];
  211. }
  212. if (i != 0 && !previous_point.gap_after) {
  213. gfx::Rect damage_rect = InflateDamageRect(gfx::ToEnclosingRect(
  214. gfx::BoundingRect(previous_point.location, current_point.location)));
  215. // Only draw the segment if it is touching the clip rect.
  216. if (clip_rect.Intersects(damage_rect)) {
  217. flags.setColor(current_point.color);
  218. DrawSegment(canvas, previous_point.location, current_point.location,
  219. height, flags, kPenTipWidth);
  220. }
  221. }
  222. previous_point = current_point;
  223. }
  224. }
  225. } // namespace ash