#include "ui/touch_selection/touch_selection_magnifier_aura.h"
#include "cc/paint/paint_flags.h"
#include "third_party/skia/include/core/SkColor.h"
#include "ui/color/color_id.h"
#include "ui/color/color_provider.h"
#include "ui/color/color_provider_manager.h"
#include "ui/compositor/layer.h"
#include "ui/compositor/layer_delegate.h"
#include "ui/compositor/paint_recorder.h"
#include "ui/compositor/scoped_layer_animation_settings.h"
#include "ui/gfx/color_palette.h"
#include "ui/gfx/geometry/point.h"
#include "ui/gfx/geometry/point_conversions.h"
#include "ui/gfx/geometry/point_f.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/rect_conversions.h"
#include "ui/gfx/geometry/rect_f.h"
#include "ui/gfx/geometry/size.h"
#include "ui/gfx/geometry/size_f.h"
#include "ui/gfx/geometry/vector2d.h"
#include "ui/gfx/shadow_value.h"
#include "ui/gfx/skia_paint_util.h"
#include "ui/native_theme/native_theme.h"
#include "ui/native_theme/native_theme_observer.h"
namespace ui {
namespace {
constexpr float kMagnifierScale = 1.25f;
constexpr int kMagnifierRadius = 20;
constexpr base::TimeDelta kMagnifierTransitionDuration = base::Milliseconds(50);
constexpr gfx::Size kMagnifierSize{100, 40};
constexpr int kMagnifierVerticalBoundsOffset = -8;
constexpr int kMagnifierBorderThickness = 1;
constexpr float kMagnifierBorderOpacity = 0.2f;
gfx::ShadowValues GetMagnifierShadowValues(SkColor key_shadow_color,
SkColor ambient_shadow_color) {
constexpr int kShadowElevation = 3;
constexpr int kShadowBlur = 2 * kShadowElevation;
return {gfx::ShadowValue(gfx::Vector2d(0, kShadowElevation), kShadowBlur,
key_shadow_color),
gfx::ShadowValue(gfx::Vector2d(), kShadowBlur, ambient_shadow_color)};
}
gfx::Outsets GetMagnifierShadowOutsets() {
return gfx::ShadowValue::GetMargin(
GetMagnifierShadowValues(gfx::kPlaceholderColor,
gfx::kPlaceholderColor))
.ToOutsets();
}
gfx::Rect GetZoomLayerBounds() {
const gfx::Outsets magnifier_shadow_outsets = GetMagnifierShadowOutsets();
return gfx::Rect(magnifier_shadow_outsets.left(),
magnifier_shadow_outsets.top(), kMagnifierSize.width(),
kMagnifierSize.height());
}
gfx::Size GetBorderLayerSize() {
return kMagnifierSize + GetMagnifierShadowOutsets().size();
}
gfx::Rect GetMagnifierLayerBounds(const gfx::Size& parent_container_size,
const gfx::Point& anchor_point) {
const gfx::Point origin(anchor_point.x() - kMagnifierSize.width() / 2,
anchor_point.y() - kMagnifierSize.height() +
kMagnifierVerticalBoundsOffset);
gfx::Rect magnifier_layer_bounds(origin, kMagnifierSize);
magnifier_layer_bounds.Outset(GetMagnifierShadowOutsets());
magnifier_layer_bounds.AdjustToFit(gfx::Rect(parent_container_size));
return magnifier_layer_bounds;
}
gfx::Point GetMagnifierSourceCenter(const gfx::Size& parent_container_size,
const gfx::Point& focus_center) {
const gfx::SizeF source_size(kMagnifierSize.width() / kMagnifierScale,
kMagnifierSize.height() / kMagnifierScale);
const gfx::PointF source_origin(focus_center.x() - source_size.width() / 2,
focus_center.y() - source_size.height() / 2);
gfx::RectF source_bounds(source_origin, source_size);
source_bounds.AdjustToFit(gfx::RectF(parent_container_size));
return gfx::ToRoundedPoint(source_bounds.CenterPoint());
}
}
class TouchSelectionMagnifierAura::BorderRenderer : public LayerDelegate {
public:
BorderRenderer() { UpdateTheme(NativeTheme::GetInstanceForNativeUi()); }
BorderRenderer(const BorderRenderer&) = delete;
BorderRenderer& operator=(const BorderRenderer&) = delete;
~BorderRenderer() override = default;
void OnPaintLayer(const PaintContext& context) override {
PaintRecorder recorder(context, GetBorderLayerSize());
const gfx::Rect zoom_layer_bounds = GetZoomLayerBounds();
cc::PaintFlags shadow_flags;
shadow_flags.setAntiAlias(true);
shadow_flags.setColor(SK_ColorTRANSPARENT);
shadow_flags.setLooper(gfx::CreateShadowDrawLooper(
GetMagnifierShadowValues(key_shadow_color_, ambient_shadow_color)));
recorder.canvas()->DrawRoundRect(zoom_layer_bounds, kMagnifierRadius,
shadow_flags);
cc::PaintFlags mask_flags;
mask_flags.setAntiAlias(true);
mask_flags.setBlendMode(SkBlendMode::kClear);
mask_flags.setStyle(cc::PaintFlags::kFill_Style);
recorder.canvas()->DrawRoundRect(zoom_layer_bounds, kMagnifierRadius,
mask_flags);
cc::PaintFlags border_flags;
border_flags.setAntiAlias(true);
border_flags.setStyle(cc::PaintFlags::kStroke_Style);
border_flags.setStrokeWidth(kMagnifierBorderThickness);
border_flags.setColor(border_color_);
border_flags.setAlphaf(kMagnifierBorderOpacity);
recorder.canvas()->DrawRoundRect(zoom_layer_bounds, kMagnifierRadius,
border_flags);
}
void OnDeviceScaleFactorChanged(float old_device_scale_factor,
float new_device_scale_factor) override {}
void UpdateTheme(NativeTheme* theme) {
auto* color_provider = ColorProviderManager::Get().GetColorProviderFor(
theme->GetColorProviderKey(nullptr));
border_color_ = color_provider->GetColor(kColorSeparator);
key_shadow_color_ =
color_provider->GetColor(kColorShadowValueKeyShadowElevationThree);
ambient_shadow_color =
color_provider->GetColor(kColorShadowValueAmbientShadowElevationThree);
}
private:
SkColor border_color_ = gfx::kPlaceholderColor;
SkColor key_shadow_color_ = gfx::kPlaceholderColor;
SkColor ambient_shadow_color = gfx::kPlaceholderColor;
};
TouchSelectionMagnifierAura::TouchSelectionMagnifierAura() {
CreateMagnifierLayer();
theme_observation_.Observe(NativeTheme::GetInstanceForNativeUi());
}
TouchSelectionMagnifierAura::~TouchSelectionMagnifierAura() = default;
void TouchSelectionMagnifierAura::ShowFocusBound(Layer* parent,
const gfx::Point& focus_start,
const gfx::Point& focus_end) {
DCHECK(parent);
if (magnifier_layer_->parent() != parent) {
magnifier_layer_->SetVisible(false);
parent->Add(magnifier_layer_.get());
}
ScopedLayerAnimationSettings settings(magnifier_layer_->GetAnimator());
if (!magnifier_layer_->IsVisible()) {
settings.SetTransitionDuration(base::Milliseconds(0));
settings.SetTweenType(gfx::Tween::ZERO);
settings.SetPreemptionStrategy(LayerAnimator::IMMEDIATELY_SET_NEW_TARGET);
} else {
settings.SetTransitionDuration(kMagnifierTransitionDuration);
settings.SetTweenType(gfx::Tween::LINEAR);
settings.SetPreemptionStrategy(
LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
}
const gfx::Size magnifier_parent_size =
magnifier_layer_->parent()->bounds().size();
const gfx::Rect focus_rect = gfx::BoundingRect(focus_start, focus_end);
const gfx::Rect magnifier_layer_bounds =
GetMagnifierLayerBounds(magnifier_parent_size, focus_rect.top_center());
magnifier_layer_->SetBounds(magnifier_layer_bounds);
gfx::Rect zoom_layer_bounds = GetZoomLayerBounds();
const gfx::Point magnifier_source_center =
GetMagnifierSourceCenter(magnifier_parent_size, focus_rect.CenterPoint());
const gfx::Point zoom_layer_center =
zoom_layer_bounds.CenterPoint() +
magnifier_layer_bounds.OffsetFromOrigin();
const gfx::Point zoom_offset =
gfx::PointAtOffsetFromOrigin(zoom_layer_center - magnifier_source_center);
zoom_layer_bounds.Offset(-zoom_offset.x(), -zoom_offset.y());
zoom_layer_->SetBounds(zoom_layer_bounds);
zoom_layer_->SetLayerOffset(zoom_offset);
if (!magnifier_layer_->IsVisible()) {
magnifier_layer_->SetVisible(true);
}
}
void TouchSelectionMagnifierAura::OnNativeThemeUpdated(
NativeTheme* observed_theme) {
border_renderer_->UpdateTheme(observed_theme);
border_layer_->SchedulePaint(gfx::Rect(border_layer_->size()));
}
gfx::Rect TouchSelectionMagnifierAura::GetZoomedContentsBoundsForTesting()
const {
const gfx::RectF bounds{zoom_layer_->bounds()};
const gfx::PointF center = bounds.CenterPoint();
const gfx::SizeF contents_size(bounds.width() / kMagnifierScale,
bounds.height() / kMagnifierScale);
const gfx::PointF contents_origin(center.x() - contents_size.width() / 2,
center.y() - contents_size.height() / 2);
gfx::Rect contents_bounds =
gfx::ToEnclosingRect(gfx::RectF(contents_origin, contents_size));
return contents_bounds + magnifier_layer_->bounds().OffsetFromOrigin();
}
gfx::Rect TouchSelectionMagnifierAura::GetMagnifierBoundsForTesting() const {
return magnifier_layer_->bounds();
}
const Layer* TouchSelectionMagnifierAura::GetMagnifierParentForTesting() const {
return magnifier_layer_->parent();
}
void TouchSelectionMagnifierAura::CreateMagnifierLayer() {
magnifier_layer_ = std::make_unique<Layer>(LAYER_NOT_DRAWN);
magnifier_layer_->SetName("TouchSelectionMagnifierAura/MagnifierLayer");
magnifier_layer_->SetFillsBoundsOpaquely(false);
zoom_layer_ = std::make_unique<Layer>(LAYER_SOLID_COLOR);
zoom_layer_->SetName("TouchSelectionMagnifierAura/ZoomLayer");
zoom_layer_->SetBackgroundZoom(kMagnifierScale, 0);
zoom_layer_->SetBackdropFilterBounds(
gfx::RRectF{gfx::RectF(kMagnifierSize), kMagnifierRadius});
magnifier_layer_->Add(zoom_layer_.get());
border_layer_ = std::make_unique<Layer>();
border_layer_->SetName("TouchSelectionMagnifierAura/BorderLayer");
border_layer_->SetBounds(gfx::Rect(GetBorderLayerSize()));
border_renderer_ = std::make_unique<BorderRenderer>();
border_layer_->set_delegate(border_renderer_.get());
border_layer_->SetFillsBoundsOpaquely(false);
magnifier_layer_->Add(border_layer_.get());
}
}