sector_layout.dart 22.6 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
Hixie's avatar
Hixie committed
2 3 4 5 6 7 8
// 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';

9
const double kTwoPi = 2 * math.pi;
Hixie's avatar
Hixie committed
10 11 12

class SectorConstraints extends Constraints {
  const SectorConstraints({
13 14 15
    this.minDeltaRadius = 0.0,
    this.maxDeltaRadius = double.infinity,
    this.minDeltaTheta = 0.0,
16
    this.maxDeltaTheta = kTwoPi,
17 18
  }) : assert(maxDeltaRadius >= minDeltaRadius),
       assert(maxDeltaTheta >= minDeltaTheta);
Hixie's avatar
Hixie committed
19

20
  const SectorConstraints.tight({ double deltaRadius = 0.0, double deltaTheta = 0.0 })
Hixie's avatar
Hixie committed
21 22 23 24 25 26 27 28 29 30 31
    : minDeltaRadius = deltaRadius,
      maxDeltaRadius = deltaRadius,
      minDeltaTheta = deltaTheta,
      maxDeltaTheta = deltaTheta;

  final double minDeltaRadius;
  final double maxDeltaRadius;
  final double minDeltaTheta;
  final double maxDeltaTheta;

  double constrainDeltaRadius(double deltaRadius) {
32
    return deltaRadius.clamp(minDeltaRadius, maxDeltaRadius);
Hixie's avatar
Hixie committed
33 34 35
  }

  double constrainDeltaTheta(double deltaTheta) {
36
    return deltaTheta.clamp(minDeltaTheta, maxDeltaTheta);
Hixie's avatar
Hixie committed
37 38
  }

39
  @override
Hixie's avatar
Hixie committed
40
  bool get isTight => minDeltaTheta >= maxDeltaTheta && minDeltaTheta >= maxDeltaTheta;
41

42
  @override
43
  bool get isNormalized => minDeltaRadius <= maxDeltaRadius && minDeltaTheta <= maxDeltaTheta;
44 45

  @override
46
  bool debugAssertIsValid({
47
    bool isAppliedConstraint = false,
48
    InformationCollector? informationCollector,
49
  }) {
50 51 52
    assert(isNormalized);
    return isNormalized;
  }
Hixie's avatar
Hixie committed
53 54 55
}

class SectorDimensions {
56
  const SectorDimensions({ this.deltaRadius = 0.0, this.deltaTheta = 0.0 });
Hixie's avatar
Hixie committed
57 58

  factory SectorDimensions.withConstraints(
59 60 61 62
    SectorConstraints constraints, {
    double deltaRadius = 0.0,
    double deltaTheta = 0.0,
  }) {
63
    return SectorDimensions(
Hixie's avatar
Hixie committed
64
      deltaRadius: constraints.constrainDeltaRadius(deltaRadius),
65
      deltaTheta: constraints.constrainDeltaTheta(deltaTheta),
Hixie's avatar
Hixie committed
66 67 68 69 70 71 72 73 74 75 76 77
    );
  }

  final double deltaRadius;
  final double deltaTheta;
}

class SectorParentData extends ParentData {
  double radius = 0.0;
  double theta = 0.0;
}

78 79 80 81 82 83 84 85 86 87 88
/// 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
89
///    Cartesian coordinate space.
Hixie's avatar
Hixie committed
90 91
abstract class RenderSector extends RenderObject {

92
  @override
Hixie's avatar
Hixie committed
93
  void setupParentData(RenderObject child) {
94
    if (child.parentData is! SectorParentData) {
95
      child.parentData = SectorParentData();
96
    }
Hixie's avatar
Hixie committed
97 98 99 100
  }

  // RenderSectors always use SectorParentData subclasses, as they need to be
  // able to read their position information for painting and hit testing.
101
  @override
102
  SectorParentData? get parentData => super.parentData as SectorParentData?;
Hixie's avatar
Hixie committed
103 104

  SectorDimensions getIntrinsicDimensions(SectorConstraints constraints, double radius) {
105
    return SectorDimensions.withConstraints(constraints);
Hixie's avatar
Hixie committed
106 107
  }

108
  @override
109
  SectorConstraints get constraints => super.constraints as SectorConstraints;
110 111

  @override
112
  void debugAssertDoesMeetConstraints() {
Hixie's avatar
Hixie committed
113 114
    assert(constraints != null);
    assert(deltaRadius != null);
115
    assert(deltaRadius < double.infinity);
Hixie's avatar
Hixie committed
116
    assert(deltaTheta != null);
117
    assert(deltaTheta < double.infinity);
118 119 120 121
    assert(constraints.minDeltaRadius <= deltaRadius);
    assert(deltaRadius <= math.max(constraints.minDeltaRadius, constraints.maxDeltaRadius));
    assert(constraints.minDeltaTheta <= deltaTheta);
    assert(deltaTheta <= math.max(constraints.minDeltaTheta, constraints.maxDeltaTheta));
Hixie's avatar
Hixie committed
122
  }
123 124

  @override
Hixie's avatar
Hixie committed
125
  void performResize() {
126
    // default behavior for subclasses that have sizedByParent = true
Hixie's avatar
Hixie committed
127 128 129
    deltaRadius = constraints.constrainDeltaRadius(0.0);
    deltaTheta = constraints.constrainDeltaTheta(0.0);
  }
130 131

  @override
Hixie's avatar
Hixie committed
132 133 134 135 136 137 138
  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);
  }

139
  @override
140
  Rect get paintBounds => Rect.fromLTWH(0.0, 0.0, 2.0 * deltaRadius, 2.0 * deltaRadius);
141 142

  @override
143
  Rect get semanticBounds => Rect.fromLTWH(-deltaRadius, -deltaRadius, 2.0 * deltaRadius, 2.0 * deltaRadius);
Hixie's avatar
Hixie committed
144

145 146
  bool hitTest(SectorHitTestResult result, { required double radius, required double theta }) {
    if (radius < parentData!.radius || radius >= parentData!.radius + deltaRadius ||
147
        theta < parentData!.theta || theta >= parentData!.theta + deltaTheta) {
Hixie's avatar
Hixie committed
148
      return false;
149
    }
Hixie's avatar
Hixie committed
150
    hitTestChildren(result, radius: radius, theta: theta);
151
    result.add(SectorHitTestEntry(this, radius: radius, theta: theta));
Hixie's avatar
Hixie committed
152 153
    return true;
  }
154
  void hitTestChildren(SectorHitTestResult result, { required double radius, required double theta }) { }
Hixie's avatar
Hixie committed
155

156 157
  late double deltaRadius;
  late double deltaTheta;
Hixie's avatar
Hixie committed
158 159 160 161
}

abstract class RenderDecoratedSector extends RenderSector {

162
  RenderDecoratedSector(BoxDecoration? decoration) : _decoration = decoration;
Hixie's avatar
Hixie committed
163

164 165 166
  BoxDecoration? _decoration;
  BoxDecoration? get decoration => _decoration;
  set decoration(BoxDecoration? value) {
167
    if (value == _decoration) {
Hixie's avatar
Hixie committed
168
      return;
169
    }
Hixie's avatar
Hixie committed
170 171 172 173 174
    _decoration = value;
    markNeedsPaint();
  }

  // offset must point to the center of the circle
175
  @override
Hixie's avatar
Hixie committed
176 177 178 179 180
  void paint(PaintingContext context, Offset offset) {
    assert(deltaRadius != null);
    assert(deltaTheta != null);
    assert(parentData is SectorParentData);

181
    if (_decoration == null) {
Hixie's avatar
Hixie committed
182
      return;
183
    }
Hixie's avatar
Hixie committed
184

185
    if (_decoration!.color != null) {
Adam Barth's avatar
Adam Barth committed
186
      final Canvas canvas = context.canvas;
187
      final Paint paint = Paint()..color = _decoration!.color!;
188
      final Path path = Path();
189
      final double outerRadius = parentData!.radius + deltaRadius;
190
      final Rect outerBounds = Rect.fromLTRB(offset.dx-outerRadius, offset.dy-outerRadius, offset.dx+outerRadius, offset.dy+outerRadius);
191 192
      path.arcTo(outerBounds, parentData!.theta, deltaTheta, true);
      final double innerRadius = parentData!.radius;
193
      final Rect innerBounds = Rect.fromLTRB(offset.dx-innerRadius, offset.dy-innerRadius, offset.dx+innerRadius, offset.dy+innerRadius);
194
      path.arcTo(innerBounds, parentData!.theta + deltaTheta, -deltaTheta, false);
Hixie's avatar
Hixie committed
195 196 197 198 199 200 201 202 203 204
      path.close();
      canvas.drawPath(path, paint);
    }
  }

}

class SectorChildListParentData extends SectorParentData with ContainerParentDataMixin<RenderSector> { }

class RenderSectorWithChildren extends RenderDecoratedSector with ContainerRenderObjectMixin<RenderSector, SectorChildListParentData> {
205
  RenderSectorWithChildren(super.decoration);
Hixie's avatar
Hixie committed
206

207
  @override
208 209
  void hitTestChildren(SectorHitTestResult result, { required double radius, required double theta }) {
    RenderSector? child = lastChild;
Hixie's avatar
Hixie committed
210
    while (child != null) {
211
      if (child.hitTest(result, radius: radius, theta: theta)) {
Hixie's avatar
Hixie committed
212
        return;
213
      }
214
      final SectorChildListParentData childParentData = child.parentData! as SectorChildListParentData;
Hixie's avatar
Hixie committed
215 216 217 218
      child = childParentData.previousSibling;
    }
  }

219
  @override
Hixie's avatar
Hixie committed
220
  void visitChildren(RenderObjectVisitor visitor) {
221
    RenderSector? child = lastChild;
Hixie's avatar
Hixie committed
222 223
    while (child != null) {
      visitor(child);
224
      final SectorChildListParentData childParentData = child.parentData! as SectorChildListParentData;
Hixie's avatar
Hixie committed
225 226 227 228 229 230 231 232 233
      child = childParentData.previousSibling;
    }
  }
}

class RenderSectorRing extends RenderSectorWithChildren {
  // lays out RenderSector children in a ring

  RenderSectorRing({
234
    BoxDecoration? decoration,
235
    double deltaRadius = double.infinity,
236
    double padding = 0.0,
237 238 239 240
  }) : _padding = padding,
       assert(deltaRadius >= 0.0),
       _desiredDeltaRadius = deltaRadius,
       super(decoration);
Hixie's avatar
Hixie committed
241 242 243

  double _desiredDeltaRadius;
  double get desiredDeltaRadius => _desiredDeltaRadius;
244
  set desiredDeltaRadius(double value) {
Hixie's avatar
Hixie committed
245
    assert(value != null);
246
    assert(value >= 0);
Hixie's avatar
Hixie committed
247 248 249 250 251 252 253 254
    if (_desiredDeltaRadius != value) {
      _desiredDeltaRadius = value;
      markNeedsLayout();
    }
  }

  double _padding;
  double get padding => _padding;
255
  set padding(double value) {
Hixie's avatar
Hixie committed
256 257 258 259 260 261 262 263
    // TODO(ianh): avoid code duplication
    assert(value != null);
    if (_padding != value) {
      _padding = value;
      markNeedsLayout();
    }
  }

264
  @override
Hixie's avatar
Hixie committed
265 266
  void setupParentData(RenderObject child) {
    // TODO(ianh): avoid code duplication
267
    if (child.parentData is! SectorChildListParentData) {
268
      child.parentData = SectorChildListParentData();
269
    }
Hixie's avatar
Hixie committed
270 271
  }

272
  @override
Hixie's avatar
Hixie committed
273
  SectorDimensions getIntrinsicDimensions(SectorConstraints constraints, double radius) {
274
    final double outerDeltaRadius = constraints.constrainDeltaRadius(desiredDeltaRadius);
275
    final double innerDeltaRadius = math.max(0.0, outerDeltaRadius - padding * 2.0);
276 277
    final double childRadius = radius + padding;
    final double paddingTheta = math.atan(padding / (radius + outerDeltaRadius));
Hixie's avatar
Hixie committed
278
    double innerTheta = paddingTheta; // increments with each child
279
    double remainingDeltaTheta = math.max(0.0, constraints.maxDeltaTheta - (innerTheta + paddingTheta));
280
    RenderSector? child = firstChild;
Hixie's avatar
Hixie committed
281
    while (child != null) {
282
      final SectorConstraints innerConstraints = SectorConstraints(
Hixie's avatar
Hixie committed
283
        maxDeltaRadius: innerDeltaRadius,
284
        maxDeltaTheta: remainingDeltaTheta,
Hixie's avatar
Hixie committed
285
      );
286
      final SectorDimensions childDimensions = child.getIntrinsicDimensions(innerConstraints, childRadius);
Hixie's avatar
Hixie committed
287 288
      innerTheta += childDimensions.deltaTheta;
      remainingDeltaTheta -= childDimensions.deltaTheta;
289
      final SectorChildListParentData childParentData = child.parentData! as SectorChildListParentData;
Hixie's avatar
Hixie committed
290 291 292 293 294 295
      child = childParentData.nextSibling;
      if (child != null) {
        innerTheta += paddingTheta;
        remainingDeltaTheta -= paddingTheta;
      }
    }
296 297 298 299 300
    return SectorDimensions.withConstraints(
      constraints,
      deltaRadius: outerDeltaRadius,
      deltaTheta: innerTheta,
    );
Hixie's avatar
Hixie committed
301 302
  }

303
  @override
Hixie's avatar
Hixie committed
304
  void performLayout() {
305
    assert(parentData is SectorParentData);
Hixie's avatar
Hixie committed
306
    deltaRadius = constraints.constrainDeltaRadius(desiredDeltaRadius);
307
    assert(deltaRadius < double.infinity);
308
    final double innerDeltaRadius = deltaRadius - padding * 2.0;
309 310
    final double childRadius = parentData!.radius + padding;
    final double paddingTheta = math.atan(padding / (parentData!.radius + deltaRadius));
Hixie's avatar
Hixie committed
311 312
    double innerTheta = paddingTheta; // increments with each child
    double remainingDeltaTheta = constraints.maxDeltaTheta - (innerTheta + paddingTheta);
313
    RenderSector? child = firstChild;
Hixie's avatar
Hixie committed
314
    while (child != null) {
315
      final SectorConstraints innerConstraints = SectorConstraints(
Hixie's avatar
Hixie committed
316
        maxDeltaRadius: innerDeltaRadius,
317
        maxDeltaTheta: remainingDeltaTheta,
Hixie's avatar
Hixie committed
318 319
      );
      assert(child.parentData is SectorParentData);
320 321
      child.parentData!.theta = innerTheta;
      child.parentData!.radius = childRadius;
Hixie's avatar
Hixie committed
322 323 324
      child.layout(innerConstraints, parentUsesSize: true);
      innerTheta += child.deltaTheta;
      remainingDeltaTheta -= child.deltaTheta;
325
      final SectorChildListParentData childParentData = child.parentData! as SectorChildListParentData;
Hixie's avatar
Hixie committed
326 327 328 329 330 331 332 333 334 335 336
      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
337
  @override
Hixie's avatar
Hixie committed
338 339 340
  void paint(PaintingContext context, Offset offset) {
    // TODO(ianh): avoid code duplication
    super.paint(context, offset);
341
    RenderSector? child = firstChild;
Hixie's avatar
Hixie committed
342
    while (child != null) {
Adam Barth's avatar
Adam Barth committed
343
      context.paintChild(child, offset);
344
      final SectorChildListParentData childParentData = child.parentData! as SectorChildListParentData;
Hixie's avatar
Hixie committed
345 346 347 348 349 350 351 352 353 354
      child = childParentData.nextSibling;
    }
  }

}

class RenderSectorSlice extends RenderSectorWithChildren {
  // lays out RenderSector children in a stack

  RenderSectorSlice({
355
    BoxDecoration? decoration,
356
    double deltaTheta = kTwoPi,
357
    double padding = 0.0,
Hixie's avatar
Hixie committed
358 359 360 361
  }) : _padding = padding, _desiredDeltaTheta = deltaTheta, super(decoration);

  double _desiredDeltaTheta;
  double get desiredDeltaTheta => _desiredDeltaTheta;
362
  set desiredDeltaTheta(double value) {
Hixie's avatar
Hixie committed
363 364 365 366 367 368 369 370 371
    assert(value != null);
    if (_desiredDeltaTheta != value) {
      _desiredDeltaTheta = value;
      markNeedsLayout();
    }
  }

  double _padding;
  double get padding => _padding;
372
  set padding(double value) {
Hixie's avatar
Hixie committed
373 374 375 376 377 378 379 380
    // TODO(ianh): avoid code duplication
    assert(value != null);
    if (_padding != value) {
      _padding = value;
      markNeedsLayout();
    }
  }

381
  @override
Hixie's avatar
Hixie committed
382 383
  void setupParentData(RenderObject child) {
    // TODO(ianh): avoid code duplication
384
    if (child.parentData is! SectorChildListParentData) {
385
      child.parentData = SectorChildListParentData();
386
    }
Hixie's avatar
Hixie committed
387 388
  }

389
  @override
Hixie's avatar
Hixie committed
390
  SectorDimensions getIntrinsicDimensions(SectorConstraints constraints, double radius) {
391
    assert(parentData is SectorParentData);
392
    final double paddingTheta = math.atan(padding / parentData!.radius);
393 394
    final double outerDeltaTheta = constraints.constrainDeltaTheta(desiredDeltaTheta);
    final double innerDeltaTheta = outerDeltaTheta - paddingTheta * 2.0;
395
    double childRadius = parentData!.radius + padding;
Hixie's avatar
Hixie committed
396
    double remainingDeltaRadius = constraints.maxDeltaRadius - (padding * 2.0);
397
    RenderSector? child = firstChild;
Hixie's avatar
Hixie committed
398
    while (child != null) {
399
      final SectorConstraints innerConstraints = SectorConstraints(
Hixie's avatar
Hixie committed
400
        maxDeltaRadius: remainingDeltaRadius,
401
        maxDeltaTheta: innerDeltaTheta,
Hixie's avatar
Hixie committed
402
      );
403
      final SectorDimensions childDimensions = child.getIntrinsicDimensions(innerConstraints, childRadius);
Hixie's avatar
Hixie committed
404 405
      childRadius += childDimensions.deltaRadius;
      remainingDeltaRadius -= childDimensions.deltaRadius;
406
      final SectorChildListParentData childParentData = child.parentData! as SectorChildListParentData;
Hixie's avatar
Hixie committed
407 408 409 410
      child = childParentData.nextSibling;
      childRadius += padding;
      remainingDeltaRadius -= padding;
    }
411 412 413 414 415
    return SectorDimensions.withConstraints(
      constraints,
      deltaRadius: childRadius - parentData!.radius,
      deltaTheta: outerDeltaTheta,
    );
Hixie's avatar
Hixie committed
416 417
  }

418
  @override
Hixie's avatar
Hixie committed
419
  void performLayout() {
420
    assert(parentData is SectorParentData);
Hixie's avatar
Hixie committed
421 422
    deltaTheta = constraints.constrainDeltaTheta(desiredDeltaTheta);
    assert(deltaTheta <= kTwoPi);
423 424
    final double paddingTheta = math.atan(padding / parentData!.radius);
    final double innerTheta = parentData!.theta + paddingTheta;
425
    final double innerDeltaTheta = deltaTheta - paddingTheta * 2.0;
426
    double childRadius = parentData!.radius + padding;
Hixie's avatar
Hixie committed
427
    double remainingDeltaRadius = constraints.maxDeltaRadius - (padding * 2.0);
428
    RenderSector? child = firstChild;
Hixie's avatar
Hixie committed
429
    while (child != null) {
430
      final SectorConstraints innerConstraints = SectorConstraints(
Hixie's avatar
Hixie committed
431
        maxDeltaRadius: remainingDeltaRadius,
432
        maxDeltaTheta: innerDeltaTheta,
Hixie's avatar
Hixie committed
433
      );
434 435
      child.parentData!.theta = innerTheta;
      child.parentData!.radius = childRadius;
Hixie's avatar
Hixie committed
436 437 438
      child.layout(innerConstraints, parentUsesSize: true);
      childRadius += child.deltaRadius;
      remainingDeltaRadius -= child.deltaRadius;
439
      final SectorChildListParentData childParentData = child.parentData! as SectorChildListParentData;
Hixie's avatar
Hixie committed
440 441 442 443
      child = childParentData.nextSibling;
      childRadius += padding;
      remainingDeltaRadius -= padding;
    }
444
    deltaRadius = childRadius - parentData!.radius;
Hixie's avatar
Hixie committed
445 446 447 448
  }

  // offset must point to the center of our circle
  // each sector then knows how to paint itself at its location
449
  @override
Hixie's avatar
Hixie committed
450 451 452
  void paint(PaintingContext context, Offset offset) {
    // TODO(ianh): avoid code duplication
    super.paint(context, offset);
453
    RenderSector? child = firstChild;
Hixie's avatar
Hixie committed
454 455
    while (child != null) {
      assert(child.parentData is SectorChildListParentData);
Adam Barth's avatar
Adam Barth committed
456
      context.paintChild(child, offset);
457
      final SectorChildListParentData childParentData = child.parentData! as SectorChildListParentData;
Hixie's avatar
Hixie committed
458 459 460 461 462 463 464
      child = childParentData.nextSibling;
    }
  }

}

class RenderBoxToRenderSectorAdapter extends RenderBox with RenderObjectWithChildMixin<RenderSector> {
465
  RenderBoxToRenderSectorAdapter({ double innerRadius = 0.0, RenderSector? child })
466
    : _innerRadius = innerRadius {
Hixie's avatar
Hixie committed
467 468 469 470 471
    this.child = child;
  }

  double _innerRadius;
  double get innerRadius => _innerRadius;
472
  set innerRadius(double value) {
Hixie's avatar
Hixie committed
473 474 475 476
    _innerRadius = value;
    markNeedsLayout();
  }

477
  @override
Hixie's avatar
Hixie committed
478
  void setupParentData(RenderObject child) {
479
    if (child.parentData is! SectorParentData) {
480
      child.parentData = SectorParentData();
481
    }
Hixie's avatar
Hixie committed
482 483
  }

484
  @override
485
  double computeMinIntrinsicWidth(double height) {
486
    if (child == null) {
487
      return 0.0;
488
    }
489
    return getIntrinsicDimensions(height: height).width;
Hixie's avatar
Hixie committed
490 491
  }

492
  @override
493
  double computeMaxIntrinsicWidth(double height) {
494
    if (child == null) {
495
      return 0.0;
496
    }
497
    return getIntrinsicDimensions(height: height).width;
Hixie's avatar
Hixie committed
498 499
  }

500
  @override
501
  double computeMinIntrinsicHeight(double width) {
502
    if (child == null) {
503
      return 0.0;
504
    }
505
    return getIntrinsicDimensions(width: width).height;
Hixie's avatar
Hixie committed
506 507
  }

508
  @override
509
  double computeMaxIntrinsicHeight(double width) {
510
    if (child == null) {
511
      return 0.0;
512
    }
513
    return getIntrinsicDimensions(width: width).height;
Hixie's avatar
Hixie committed
514 515
  }

516
  Size getIntrinsicDimensions({
517
    double width = double.infinity,
518
    double height = double.infinity,
519
  }) {
Hixie's avatar
Hixie committed
520
    assert(child is RenderSector);
521
    assert(child!.parentData is SectorParentData);
522 523
    assert(width != null);
    assert(height != null);
524
    if (!width.isFinite && !height.isFinite) {
525
      return Size.zero;
526
    }
527
    final double maxChildDeltaRadius = math.max(0.0, math.min(width, height) / 2.0 - innerRadius);
528
    final SectorDimensions childDimensions = child!.getIntrinsicDimensions(SectorConstraints(maxDeltaRadius: maxChildDeltaRadius), innerRadius);
529
    final double dimension = (innerRadius + childDimensions.deltaRadius) * 2.0;
530
    return Size.square(dimension);
Hixie's avatar
Hixie committed
531 532
  }

533
  @override
Hixie's avatar
Hixie committed
534
  void performLayout() {
535
    if (child == null || (!constraints.hasBoundedWidth && !constraints.hasBoundedHeight)) {
Hixie's avatar
Hixie committed
536
      size = constraints.constrain(Size.zero);
537
      child?.layout(SectorConstraints(maxDeltaRadius: innerRadius), parentUsesSize: true);
538
      return;
Hixie's avatar
Hixie committed
539
    }
540
    assert(child is RenderSector);
541
    assert(child!.parentData is SectorParentData);
542
    final double maxChildDeltaRadius = math.min(constraints.maxWidth, constraints.maxHeight) / 2.0 - innerRadius;
543 544 545 546
    child!.parentData!.radius = innerRadius;
    child!.parentData!.theta = 0.0;
    child!.layout(SectorConstraints(maxDeltaRadius: maxChildDeltaRadius), parentUsesSize: true);
    final double dimension = (innerRadius + child!.deltaRadius) * 2.0;
547
    size = constraints.constrain(Size(dimension, dimension));
Hixie's avatar
Hixie committed
548 549
  }

550
  @override
Hixie's avatar
Hixie committed
551 552 553
  void paint(PaintingContext context, Offset offset) {
    super.paint(context, offset);
    if (child != null) {
554
      final Rect bounds = offset & size;
Hixie's avatar
Hixie committed
555
      // we move the offset to the center of the circle for the RenderSectors
556
      context.paintChild(child!, bounds.center);
Hixie's avatar
Hixie committed
557 558 559
    }
  }

560
  @override
561
  bool hitTest(BoxHitTestResult result, { required Offset position }) {
562
    if (child == null) {
Hixie's avatar
Hixie committed
563
      return false;
564
    }
565 566
    double x = position.dx;
    double y = position.dy;
Hixie's avatar
Hixie committed
567
    // translate to our origin
568 569
    x -= size.width / 2.0;
    y -= size.height / 2.0;
Hixie's avatar
Hixie committed
570
    // convert to radius/theta
571
    final double radius = math.sqrt(x * x + y * y);
572
    final double theta = (math.atan2(x, -y) - math.pi / 2.0) % kTwoPi;
573
    if (radius < innerRadius) {
Hixie's avatar
Hixie committed
574
      return false;
575 576
    }
    if (radius >= innerRadius + child!.deltaRadius) {
Hixie's avatar
Hixie committed
577
      return false;
578 579
    }
    if (theta > child!.deltaTheta) {
Hixie's avatar
Hixie committed
580
      return false;
581
    }
582
    child!.hitTest(SectorHitTestResult.wrap(result), radius: radius, theta: theta);
583
    result.add(BoxHitTestEntry(this, position));
Hixie's avatar
Hixie committed
584 585 586 587 588
    return true;
  }
}

class RenderSolidColor extends RenderDecoratedSector {
589 590
  RenderSolidColor(
    this.backgroundColor, {
591
    this.desiredDeltaRadius = double.infinity,
592
    this.desiredDeltaTheta = kTwoPi,
593
  }) : super(BoxDecoration(color: backgroundColor));
Hixie's avatar
Hixie committed
594 595 596 597 598

  double desiredDeltaRadius;
  double desiredDeltaTheta;
  final Color backgroundColor;

599
  @override
Hixie's avatar
Hixie committed
600
  SectorDimensions getIntrinsicDimensions(SectorConstraints constraints, double radius) {
601
    return SectorDimensions.withConstraints(constraints, deltaTheta: desiredDeltaTheta);
Hixie's avatar
Hixie committed
602 603
  }

604
  @override
Hixie's avatar
Hixie committed
605 606 607 608 609
  void performLayout() {
    deltaRadius = constraints.constrainDeltaRadius(desiredDeltaRadius);
    deltaTheta = constraints.constrainDeltaTheta(desiredDeltaTheta);
  }

610
  @override
Ian Hickson's avatar
Ian Hickson committed
611 612
  void handleEvent(PointerEvent event, HitTestEntry entry) {
    if (event is PointerDownEvent) {
613
      decoration = const BoxDecoration(color: Color(0xFFFF0000));
Ian Hickson's avatar
Ian Hickson committed
614
    } else if (event is PointerUpEvent) {
615
      decoration = BoxDecoration(color: backgroundColor);
Ian Hickson's avatar
Ian Hickson committed
616
    }
Hixie's avatar
Hixie committed
617 618
  }
}
619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639

/// 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].
640
  SectorHitTestResult.wrap(super.result) : super.wrap();
641 642 643

  // TODO(goderbauer): Add convenience methods to transform hit test positions
  //    once we have RenderSector implementations that move the origin of their
644
  //    children (e.g. RenderSectorTransform analogs to RenderTransform).
645 646 647 648 649 650 651
}

/// 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.
652
  SectorHitTestEntry(RenderSector super.target, { required this.radius,  required this.theta })
653
      : assert(radius != null),
654
        assert(theta != null);
655 656

  @override
657
  RenderSector get target => super.target as RenderSector;
658 659 660 661 662 663 664 665 666

  /// 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;
}