// 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' as ui show Color; import 'package:flutter/animation.dart'; import 'package:flutter/foundation.dart'; import 'package:vector_math/vector_math_64.dart'; import 'layer.dart'; import 'object.dart'; import 'proxy_box.dart'; import 'sliver.dart'; /// A base class for sliver render objects that resemble their children. /// /// A proxy sliver has a single child and simply mimics all the properties of /// that child by calling through to the child for each function in the render /// sliver protocol. For example, a proxy sliver determines its geometry by /// asking its sliver child to layout with the same constraints and then /// matching the geometry. /// /// A proxy sliver isn't useful on its own because you might as well just /// replace the proxy sliver with its child. However, RenderProxySliver is a /// useful base class for render objects that wish to mimic most, but not all, /// of the properties of their sliver child. /// /// See also: /// /// * [RenderProxyBox], a base class for render boxes that resemble their /// children. abstract class RenderProxySliver extends RenderSliver with RenderObjectWithChildMixin<RenderSliver> { /// Creates a proxy render sliver. /// /// Proxy render slivers aren't created directly because they simply proxy /// the render sliver protocol to their sliver [child]. Instead, use one of /// the subclasses. RenderProxySliver([RenderSliver child]) { this.child = child; } @override void setupParentData(RenderObject child) { if (child.parentData is! SliverPhysicalParentData) child.parentData = SliverPhysicalParentData(); } @override void performLayout() { assert(child != null); child.layout(constraints, parentUsesSize: true); geometry = child.geometry; } @override void paint(PaintingContext context, Offset offset) { if (child != null) context.paintChild(child, offset); } @override bool hitTestChildren(SliverHitTestResult result, {double mainAxisPosition, double crossAxisPosition}) { return child != null && child.geometry.hitTestExtent > 0 && child.hitTest( result, mainAxisPosition: mainAxisPosition, crossAxisPosition: crossAxisPosition, ); } @override double childMainAxisPosition(RenderSliver child) { assert(child != null); assert(child == this.child); return 0.0; } @override void applyPaintTransform(RenderObject child, Matrix4 transform) { assert(child != null); final SliverPhysicalParentData childParentData = child.parentData as SliverPhysicalParentData; childParentData.applyPaintTransform(transform); } } /// Makes its sliver child partially transparent. /// /// This class paints its sliver child into an intermediate buffer and then /// blends the sliver child back into the scene, partially transparent. /// /// For values of opacity other than 0.0 and 1.0, this class is relatively /// expensive, because it requires painting the sliver child into an intermediate /// buffer. For the value 0.0, the sliver child is simply not painted at all. /// For the value 1.0, the sliver child is painted immediately without an /// intermediate buffer. class RenderSliverOpacity extends RenderProxySliver { /// Creates a partially transparent render object. /// /// The [opacity] argument must be between 0.0 and 1.0, inclusive. RenderSliverOpacity({ double opacity = 1.0, bool alwaysIncludeSemantics = false, RenderSliver sliver, }) : assert(opacity != null && opacity >= 0.0 && opacity <= 1.0), assert(alwaysIncludeSemantics != null), _opacity = opacity, _alwaysIncludeSemantics = alwaysIncludeSemantics, _alpha = ui.Color.getAlphaFromOpacity(opacity) { child = sliver; } @override bool get alwaysNeedsCompositing => child != null && (_alpha != 0 && _alpha != 255); int _alpha; /// The fraction to scale the child's alpha value. /// /// An opacity of 1.0 is fully opaque. An opacity of 0.0 is fully transparent /// (i.e. invisible). /// /// The opacity must not be null. /// /// Values 1.0 and 0.0 are painted with a fast path. Other values /// require painting the child into an intermediate buffer, which is /// expensive. double get opacity => _opacity; double _opacity; set opacity(double value) { assert(value != null); assert(value >= 0.0 && value <= 1.0); if (_opacity == value) return; final bool didNeedCompositing = alwaysNeedsCompositing; final bool wasVisible = _alpha != 0; _opacity = value; _alpha = ui.Color.getAlphaFromOpacity(_opacity); if (didNeedCompositing != alwaysNeedsCompositing) markNeedsCompositingBitsUpdate(); markNeedsPaint(); if (wasVisible != (_alpha != 0) && !alwaysIncludeSemantics) markNeedsSemanticsUpdate(); } /// Whether child semantics are included regardless of the opacity. /// /// If false, semantics are excluded when [opacity] is 0.0. /// /// Defaults to false. bool get alwaysIncludeSemantics => _alwaysIncludeSemantics; bool _alwaysIncludeSemantics; set alwaysIncludeSemantics(bool value) { if (value == _alwaysIncludeSemantics) return; _alwaysIncludeSemantics = value; markNeedsSemanticsUpdate(); } @override void paint(PaintingContext context, Offset offset) { if (child != null && child.geometry.visible) { if (_alpha == 0) { // No need to keep the layer. We'll create a new one if necessary. layer = null; return; } if (_alpha == 255) { // No need to keep the layer. We'll create a new one if necessary. layer = null; context.paintChild(child, offset); return; } assert(needsCompositing); layer = context.pushOpacity( offset, _alpha, super.paint, oldLayer: layer as OpacityLayer, ); } } @override void visitChildrenForSemantics(RenderObjectVisitor visitor) { if (child != null && (_alpha != 0 || alwaysIncludeSemantics)) visitor(child); } @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); properties.add(DoubleProperty('opacity', opacity)); properties.add(FlagProperty('alwaysIncludeSemantics', value: alwaysIncludeSemantics, ifTrue: 'alwaysIncludeSemantics')); } } /// A render object that is invisible during hit testing. /// /// When [ignoring] is true, this render object (and its subtree) is invisible /// to hit testing. It still consumes space during layout and paints its sliver /// child as usual. It just cannot be the target of located events, because its /// render object returns false from [hitTest]. /// /// When [ignoringSemantics] is true, the subtree will be invisible to the /// semantics layer (and thus e.g. accessibility tools). If [ignoringSemantics] /// is null, it uses the value of [ignoring]. class RenderSliverIgnorePointer extends RenderProxySliver { /// Creates a render object that is invisible to hit testing. /// /// The [ignoring] argument must not be null. If [ignoringSemantics] is null, /// this render object will be ignored for semantics if [ignoring] is true. RenderSliverIgnorePointer({ RenderSliver sliver, bool ignoring = true, bool ignoringSemantics, }) : assert(ignoring != null), _ignoring = ignoring, _ignoringSemantics = ignoringSemantics { child = sliver; } /// Whether this render object is ignored during hit testing. /// /// Regardless of whether this render object is ignored during hit testing, it /// will still consume space during layout and be visible during painting. bool get ignoring => _ignoring; bool _ignoring; set ignoring(bool value) { assert(value != null); if (value == _ignoring) return; _ignoring = value; if (_ignoringSemantics == null || !_ignoringSemantics) markNeedsSemanticsUpdate(); } /// Whether the semantics of this render object is ignored when compiling the /// semantics tree. /// /// If null, defaults to value of [ignoring]. /// /// See [SemanticsNode] for additional information about the semantics tree. bool get ignoringSemantics => _ignoringSemantics; bool _ignoringSemantics; set ignoringSemantics(bool value) { if (value == _ignoringSemantics) return; final bool oldEffectiveValue = _effectiveIgnoringSemantics; _ignoringSemantics = value; if (oldEffectiveValue != _effectiveIgnoringSemantics) markNeedsSemanticsUpdate(); } bool get _effectiveIgnoringSemantics => ignoringSemantics ?? ignoring; @override bool hitTest(SliverHitTestResult result, {double mainAxisPosition, double crossAxisPosition}) { return !ignoring && super.hitTest( result, mainAxisPosition: mainAxisPosition, crossAxisPosition: crossAxisPosition, ); } @override void visitChildrenForSemantics(RenderObjectVisitor visitor) { if (child != null && !_effectiveIgnoringSemantics) visitor(child); } @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); properties.add(DiagnosticsProperty<bool>('ignoring', ignoring)); properties.add(DiagnosticsProperty<bool>('ignoringSemantics', _effectiveIgnoringSemantics, description: ignoringSemantics == null ? 'implicitly $_effectiveIgnoringSemantics' : null)); } } /// Lays the sliver child out as if it was in the tree, but without painting /// anything, without making the sliver child available for hit testing, and /// without taking any room in the parent. class RenderSliverOffstage extends RenderProxySliver { /// Creates an offstage render object. RenderSliverOffstage({ bool offstage = true, RenderSliver sliver, }) : assert(offstage != null), _offstage = offstage { child = sliver; } /// Whether the sliver child is hidden from the rest of the tree. /// /// If true, the sliver child is laid out as if it was in the tree, but /// without painting anything, without making the sliver child available for /// hit testing, and without taking any room in the parent. /// /// If false, the sliver child is included in the tree as normal. bool get offstage => _offstage; bool _offstage; set offstage(bool value) { assert(value != null); if (value == _offstage) return; _offstage = value; markNeedsLayoutForSizedByParentChange(); } @override void performLayout() { assert(child != null); child.layout(constraints, parentUsesSize: true); if (!offstage) geometry = child.geometry; else geometry = const SliverGeometry( scrollExtent: 0.0, visible: false, maxPaintExtent: 0.0, ); } @override bool hitTest(SliverHitTestResult result, {double mainAxisPosition, double crossAxisPosition}) { return !offstage && super.hitTest( result, mainAxisPosition: mainAxisPosition, crossAxisPosition: crossAxisPosition, ); } @override bool hitTestChildren(SliverHitTestResult result, {double mainAxisPosition, double crossAxisPosition}) { return !offstage && child != null && child.geometry.hitTestExtent > 0 && child.hitTest( result, mainAxisPosition: mainAxisPosition, crossAxisPosition: crossAxisPosition, ); } @override void paint(PaintingContext context, Offset offset) { if (offstage) return; context.paintChild(child, offset); } @override void visitChildrenForSemantics(RenderObjectVisitor visitor) { if (offstage) return; super.visitChildrenForSemantics(visitor); } @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); properties.add(DiagnosticsProperty<bool>('offstage', offstage)); } @override List<DiagnosticsNode> debugDescribeChildren() { if (child == null) return <DiagnosticsNode>[]; return <DiagnosticsNode>[ child.toDiagnosticsNode( name: 'child', style: offstage ? DiagnosticsTreeStyle.offstage : DiagnosticsTreeStyle.sparse, ), ]; } } /// Makes its sliver child partially transparent, driven from an [Animation]. /// /// This is a variant of [RenderSliverOpacity] that uses an [Animation<double>] /// rather than a [double] to control the opacity. class RenderSliverAnimatedOpacity extends RenderProxySliver with RenderAnimatedOpacityMixin<RenderSliver>{ /// Creates a partially transparent render object. /// /// The [opacity] argument must not be null. RenderSliverAnimatedOpacity({ @required Animation<double> opacity, bool alwaysIncludeSemantics = false, RenderSliver sliver, }) : assert(opacity != null), assert(alwaysIncludeSemantics != null) { this.opacity = opacity; this.alwaysIncludeSemantics = alwaysIncludeSemantics; child = sliver; } }