chromium/ios/chrome/common/ui/util/pointer_interaction_util.mm

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

#import "ios/chrome/common/ui/util/pointer_interaction_util.h"

#import <ostream>

#import "base/logging.h"

namespace {
// Returns a pointer style with a hover effect with a slight tint and no pointer
// shape (i.e., the pointer stays the same).
UIPointerStyle* CreateHoverEffectNoShapePointerStyle(UIButton* button) {
  UITargetedPreview* preview = [[UITargetedPreview alloc] initWithView:button];
  UIPointerHoverEffect* effect =
      [UIPointerHoverEffect effectWithPreview:preview];
  effect.preferredTintMode = UIPointerEffectTintModeOverlay;
  effect.prefersScaledContent = NO;
  effect.prefersShadow = NO;
  return [UIPointerStyle styleWithEffect:effect shape:nil];
}

// Returns a pointer style with a highlight effect and a rounded rectangle
// pointer shape sized to the button frame.
UIPointerStyle* CreateHighlightEffectRectShapePointerStyle(UIButton* button) {
  UITargetedPreview* preview = [[UITargetedPreview alloc] initWithView:button];
  UIPointerHighlightEffect* effect =
      [UIPointerHighlightEffect effectWithPreview:preview];
  UIPointerShape* shape = [UIPointerShape shapeWithRoundedRect:button.frame];
  return [UIPointerStyle styleWithEffect:effect shape:shape];
}
}  // namespace

UIButtonPointerStyleProvider CreateDefaultEffectCirclePointerStyleProvider() {
  return ^UIPointerStyle*(UIButton* button, UIPointerEffect* proposedEffect,
                          UIPointerShape* proposedShape) {
    CGRect frame = button.frame;
    if (abs(frame.size.width - frame.size.height) > 0.5) {
      LOG(ERROR) << "Button frame for circular pointer isn't square: ("
                 << frame.size.width << " x " << frame.size.height << ")";
    }
    UIBezierPath* path = [UIBezierPath bezierPathWithOvalInRect:frame];
    UIPointerShape* shape = [UIPointerShape shapeWithPath:path];
    return [UIPointerStyle styleWithEffect:proposedEffect shape:shape];
  };
}

UIButtonPointerStyleProvider CreateLiftEffectCirclePointerStyleProvider() {
  return ^UIPointerStyle*(UIButton* button, UIPointerEffect* proposedEffect,
                          UIPointerShape* proposedShape) {
    CGRect frame = button.frame;
    if (abs(frame.size.width - frame.size.height) > 0.5) {
      LOG(ERROR) << "Button frame for circular pointer isn't square: ("
                 << frame.size.width << " x " << frame.size.height << ")";
    }
    UITargetedPreview* preview =
        [[UITargetedPreview alloc] initWithView:button];
    UIPointerLiftEffect* effect =
        [UIPointerLiftEffect effectWithPreview:preview];
    UIBezierPath* path = [UIBezierPath bezierPathWithOvalInRect:frame];
    UIPointerShape* shape = [UIPointerShape shapeWithPath:path];
    return [UIPointerStyle styleWithEffect:effect shape:shape];
  };
}

UIButtonPointerStyleProvider CreateOpaqueButtonPointerStyleProvider() {
  return ^UIPointerStyle*(UIButton* button, UIPointerEffect* proposedEffect,
                          UIPointerShape* proposedShape) {
    if (!button.backgroundColor ||
        button.backgroundColor == [UIColor clearColor]) {
      LOG(ERROR) << "Expected an opaque background for button.";
    }
    return CreateHoverEffectNoShapePointerStyle(button);
  };
}

UIButtonPointerStyleProvider CreateTransparentButtonPointerStyleProvider() {
  return ^UIPointerStyle*(UIButton* button, UIPointerEffect* proposedEffect,
                          UIPointerShape* proposedShape) {
    if (button.backgroundColor &&
        button.backgroundColor != [UIColor clearColor]) {
      LOG(ERROR) << "Expected a transparent background for button.";
    }
    return CreateHighlightEffectRectShapePointerStyle(button);
  };
}

UIButtonPointerStyleProvider
CreateOpaqueOrTransparentButtonPointerStyleProvider() {
  return ^UIPointerStyle*(UIButton* button, UIPointerEffect* proposedEffect,
                          UIPointerShape* proposedShape) {
    if (button.backgroundColor &&
        button.backgroundColor != [UIColor clearColor]) {
      return CreateHoverEffectNoShapePointerStyle(button);
    }
    return CreateHighlightEffectRectShapePointerStyle(button);
  };
}

@interface ViewPointerInteraction ()
@property(nonatomic, strong) UIPointerInteraction* pointerInteraction;
@end

@implementation ViewPointerInteraction

- (instancetype)init {
  self = [super init];
  if (self) {
    self.pointerInteraction =
        [[UIPointerInteraction alloc] initWithDelegate:self];
  }
  return self;
}

#pragma mark UIInteraction

- (__kindof UIView*)view {
  return [self.pointerInteraction view];
}

- (void)didMoveToView:(UIView*)view {
  [self.pointerInteraction didMoveToView:view];
}

- (void)willMoveToView:(UIView*)view {
  [self.pointerInteraction willMoveToView:view];
}

#pragma mark UIPointerInteractionDelegate

- (UIPointerRegion*)pointerInteraction:(UIPointerInteraction*)interaction
                      regionForRequest:(UIPointerRegionRequest*)request
                         defaultRegion:(UIPointerRegion*)defaultRegion {
  return defaultRegion;
}

- (UIPointerStyle*)pointerInteraction:(UIPointerInteraction*)interaction
                       styleForRegion:(UIPointerRegion*)region {
  if (!interaction.view.window)
    return nil;

  UIPointerHoverEffect* effect = [UIPointerHoverEffect
      effectWithPreview:[[UITargetedPreview alloc]
                            initWithView:interaction.view]];
  effect.prefersScaledContent = NO;
  effect.prefersShadow = NO;
  return [UIPointerStyle styleWithEffect:effect shape:nil];
}

@end