// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'dart:ui';

import 'package:flutter/foundation.dart';
import 'package:flutter/rendering.dart';

import 'framework.dart';

/// Applies an [ImageFilter] to its child.
///
/// An image filter will always apply its filter operation to the child widget,
/// even if said filter is conceptually a "no-op", such as an ImageFilter.blur
/// with a radius of 0 or an ImageFilter.matrix with an identity matrix. Setting
/// [ImageFiltered.enabled] to `false` is a more efficient manner of disabling
/// an image filter.
///
/// The framework does not attempt to optimize out "no-op" filters because it
/// cannot tell the difference between an intentional no-op and a filter that is
/// only incidentally a no-op. Consider an ImageFilter.matrix that is animated
/// and happens to pass through the identity matrix. If the framework identified it
/// as a no-op it would drop and then recreate the layer during the animation which
/// would be more expensive than keeping it around.
///
/// {@youtube 560 315 https://www.youtube.com/watch?v=7Lftorq4i2o}
///
/// See also:
///
/// * [BackdropFilter], which applies an [ImageFilter] to everything
///   beneath its child.
/// * [ColorFiltered], which applies a [ColorFilter] to its child.
@immutable
class ImageFiltered extends SingleChildRenderObjectWidget {
  /// Creates a widget that applies an [ImageFilter] to its child.
  ///
  /// The [imageFilter] must not be null.
  const ImageFiltered({
    super.key,
    required this.imageFilter,
    super.child,
    this.enabled = true,
  }) : assert(imageFilter != null);

  /// The image filter to apply to the child of this widget.
  final ImageFilter imageFilter;

  /// Whether or not to apply the image filter operation to the child of this
  /// widget.
  ///
  /// Prefer setting enabled to `false` instead of creating a "no-op" filter
  /// type for performance reasons.
  final bool enabled;

  @override
  RenderObject createRenderObject(BuildContext context) => _ImageFilterRenderObject(imageFilter, enabled);

  @override
  void updateRenderObject(BuildContext context, RenderObject renderObject) {
    (renderObject as _ImageFilterRenderObject)
      ..enabled = enabled
      ..imageFilter = imageFilter;
  }

  @override
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    properties.add(DiagnosticsProperty<ImageFilter>('imageFilter', imageFilter));
  }
}

class _ImageFilterRenderObject extends RenderProxyBox {
  _ImageFilterRenderObject(this._imageFilter, this._enabled);

  bool get enabled => _enabled;
  bool _enabled;
  set enabled(bool value) {
    if (enabled == value) {
      return;
    }
    _enabled = value;
    markNeedsPaint();
  }

  ImageFilter get imageFilter => _imageFilter;
  ImageFilter _imageFilter;
  set imageFilter(ImageFilter value) {
    assert(value != null);
    if (value != _imageFilter) {
      _imageFilter = value;
      markNeedsPaint();
    }
  }

  @override
  bool get alwaysNeedsCompositing => child != null && enabled;

  @override
  void paint(PaintingContext context, Offset offset) {
    assert(imageFilter != null);
    if (!enabled) {
      layer = null;
      return super.paint(context, offset);
    }

    if (layer == null) {
      layer = ImageFilterLayer(imageFilter: imageFilter, offset: offset);
    } else {
      final ImageFilterLayer filterLayer = layer! as ImageFilterLayer;
      filterLayer.imageFilter = imageFilter;
      filterLayer.offset = offset;
    }
    context.pushLayer(layer!, super.paint, Offset.zero);
    assert(() {
      layer!.debugCreator = debugCreator;
      return true;
    }());
  }
}
