| // 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:math' as math; |
| |
| import 'package:flutter/rendering.dart'; |
| |
| const double kTwoPi = 2 * math.pi; |
| |
| class SectorConstraints extends Constraints { |
| const SectorConstraints({ |
| this.minDeltaRadius = 0.0, |
| this.maxDeltaRadius = double.infinity, |
| this.minDeltaTheta = 0.0, |
| this.maxDeltaTheta = kTwoPi, |
| }) : assert(maxDeltaRadius >= minDeltaRadius), |
| assert(maxDeltaTheta >= minDeltaTheta); |
| |
| const SectorConstraints.tight({ double deltaRadius = 0.0, double deltaTheta = 0.0 }) |
| : minDeltaRadius = deltaRadius, |
| maxDeltaRadius = deltaRadius, |
| minDeltaTheta = deltaTheta, |
| maxDeltaTheta = deltaTheta; |
| |
| final double minDeltaRadius; |
| final double maxDeltaRadius; |
| final double minDeltaTheta; |
| final double maxDeltaTheta; |
| |
| double constrainDeltaRadius(double deltaRadius) { |
| return deltaRadius.clamp(minDeltaRadius, maxDeltaRadius); |
| } |
| |
| double constrainDeltaTheta(double deltaTheta) { |
| return deltaTheta.clamp(minDeltaTheta, maxDeltaTheta); |
| } |
| |
| @override |
| bool get isTight => minDeltaTheta >= maxDeltaTheta && minDeltaTheta >= maxDeltaTheta; |
| |
| @override |
| bool get isNormalized => minDeltaRadius <= maxDeltaRadius && minDeltaTheta <= maxDeltaTheta; |
| |
| @override |
| bool debugAssertIsValid({ |
| bool isAppliedConstraint = false, |
| InformationCollector? informationCollector, |
| }) { |
| assert(isNormalized); |
| return isNormalized; |
| } |
| } |
| |
| class SectorDimensions { |
| const SectorDimensions({ this.deltaRadius = 0.0, this.deltaTheta = 0.0 }); |
| |
| factory SectorDimensions.withConstraints( |
| SectorConstraints constraints, { |
| double deltaRadius = 0.0, |
| double deltaTheta = 0.0, |
| }) { |
| return SectorDimensions( |
| deltaRadius: constraints.constrainDeltaRadius(deltaRadius), |
| deltaTheta: constraints.constrainDeltaTheta(deltaTheta), |
| ); |
| } |
| |
| final double deltaRadius; |
| final double deltaTheta; |
| } |
| |
| class SectorParentData extends ParentData { |
| double radius = 0.0; |
| double theta = 0.0; |
| } |
| |
| /// Base class for [RenderObject]s that live in a polar coordinate space. |
| /// |
| /// In a polar coordinate system each point on a plane is determined by a |
| /// distance from a reference point ("radius") and an angle from a reference |
| /// direction ("theta"). |
| /// |
| /// See also: |
| /// |
| /// * <https://en.wikipedia.org/wiki/Polar_coordinate_system>, which defines |
| /// the polar coordinate space. |
| /// * [RenderBox], which is the base class for [RenderObject]s that live in a |
| /// Cartesian coordinate space. |
| abstract class RenderSector extends RenderObject { |
| |
| @override |
| void setupParentData(RenderObject child) { |
| if (child.parentData is! SectorParentData) |
| child.parentData = SectorParentData(); |
| } |
| |
| // RenderSectors always use SectorParentData subclasses, as they need to be |
| // able to read their position information for painting and hit testing. |
| @override |
| SectorParentData? get parentData => super.parentData as SectorParentData?; |
| |
| SectorDimensions getIntrinsicDimensions(SectorConstraints constraints, double radius) { |
| return SectorDimensions.withConstraints(constraints); |
| } |
| |
| @override |
| SectorConstraints get constraints => super.constraints as SectorConstraints; |
| |
| @override |
| void debugAssertDoesMeetConstraints() { |
| assert(constraints != null); |
| assert(deltaRadius != null); |
| assert(deltaRadius < double.infinity); |
| assert(deltaTheta != null); |
| assert(deltaTheta < double.infinity); |
| assert(constraints.minDeltaRadius <= deltaRadius); |
| assert(deltaRadius <= math.max(constraints.minDeltaRadius, constraints.maxDeltaRadius)); |
| assert(constraints.minDeltaTheta <= deltaTheta); |
| assert(deltaTheta <= math.max(constraints.minDeltaTheta, constraints.maxDeltaTheta)); |
| } |
| |
| @override |
| void performResize() { |
| // default behavior for subclasses that have sizedByParent = true |
| deltaRadius = constraints.constrainDeltaRadius(0.0); |
| deltaTheta = constraints.constrainDeltaTheta(0.0); |
| } |
| |
| @override |
| void performLayout() { |
| // descendants have to either override performLayout() to set both |
| // the dimensions and lay out children, or, set sizedByParent to |
| // true so that performResize()'s logic above does its thing. |
| assert(sizedByParent); |
| } |
| |
| @override |
| Rect get paintBounds => Rect.fromLTWH(0.0, 0.0, 2.0 * deltaRadius, 2.0 * deltaRadius); |
| |
| @override |
| Rect get semanticBounds => Rect.fromLTWH(-deltaRadius, -deltaRadius, 2.0 * deltaRadius, 2.0 * deltaRadius); |
| |
| bool hitTest(SectorHitTestResult result, { required double radius, required double theta }) { |
| if (radius < parentData!.radius || radius >= parentData!.radius + deltaRadius || |
| theta < parentData!.theta || theta >= parentData!.theta + deltaTheta) |
| return false; |
| hitTestChildren(result, radius: radius, theta: theta); |
| result.add(SectorHitTestEntry(this, radius: radius, theta: theta)); |
| return true; |
| } |
| void hitTestChildren(SectorHitTestResult result, { required double radius, required double theta }) { } |
| |
| late double deltaRadius; |
| late double deltaTheta; |
| } |
| |
| abstract class RenderDecoratedSector extends RenderSector { |
| |
| RenderDecoratedSector(BoxDecoration? decoration) : _decoration = decoration; |
| |
| BoxDecoration? _decoration; |
| BoxDecoration? get decoration => _decoration; |
| set decoration(BoxDecoration? value) { |
| if (value == _decoration) |
| return; |
| _decoration = value; |
| markNeedsPaint(); |
| } |
| |
| // offset must point to the center of the circle |
| @override |
| void paint(PaintingContext context, Offset offset) { |
| assert(deltaRadius != null); |
| assert(deltaTheta != null); |
| assert(parentData is SectorParentData); |
| |
| if (_decoration == null) |
| return; |
| |
| if (_decoration!.color != null) { |
| final Canvas canvas = context.canvas; |
| final Paint paint = Paint()..color = _decoration!.color!; |
| final Path path = Path(); |
| final double outerRadius = parentData!.radius + deltaRadius; |
| final Rect outerBounds = Rect.fromLTRB(offset.dx-outerRadius, offset.dy-outerRadius, offset.dx+outerRadius, offset.dy+outerRadius); |
| path.arcTo(outerBounds, parentData!.theta, deltaTheta, true); |
| final double innerRadius = parentData!.radius; |
| final Rect innerBounds = Rect.fromLTRB(offset.dx-innerRadius, offset.dy-innerRadius, offset.dx+innerRadius, offset.dy+innerRadius); |
| path.arcTo(innerBounds, parentData!.theta + deltaTheta, -deltaTheta, false); |
| path.close(); |
| canvas.drawPath(path, paint); |
| } |
| } |
| |
| } |
| |
| class SectorChildListParentData extends SectorParentData with ContainerParentDataMixin<RenderSector> { } |
| |
| class RenderSectorWithChildren extends RenderDecoratedSector with ContainerRenderObjectMixin<RenderSector, SectorChildListParentData> { |
| RenderSectorWithChildren(BoxDecoration? decoration) : super(decoration); |
| |
| @override |
| void hitTestChildren(SectorHitTestResult result, { required double radius, required double theta }) { |
| RenderSector? child = lastChild; |
| while (child != null) { |
| if (child.hitTest(result, radius: radius, theta: theta)) |
| return; |
| final SectorChildListParentData childParentData = child.parentData! as SectorChildListParentData; |
| child = childParentData.previousSibling; |
| } |
| } |
| |
| @override |
| void visitChildren(RenderObjectVisitor visitor) { |
| RenderSector? child = lastChild; |
| while (child != null) { |
| visitor(child); |
| final SectorChildListParentData childParentData = child.parentData! as SectorChildListParentData; |
| child = childParentData.previousSibling; |
| } |
| } |
| } |
| |
| class RenderSectorRing extends RenderSectorWithChildren { |
| // lays out RenderSector children in a ring |
| |
| RenderSectorRing({ |
| BoxDecoration? decoration, |
| double deltaRadius = double.infinity, |
| double padding = 0.0, |
| }) : _padding = padding, |
| assert(deltaRadius >= 0.0), |
| _desiredDeltaRadius = deltaRadius, |
| super(decoration); |
| |
| double _desiredDeltaRadius; |
| double get desiredDeltaRadius => _desiredDeltaRadius; |
| set desiredDeltaRadius(double value) { |
| assert(value != null); |
| assert(value >= 0); |
| if (_desiredDeltaRadius != value) { |
| _desiredDeltaRadius = value; |
| markNeedsLayout(); |
| } |
| } |
| |
| double _padding; |
| double get padding => _padding; |
| set padding(double value) { |
| // TODO(ianh): avoid code duplication |
| assert(value != null); |
| if (_padding != value) { |
| _padding = value; |
| markNeedsLayout(); |
| } |
| } |
| |
| @override |
| void setupParentData(RenderObject child) { |
| // TODO(ianh): avoid code duplication |
| if (child.parentData is! SectorChildListParentData) |
| child.parentData = SectorChildListParentData(); |
| } |
| |
| @override |
| SectorDimensions getIntrinsicDimensions(SectorConstraints constraints, double radius) { |
| final double outerDeltaRadius = constraints.constrainDeltaRadius(desiredDeltaRadius); |
| final double innerDeltaRadius = math.max(0.0, outerDeltaRadius - padding * 2.0); |
| final double childRadius = radius + padding; |
| final double paddingTheta = math.atan(padding / (radius + outerDeltaRadius)); |
| double innerTheta = paddingTheta; // increments with each child |
| double remainingDeltaTheta = math.max(0.0, constraints.maxDeltaTheta - (innerTheta + paddingTheta)); |
| RenderSector? child = firstChild; |
| while (child != null) { |
| final SectorConstraints innerConstraints = SectorConstraints( |
| maxDeltaRadius: innerDeltaRadius, |
| maxDeltaTheta: remainingDeltaTheta, |
| ); |
| final SectorDimensions childDimensions = child.getIntrinsicDimensions(innerConstraints, childRadius); |
| innerTheta += childDimensions.deltaTheta; |
| remainingDeltaTheta -= childDimensions.deltaTheta; |
| final SectorChildListParentData childParentData = child.parentData! as SectorChildListParentData; |
| child = childParentData.nextSibling; |
| if (child != null) { |
| innerTheta += paddingTheta; |
| remainingDeltaTheta -= paddingTheta; |
| } |
| } |
| return SectorDimensions.withConstraints( |
| constraints, |
| deltaRadius: outerDeltaRadius, |
| deltaTheta: innerTheta, |
| ); |
| } |
| |
| @override |
| void performLayout() { |
| assert(parentData is SectorParentData); |
| deltaRadius = constraints.constrainDeltaRadius(desiredDeltaRadius); |
| assert(deltaRadius < double.infinity); |
| final double innerDeltaRadius = deltaRadius - padding * 2.0; |
| final double childRadius = parentData!.radius + padding; |
| final double paddingTheta = math.atan(padding / (parentData!.radius + deltaRadius)); |
| double innerTheta = paddingTheta; // increments with each child |
| double remainingDeltaTheta = constraints.maxDeltaTheta - (innerTheta + paddingTheta); |
| RenderSector? child = firstChild; |
| while (child != null) { |
| final SectorConstraints innerConstraints = SectorConstraints( |
| maxDeltaRadius: innerDeltaRadius, |
| maxDeltaTheta: remainingDeltaTheta, |
| ); |
| assert(child.parentData is SectorParentData); |
| child.parentData!.theta = innerTheta; |
| child.parentData!.radius = childRadius; |
| child.layout(innerConstraints, parentUsesSize: true); |
| innerTheta += child.deltaTheta; |
| remainingDeltaTheta -= child.deltaTheta; |
| final SectorChildListParentData childParentData = child.parentData! as SectorChildListParentData; |
| child = childParentData.nextSibling; |
| if (child != null) { |
| innerTheta += paddingTheta; |
| remainingDeltaTheta -= paddingTheta; |
| } |
| } |
| deltaTheta = innerTheta; |
| } |
| |
| // offset must point to the center of our circle |
| // each sector then knows how to paint itself at its location |
| @override |
| void paint(PaintingContext context, Offset offset) { |
| // TODO(ianh): avoid code duplication |
| super.paint(context, offset); |
| RenderSector? child = firstChild; |
| while (child != null) { |
| context.paintChild(child, offset); |
| final SectorChildListParentData childParentData = child.parentData! as SectorChildListParentData; |
| child = childParentData.nextSibling; |
| } |
| } |
| |
| } |
| |
| class RenderSectorSlice extends RenderSectorWithChildren { |
| // lays out RenderSector children in a stack |
| |
| RenderSectorSlice({ |
| BoxDecoration? decoration, |
| double deltaTheta = kTwoPi, |
| double padding = 0.0, |
| }) : _padding = padding, _desiredDeltaTheta = deltaTheta, super(decoration); |
| |
| double _desiredDeltaTheta; |
| double get desiredDeltaTheta => _desiredDeltaTheta; |
| set desiredDeltaTheta(double value) { |
| assert(value != null); |
| if (_desiredDeltaTheta != value) { |
| _desiredDeltaTheta = value; |
| markNeedsLayout(); |
| } |
| } |
| |
| double _padding; |
| double get padding => _padding; |
| set padding(double value) { |
| // TODO(ianh): avoid code duplication |
| assert(value != null); |
| if (_padding != value) { |
| _padding = value; |
| markNeedsLayout(); |
| } |
| } |
| |
| @override |
| void setupParentData(RenderObject child) { |
| // TODO(ianh): avoid code duplication |
| if (child.parentData is! SectorChildListParentData) |
| child.parentData = SectorChildListParentData(); |
| } |
| |
| @override |
| SectorDimensions getIntrinsicDimensions(SectorConstraints constraints, double radius) { |
| assert(parentData is SectorParentData); |
| final double paddingTheta = math.atan(padding / parentData!.radius); |
| final double outerDeltaTheta = constraints.constrainDeltaTheta(desiredDeltaTheta); |
| final double innerDeltaTheta = outerDeltaTheta - paddingTheta * 2.0; |
| double childRadius = parentData!.radius + padding; |
| double remainingDeltaRadius = constraints.maxDeltaRadius - (padding * 2.0); |
| RenderSector? child = firstChild; |
| while (child != null) { |
| final SectorConstraints innerConstraints = SectorConstraints( |
| maxDeltaRadius: remainingDeltaRadius, |
| maxDeltaTheta: innerDeltaTheta, |
| ); |
| final SectorDimensions childDimensions = child.getIntrinsicDimensions(innerConstraints, childRadius); |
| childRadius += childDimensions.deltaRadius; |
| remainingDeltaRadius -= childDimensions.deltaRadius; |
| final SectorChildListParentData childParentData = child.parentData! as SectorChildListParentData; |
| child = childParentData.nextSibling; |
| childRadius += padding; |
| remainingDeltaRadius -= padding; |
| } |
| return SectorDimensions.withConstraints( |
| constraints, |
| deltaRadius: childRadius - parentData!.radius, |
| deltaTheta: outerDeltaTheta, |
| ); |
| } |
| |
| @override |
| void performLayout() { |
| assert(parentData is SectorParentData); |
| deltaTheta = constraints.constrainDeltaTheta(desiredDeltaTheta); |
| assert(deltaTheta <= kTwoPi); |
| final double paddingTheta = math.atan(padding / parentData!.radius); |
| final double innerTheta = parentData!.theta + paddingTheta; |
| final double innerDeltaTheta = deltaTheta - paddingTheta * 2.0; |
| double childRadius = parentData!.radius + padding; |
| double remainingDeltaRadius = constraints.maxDeltaRadius - (padding * 2.0); |
| RenderSector? child = firstChild; |
| while (child != null) { |
| final SectorConstraints innerConstraints = SectorConstraints( |
| maxDeltaRadius: remainingDeltaRadius, |
| maxDeltaTheta: innerDeltaTheta, |
| ); |
| child.parentData!.theta = innerTheta; |
| child.parentData!.radius = childRadius; |
| child.layout(innerConstraints, parentUsesSize: true); |
| childRadius += child.deltaRadius; |
| remainingDeltaRadius -= child.deltaRadius; |
| final SectorChildListParentData childParentData = child.parentData! as SectorChildListParentData; |
| child = childParentData.nextSibling; |
| childRadius += padding; |
| remainingDeltaRadius -= padding; |
| } |
| deltaRadius = childRadius - parentData!.radius; |
| } |
| |
| // offset must point to the center of our circle |
| // each sector then knows how to paint itself at its location |
| @override |
| void paint(PaintingContext context, Offset offset) { |
| // TODO(ianh): avoid code duplication |
| super.paint(context, offset); |
| RenderSector? child = firstChild; |
| while (child != null) { |
| assert(child.parentData is SectorChildListParentData); |
| context.paintChild(child, offset); |
| final SectorChildListParentData childParentData = child.parentData! as SectorChildListParentData; |
| child = childParentData.nextSibling; |
| } |
| } |
| |
| } |
| |
| class RenderBoxToRenderSectorAdapter extends RenderBox with RenderObjectWithChildMixin<RenderSector> { |
| RenderBoxToRenderSectorAdapter({ double innerRadius = 0.0, RenderSector? child }) |
| : _innerRadius = innerRadius { |
| this.child = child; |
| } |
| |
| double _innerRadius; |
| double get innerRadius => _innerRadius; |
| set innerRadius(double value) { |
| _innerRadius = value; |
| markNeedsLayout(); |
| } |
| |
| @override |
| void setupParentData(RenderObject child) { |
| if (child.parentData is! SectorParentData) |
| child.parentData = SectorParentData(); |
| } |
| |
| @override |
| double computeMinIntrinsicWidth(double height) { |
| if (child == null) |
| return 0.0; |
| return getIntrinsicDimensions(height: height).width; |
| } |
| |
| @override |
| double computeMaxIntrinsicWidth(double height) { |
| if (child == null) |
| return 0.0; |
| return getIntrinsicDimensions(height: height).width; |
| } |
| |
| @override |
| double computeMinIntrinsicHeight(double width) { |
| if (child == null) |
| return 0.0; |
| return getIntrinsicDimensions(width: width).height; |
| } |
| |
| @override |
| double computeMaxIntrinsicHeight(double width) { |
| if (child == null) |
| return 0.0; |
| return getIntrinsicDimensions(width: width).height; |
| } |
| |
| Size getIntrinsicDimensions({ |
| double width = double.infinity, |
| double height = double.infinity, |
| }) { |
| assert(child is RenderSector); |
| assert(child!.parentData is SectorParentData); |
| assert(width != null); |
| assert(height != null); |
| if (!width.isFinite && !height.isFinite) |
| return Size.zero; |
| final double maxChildDeltaRadius = math.max(0.0, math.min(width, height) / 2.0 - innerRadius); |
| final SectorDimensions childDimensions = child!.getIntrinsicDimensions(SectorConstraints(maxDeltaRadius: maxChildDeltaRadius), innerRadius); |
| final double dimension = (innerRadius + childDimensions.deltaRadius) * 2.0; |
| return Size.square(dimension); |
| } |
| |
| @override |
| void performLayout() { |
| if (child == null || (!constraints.hasBoundedWidth && !constraints.hasBoundedHeight)) { |
| size = constraints.constrain(Size.zero); |
| child?.layout(SectorConstraints(maxDeltaRadius: innerRadius), parentUsesSize: true); |
| return; |
| } |
| assert(child is RenderSector); |
| assert(child!.parentData is SectorParentData); |
| final double maxChildDeltaRadius = math.min(constraints.maxWidth, constraints.maxHeight) / 2.0 - innerRadius; |
| child!.parentData!.radius = innerRadius; |
| child!.parentData!.theta = 0.0; |
| child!.layout(SectorConstraints(maxDeltaRadius: maxChildDeltaRadius), parentUsesSize: true); |
| final double dimension = (innerRadius + child!.deltaRadius) * 2.0; |
| size = constraints.constrain(Size(dimension, dimension)); |
| } |
| |
| @override |
| void paint(PaintingContext context, Offset offset) { |
| super.paint(context, offset); |
| if (child != null) { |
| final Rect bounds = offset & size; |
| // we move the offset to the center of the circle for the RenderSectors |
| context.paintChild(child!, bounds.center); |
| } |
| } |
| |
| @override |
| bool hitTest(BoxHitTestResult result, { required Offset position }) { |
| if (child == null) |
| return false; |
| double x = position.dx; |
| double y = position.dy; |
| // translate to our origin |
| x -= size.width / 2.0; |
| y -= size.height / 2.0; |
| // convert to radius/theta |
| final double radius = math.sqrt(x * x + y * y); |
| final double theta = (math.atan2(x, -y) - math.pi / 2.0) % kTwoPi; |
| if (radius < innerRadius) |
| return false; |
| if (radius >= innerRadius + child!.deltaRadius) |
| return false; |
| if (theta > child!.deltaTheta) |
| return false; |
| child!.hitTest(SectorHitTestResult.wrap(result), radius: radius, theta: theta); |
| result.add(BoxHitTestEntry(this, position)); |
| return true; |
| } |
| } |
| |
| class RenderSolidColor extends RenderDecoratedSector { |
| RenderSolidColor( |
| this.backgroundColor, { |
| this.desiredDeltaRadius = double.infinity, |
| this.desiredDeltaTheta = kTwoPi, |
| }) : super(BoxDecoration(color: backgroundColor)); |
| |
| double desiredDeltaRadius; |
| double desiredDeltaTheta; |
| final Color backgroundColor; |
| |
| @override |
| SectorDimensions getIntrinsicDimensions(SectorConstraints constraints, double radius) { |
| return SectorDimensions.withConstraints(constraints, deltaTheta: desiredDeltaTheta); |
| } |
| |
| @override |
| void performLayout() { |
| deltaRadius = constraints.constrainDeltaRadius(desiredDeltaRadius); |
| deltaTheta = constraints.constrainDeltaTheta(desiredDeltaTheta); |
| } |
| |
| @override |
| void handleEvent(PointerEvent event, HitTestEntry entry) { |
| if (event is PointerDownEvent) { |
| decoration = const BoxDecoration(color: Color(0xFFFF0000)); |
| } else if (event is PointerUpEvent) { |
| decoration = BoxDecoration(color: backgroundColor); |
| } |
| } |
| } |
| |
| /// The result of performing a hit test on [RenderSector]s. |
| class SectorHitTestResult extends HitTestResult { |
| /// Creates an empty hit test result for hit testing on [RenderSector]. |
| SectorHitTestResult() : super(); |
| |
| /// Wraps `result` to create a [HitTestResult] that implements the |
| /// [SectorHitTestResult] protocol for hit testing on [RenderSector]s. |
| /// |
| /// This method is used by [RenderObject]s that adapt between the |
| /// [RenderSector]-world and the non-[RenderSector]-world to convert a (subtype of) |
| /// [HitTestResult] to a [SectorHitTestResult] for hit testing on [RenderSector]s. |
| /// |
| /// The [HitTestEntry]s added to the returned [SectorHitTestResult] are also |
| /// added to the wrapped `result` (both share the same underlying data |
| /// structure to store [HitTestEntry]s). |
| /// |
| /// See also: |
| /// |
| /// * [HitTestResult.wrap], which turns a [SectorHitTestResult] back into a |
| /// generic [HitTestResult]. |
| SectorHitTestResult.wrap(HitTestResult result) : super.wrap(result); |
| |
| // TODO(goderbauer): Add convenience methods to transform hit test positions |
| // once we have RenderSector implementations that move the origin of their |
| // children (e.g. RenderSectorTransform analogs to RenderTransform). |
| } |
| |
| /// A hit test entry used by [RenderSector]. |
| class SectorHitTestEntry extends HitTestEntry { |
| /// Creates a box hit test entry. |
| /// |
| /// The [radius] and [theta] argument must not be null. |
| SectorHitTestEntry(RenderSector target, { required this.radius, required this.theta }) |
| : assert(radius != null), |
| assert(theta != null), |
| super(target); |
| |
| @override |
| RenderSector get target => super.target as RenderSector; |
| |
| /// The radius component of the hit test position in the local coordinates of |
| /// [target]. |
| final double radius; |
| |
| /// The theta component of the hit test position in the local coordinates of |
| /// [target]. |
| final double theta; |
| } |