mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
Port first sector demo to fn3
Also, fix warnings in rendering/sector_layout.dart Also, fix hit testing in rendering/sector_layout.dart Also, add WidgetToRenderBoxAdapter Also, make the rendering library debugging tools more resilient to dumping stuff before layout is complete.
This commit is contained in:
parent
8f65e0cb34
commit
1cf1cf9c64
@ -1,4 +1,4 @@
|
|||||||
name: raw
|
name: sky_raw_examples
|
||||||
dependencies:
|
dependencies:
|
||||||
flutter: ">=0.0.3 <0.1.0"
|
flutter: ">=0.0.3 <0.1.0"
|
||||||
sky_tools: any
|
sky_tools: any
|
||||||
|
527
examples/rendering/lib/sector_layout.dart
Normal file
527
examples/rendering/lib/sector_layout.dart
Normal file
@ -0,0 +1,527 @@
|
|||||||
|
// Copyright 2015 The Chromium 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 'dart:ui' as ui;
|
||||||
|
|
||||||
|
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
|
||||||
|
});
|
||||||
|
|
||||||
|
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 clamp(min: minDeltaRadius, max: maxDeltaRadius, value: deltaRadius);
|
||||||
|
}
|
||||||
|
|
||||||
|
double constrainDeltaTheta(double deltaTheta) {
|
||||||
|
return clamp(min: minDeltaTheta, max: maxDeltaTheta, value: deltaTheta);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool get isTight => minDeltaTheta >= maxDeltaTheta && minDeltaTheta >= maxDeltaTheta;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 new 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class RenderSector extends RenderObject {
|
||||||
|
|
||||||
|
void setupParentData(RenderObject child) {
|
||||||
|
if (child.parentData is! SectorParentData)
|
||||||
|
child.parentData = new SectorParentData();
|
||||||
|
}
|
||||||
|
|
||||||
|
// RenderSectors always use SectorParentData subclasses, as they need to be
|
||||||
|
// able to read their position information for painting and hit testing.
|
||||||
|
SectorParentData get parentData => super.parentData;
|
||||||
|
|
||||||
|
SectorDimensions getIntrinsicDimensions(SectorConstraints constraints, double radius) {
|
||||||
|
return new SectorDimensions.withConstraints(constraints);
|
||||||
|
}
|
||||||
|
|
||||||
|
SectorConstraints get constraints => super.constraints;
|
||||||
|
bool debugDoesMeetConstraints() {
|
||||||
|
assert(constraints != null);
|
||||||
|
assert(deltaRadius != null);
|
||||||
|
assert(deltaRadius < double.INFINITY);
|
||||||
|
assert(deltaTheta != null);
|
||||||
|
assert(deltaTheta < double.INFINITY);
|
||||||
|
return constraints.minDeltaRadius <= deltaRadius &&
|
||||||
|
deltaRadius <= math.max(constraints.minDeltaRadius, constraints.maxDeltaRadius) &&
|
||||||
|
constraints.minDeltaTheta <= deltaTheta &&
|
||||||
|
deltaTheta <= math.max(constraints.minDeltaTheta, constraints.maxDeltaTheta);
|
||||||
|
}
|
||||||
|
void performResize() {
|
||||||
|
// default behaviour for subclasses that have sizedByParent = true
|
||||||
|
deltaRadius = constraints.constrainDeltaRadius(0.0);
|
||||||
|
deltaTheta = constraints.constrainDeltaTheta(0.0);
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
Rect get paintBounds => new Rect.fromLTWH(0.0, 0.0, 2.0 * deltaRadius, 2.0 * deltaRadius);
|
||||||
|
|
||||||
|
bool hitTest(HitTestResult result, { double radius, 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(new HitTestEntry(this));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
void hitTestChildren(HitTestResult result, { double radius, double theta }) { }
|
||||||
|
|
||||||
|
double deltaRadius;
|
||||||
|
double deltaTheta;
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class RenderDecoratedSector extends RenderSector {
|
||||||
|
|
||||||
|
RenderDecoratedSector(BoxDecoration decoration) : _decoration = decoration;
|
||||||
|
|
||||||
|
BoxDecoration _decoration;
|
||||||
|
BoxDecoration get decoration => _decoration;
|
||||||
|
void set decoration (BoxDecoration value) {
|
||||||
|
if (value == _decoration)
|
||||||
|
return;
|
||||||
|
_decoration = value;
|
||||||
|
markNeedsPaint();
|
||||||
|
}
|
||||||
|
|
||||||
|
// offset must point to the center of the circle
|
||||||
|
void paint(PaintingContext context, Offset offset) {
|
||||||
|
assert(deltaRadius != null);
|
||||||
|
assert(deltaTheta != null);
|
||||||
|
assert(parentData is SectorParentData);
|
||||||
|
|
||||||
|
if (_decoration == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (_decoration.backgroundColor != null) {
|
||||||
|
final PaintingCanvas canvas = context.canvas;
|
||||||
|
Paint paint = new Paint()..color = _decoration.backgroundColor;
|
||||||
|
Path path = new Path();
|
||||||
|
double outerRadius = (parentData.radius + deltaRadius);
|
||||||
|
Rect outerBounds = new Rect.fromLTRB(offset.dx-outerRadius, offset.dy-outerRadius, offset.dx+outerRadius, offset.dy+outerRadius);
|
||||||
|
path.arcTo(outerBounds, parentData.theta, deltaTheta, true);
|
||||||
|
double innerRadius = parentData.radius;
|
||||||
|
Rect innerBounds = new 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);
|
||||||
|
|
||||||
|
void hitTestChildren(HitTestResult result, { double radius, double theta }) {
|
||||||
|
RenderSector child = lastChild;
|
||||||
|
while (child != null) {
|
||||||
|
if (child.hitTest(result, radius: radius, theta: theta))
|
||||||
|
return;
|
||||||
|
final SectorChildListParentData childParentData = child.parentData;
|
||||||
|
child = childParentData.previousSibling;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void visitChildren(RenderObjectVisitor visitor) {
|
||||||
|
RenderSector child = lastChild;
|
||||||
|
while (child != null) {
|
||||||
|
visitor(child);
|
||||||
|
final SectorChildListParentData childParentData = child.parentData;
|
||||||
|
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, _desiredDeltaRadius = deltaRadius, super(decoration);
|
||||||
|
|
||||||
|
double _desiredDeltaRadius;
|
||||||
|
double get desiredDeltaRadius => _desiredDeltaRadius;
|
||||||
|
void set desiredDeltaRadius(double value) {
|
||||||
|
assert(value != null);
|
||||||
|
if (_desiredDeltaRadius != value) {
|
||||||
|
_desiredDeltaRadius = value;
|
||||||
|
markNeedsLayout();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
double _padding;
|
||||||
|
double get padding => _padding;
|
||||||
|
void set padding(double value) {
|
||||||
|
// TODO(ianh): avoid code duplication
|
||||||
|
assert(value != null);
|
||||||
|
if (_padding != value) {
|
||||||
|
_padding = value;
|
||||||
|
markNeedsLayout();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void setupParentData(RenderObject child) {
|
||||||
|
// TODO(ianh): avoid code duplication
|
||||||
|
if (child.parentData is! SectorChildListParentData)
|
||||||
|
child.parentData = new SectorChildListParentData();
|
||||||
|
}
|
||||||
|
|
||||||
|
SectorDimensions getIntrinsicDimensions(SectorConstraints constraints, double radius) {
|
||||||
|
double outerDeltaRadius = constraints.constrainDeltaRadius(desiredDeltaRadius);
|
||||||
|
double innerDeltaRadius = outerDeltaRadius - padding * 2.0;
|
||||||
|
double childRadius = radius + padding;
|
||||||
|
double paddingTheta = math.atan(padding / (radius + outerDeltaRadius));
|
||||||
|
double innerTheta = paddingTheta; // increments with each child
|
||||||
|
double remainingDeltaTheta = constraints.maxDeltaTheta - (innerTheta + paddingTheta);
|
||||||
|
RenderSector child = firstChild;
|
||||||
|
while (child != null) {
|
||||||
|
SectorConstraints innerConstraints = new SectorConstraints(
|
||||||
|
maxDeltaRadius: innerDeltaRadius,
|
||||||
|
maxDeltaTheta: remainingDeltaTheta
|
||||||
|
);
|
||||||
|
SectorDimensions childDimensions = child.getIntrinsicDimensions(innerConstraints, childRadius);
|
||||||
|
innerTheta += childDimensions.deltaTheta;
|
||||||
|
remainingDeltaTheta -= childDimensions.deltaTheta;
|
||||||
|
final SectorChildListParentData childParentData = child.parentData;
|
||||||
|
child = childParentData.nextSibling;
|
||||||
|
if (child != null) {
|
||||||
|
innerTheta += paddingTheta;
|
||||||
|
remainingDeltaTheta -= paddingTheta;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new SectorDimensions.withConstraints(constraints,
|
||||||
|
deltaRadius: outerDeltaRadius,
|
||||||
|
deltaTheta: innerTheta);
|
||||||
|
}
|
||||||
|
|
||||||
|
void performLayout() {
|
||||||
|
assert(this.parentData is SectorParentData);
|
||||||
|
deltaRadius = constraints.constrainDeltaRadius(desiredDeltaRadius);
|
||||||
|
assert(deltaRadius < double.INFINITY);
|
||||||
|
double innerDeltaRadius = deltaRadius - padding * 2.0;
|
||||||
|
double childRadius = this.parentData.radius + padding;
|
||||||
|
double paddingTheta = math.atan(padding / (this.parentData.radius + deltaRadius));
|
||||||
|
double innerTheta = paddingTheta; // increments with each child
|
||||||
|
double remainingDeltaTheta = constraints.maxDeltaTheta - (innerTheta + paddingTheta);
|
||||||
|
RenderSector child = firstChild;
|
||||||
|
while (child != null) {
|
||||||
|
SectorConstraints innerConstraints = new 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;
|
||||||
|
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
|
||||||
|
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.toPoint());
|
||||||
|
final SectorChildListParentData childParentData = child.parentData;
|
||||||
|
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;
|
||||||
|
void set desiredDeltaTheta(double value) {
|
||||||
|
assert(value != null);
|
||||||
|
if (_desiredDeltaTheta != value) {
|
||||||
|
_desiredDeltaTheta = value;
|
||||||
|
markNeedsLayout();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
double _padding;
|
||||||
|
double get padding => _padding;
|
||||||
|
void set padding(double value) {
|
||||||
|
// TODO(ianh): avoid code duplication
|
||||||
|
assert(value != null);
|
||||||
|
if (_padding != value) {
|
||||||
|
_padding = value;
|
||||||
|
markNeedsLayout();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void setupParentData(RenderObject child) {
|
||||||
|
// TODO(ianh): avoid code duplication
|
||||||
|
if (child.parentData is! SectorChildListParentData)
|
||||||
|
child.parentData = new SectorChildListParentData();
|
||||||
|
}
|
||||||
|
|
||||||
|
SectorDimensions getIntrinsicDimensions(SectorConstraints constraints, double radius) {
|
||||||
|
assert(this.parentData is SectorParentData);
|
||||||
|
double paddingTheta = math.atan(padding / this.parentData.radius);
|
||||||
|
double outerDeltaTheta = constraints.constrainDeltaTheta(desiredDeltaTheta);
|
||||||
|
double innerDeltaTheta = outerDeltaTheta - paddingTheta * 2.0;
|
||||||
|
double childRadius = this.parentData.radius + padding;
|
||||||
|
double remainingDeltaRadius = constraints.maxDeltaRadius - (padding * 2.0);
|
||||||
|
RenderSector child = firstChild;
|
||||||
|
while (child != null) {
|
||||||
|
SectorConstraints innerConstraints = new SectorConstraints(
|
||||||
|
maxDeltaRadius: remainingDeltaRadius,
|
||||||
|
maxDeltaTheta: innerDeltaTheta
|
||||||
|
);
|
||||||
|
SectorDimensions childDimensions = child.getIntrinsicDimensions(innerConstraints, childRadius);
|
||||||
|
childRadius += childDimensions.deltaRadius;
|
||||||
|
remainingDeltaRadius -= childDimensions.deltaRadius;
|
||||||
|
final SectorChildListParentData childParentData = child.parentData;
|
||||||
|
child = childParentData.nextSibling;
|
||||||
|
childRadius += padding;
|
||||||
|
remainingDeltaRadius -= padding;
|
||||||
|
}
|
||||||
|
return new SectorDimensions.withConstraints(constraints,
|
||||||
|
deltaRadius: childRadius - this.parentData.radius,
|
||||||
|
deltaTheta: outerDeltaTheta);
|
||||||
|
}
|
||||||
|
|
||||||
|
void performLayout() {
|
||||||
|
assert(this.parentData is SectorParentData);
|
||||||
|
deltaTheta = constraints.constrainDeltaTheta(desiredDeltaTheta);
|
||||||
|
assert(deltaTheta <= kTwoPi);
|
||||||
|
double paddingTheta = math.atan(padding / this.parentData.radius);
|
||||||
|
double innerTheta = this.parentData.theta + paddingTheta;
|
||||||
|
double innerDeltaTheta = deltaTheta - paddingTheta * 2.0;
|
||||||
|
double childRadius = this.parentData.radius + padding;
|
||||||
|
double remainingDeltaRadius = constraints.maxDeltaRadius - (padding * 2.0);
|
||||||
|
RenderSector child = firstChild;
|
||||||
|
while (child != null) {
|
||||||
|
SectorConstraints innerConstraints = new 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;
|
||||||
|
child = childParentData.nextSibling;
|
||||||
|
childRadius += padding;
|
||||||
|
remainingDeltaRadius -= padding;
|
||||||
|
}
|
||||||
|
deltaRadius = childRadius - this.parentData.radius;
|
||||||
|
}
|
||||||
|
|
||||||
|
// offset must point to the center of our circle
|
||||||
|
// each sector then knows how to paint itself at its location
|
||||||
|
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.toPoint());
|
||||||
|
final SectorChildListParentData childParentData = child.parentData;
|
||||||
|
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;
|
||||||
|
void set innerRadius(double value) {
|
||||||
|
_innerRadius = value;
|
||||||
|
markNeedsLayout();
|
||||||
|
}
|
||||||
|
|
||||||
|
void setupParentData(RenderObject child) {
|
||||||
|
if (child.parentData is! SectorParentData)
|
||||||
|
child.parentData = new SectorParentData();
|
||||||
|
}
|
||||||
|
|
||||||
|
double getMinIntrinsicWidth(BoxConstraints constraints) {
|
||||||
|
if (child == null)
|
||||||
|
return super.getMinIntrinsicWidth(constraints);
|
||||||
|
return getIntrinsicDimensions(constraints).width;
|
||||||
|
}
|
||||||
|
|
||||||
|
double getMaxIntrinsicWidth(BoxConstraints constraints) {
|
||||||
|
if (child == null)
|
||||||
|
return super.getMaxIntrinsicWidth(constraints);
|
||||||
|
return getIntrinsicDimensions(constraints).width;
|
||||||
|
}
|
||||||
|
|
||||||
|
double getMinIntrinsicHeight(BoxConstraints constraints) {
|
||||||
|
if (child == null)
|
||||||
|
return super.getMinIntrinsicHeight(constraints);
|
||||||
|
return getIntrinsicDimensions(constraints).height;
|
||||||
|
}
|
||||||
|
|
||||||
|
double getMaxIntrinsicHeight(BoxConstraints constraints) {
|
||||||
|
if (child == null)
|
||||||
|
return super.getMaxIntrinsicHeight(constraints);
|
||||||
|
return getIntrinsicDimensions(constraints).height;
|
||||||
|
}
|
||||||
|
|
||||||
|
Size getIntrinsicDimensions(BoxConstraints constraints) {
|
||||||
|
assert(child is RenderSector);
|
||||||
|
assert(child.parentData is SectorParentData);
|
||||||
|
assert(constraints.maxWidth < double.INFINITY || constraints.maxHeight < double.INFINITY);
|
||||||
|
double maxChildDeltaRadius = math.min(constraints.maxWidth, constraints.maxHeight) / 2.0 - innerRadius;
|
||||||
|
SectorDimensions childDimensions = child.getIntrinsicDimensions(new SectorConstraints(maxDeltaRadius: maxChildDeltaRadius), innerRadius);
|
||||||
|
double dimension = (innerRadius + childDimensions.deltaRadius) * 2.0;
|
||||||
|
return constraints.constrain(new Size(dimension, dimension));
|
||||||
|
}
|
||||||
|
|
||||||
|
void performLayout() {
|
||||||
|
if (child == null) {
|
||||||
|
size = constraints.constrain(Size.zero);
|
||||||
|
} else {
|
||||||
|
assert(child is RenderSector);
|
||||||
|
assert(constraints.maxWidth < double.INFINITY || constraints.maxHeight < double.INFINITY);
|
||||||
|
double maxChildDeltaRadius = math.min(constraints.maxWidth, constraints.maxHeight) / 2.0 - innerRadius;
|
||||||
|
assert(child.parentData is SectorParentData);
|
||||||
|
child.parentData.radius = innerRadius;
|
||||||
|
child.parentData.theta = 0.0;
|
||||||
|
child.layout(new SectorConstraints(maxDeltaRadius: maxChildDeltaRadius), parentUsesSize: true);
|
||||||
|
double dimension = (innerRadius + child.deltaRadius) * 2.0;
|
||||||
|
size = constraints.constrain(new Size(dimension, dimension));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void paint(PaintingContext context, Offset offset) {
|
||||||
|
super.paint(context, offset);
|
||||||
|
if (child != null) {
|
||||||
|
Rect bounds = offset & size;
|
||||||
|
// we move the offset to the center of the circle for the RenderSectors
|
||||||
|
context.paintChild(child, bounds.center);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool hitTest(HitTestResult result, { Point position }) {
|
||||||
|
if (child == null)
|
||||||
|
return false;
|
||||||
|
double x = position.x;
|
||||||
|
double y = position.y;
|
||||||
|
// translate to our origin
|
||||||
|
x -= size.width/2.0;
|
||||||
|
y -= size.height/2.0;
|
||||||
|
// convert to radius/theta
|
||||||
|
double radius = math.sqrt(x*x+y*y);
|
||||||
|
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(result, radius: radius, theta: theta);
|
||||||
|
result.add(new BoxHitTestEntry(this, position));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class RenderSolidColor extends RenderDecoratedSector {
|
||||||
|
RenderSolidColor(Color backgroundColor, {
|
||||||
|
this.desiredDeltaRadius: double.INFINITY,
|
||||||
|
this.desiredDeltaTheta: kTwoPi
|
||||||
|
}) : this.backgroundColor = backgroundColor,
|
||||||
|
super(new BoxDecoration(backgroundColor: backgroundColor));
|
||||||
|
|
||||||
|
double desiredDeltaRadius;
|
||||||
|
double desiredDeltaTheta;
|
||||||
|
final Color backgroundColor;
|
||||||
|
|
||||||
|
SectorDimensions getIntrinsicDimensions(SectorConstraints constraints, double radius) {
|
||||||
|
return new SectorDimensions.withConstraints(constraints, deltaTheta: desiredDeltaTheta);
|
||||||
|
}
|
||||||
|
|
||||||
|
void performLayout() {
|
||||||
|
deltaRadius = constraints.constrainDeltaRadius(desiredDeltaRadius);
|
||||||
|
deltaTheta = constraints.constrainDeltaTheta(desiredDeltaTheta);
|
||||||
|
}
|
||||||
|
|
||||||
|
void handleEvent(ui.Event event, HitTestEntry entry) {
|
||||||
|
if (event.type == 'pointerdown')
|
||||||
|
decoration = new BoxDecoration(backgroundColor: const Color(0xFFFF0000));
|
||||||
|
else if (event.type == 'pointerup')
|
||||||
|
decoration = new BoxDecoration(backgroundColor: backgroundColor);
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
name: rendering
|
name: flutter_rendering_examples
|
||||||
dependencies:
|
dependencies:
|
||||||
flutter: ">=0.0.3 <0.1.0"
|
flutter: ">=0.0.3 <0.1.0"
|
||||||
sky_tools: any
|
sky_tools: any
|
||||||
|
@ -2,539 +2,9 @@
|
|||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
import 'dart:math' as math;
|
import 'dart:async';
|
||||||
import 'dart:ui' as ui;
|
|
||||||
|
|
||||||
import 'package:flutter/rendering.dart';
|
import 'package:flutter/rendering.dart';
|
||||||
|
import 'lib/sector_layout.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
|
|
||||||
});
|
|
||||||
|
|
||||||
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 clamp(min: minDeltaRadius, max: maxDeltaRadius, value: deltaRadius);
|
|
||||||
}
|
|
||||||
|
|
||||||
double constrainDeltaTheta(double deltaTheta) {
|
|
||||||
return clamp(min: minDeltaTheta, max: maxDeltaTheta, value: deltaTheta);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool get isTight => minDeltaTheta >= maxDeltaTheta && minDeltaTheta >= maxDeltaTheta;
|
|
||||||
}
|
|
||||||
|
|
||||||
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 new 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract class RenderSector extends RenderObject {
|
|
||||||
|
|
||||||
void setupParentData(RenderObject child) {
|
|
||||||
if (child.parentData is! SectorParentData)
|
|
||||||
child.parentData = new SectorParentData();
|
|
||||||
}
|
|
||||||
|
|
||||||
SectorDimensions getIntrinsicDimensions(SectorConstraints constraints, double radius) {
|
|
||||||
return new SectorDimensions.withConstraints(constraints);
|
|
||||||
}
|
|
||||||
|
|
||||||
SectorConstraints get constraints => super.constraints;
|
|
||||||
bool debugDoesMeetConstraints() {
|
|
||||||
assert(constraints != null);
|
|
||||||
assert(deltaRadius != null);
|
|
||||||
assert(deltaRadius < double.INFINITY);
|
|
||||||
assert(deltaTheta != null);
|
|
||||||
assert(deltaTheta < double.INFINITY);
|
|
||||||
return constraints.minDeltaRadius <= deltaRadius &&
|
|
||||||
deltaRadius <= math.max(constraints.minDeltaRadius, constraints.maxDeltaRadius) &&
|
|
||||||
constraints.minDeltaTheta <= deltaTheta &&
|
|
||||||
deltaTheta <= math.max(constraints.minDeltaTheta, constraints.maxDeltaTheta);
|
|
||||||
}
|
|
||||||
void performResize() {
|
|
||||||
// default behaviour for subclasses that have sizedByParent = true
|
|
||||||
deltaRadius = constraints.constrainDeltaRadius(0.0);
|
|
||||||
deltaTheta = constraints.constrainDeltaTheta(0.0);
|
|
||||||
}
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
Rect get paintBounds => new Rect.fromLTWH(0.0, 0.0, 2.0 * deltaRadius, 2.0 * deltaRadius);
|
|
||||||
|
|
||||||
bool hitTest(HitTestResult result, { double radius, double theta }) {
|
|
||||||
assert(parentData is SectorParentData);
|
|
||||||
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(new HitTestEntry(this));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
void hitTestChildren(HitTestResult result, { double radius, double theta }) { }
|
|
||||||
|
|
||||||
double deltaRadius;
|
|
||||||
double deltaTheta;
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract class RenderDecoratedSector extends RenderSector {
|
|
||||||
|
|
||||||
RenderDecoratedSector(BoxDecoration decoration) : _decoration = decoration;
|
|
||||||
|
|
||||||
BoxDecoration _decoration;
|
|
||||||
BoxDecoration get decoration => _decoration;
|
|
||||||
void set decoration (BoxDecoration value) {
|
|
||||||
if (value == _decoration)
|
|
||||||
return;
|
|
||||||
_decoration = value;
|
|
||||||
markNeedsPaint();
|
|
||||||
}
|
|
||||||
|
|
||||||
// offset must point to the center of the circle
|
|
||||||
void paint(PaintingContext context, Offset offset) {
|
|
||||||
assert(deltaRadius != null);
|
|
||||||
assert(deltaTheta != null);
|
|
||||||
assert(parentData is SectorParentData);
|
|
||||||
|
|
||||||
if (_decoration == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (_decoration.backgroundColor != null) {
|
|
||||||
final PaintingCanvas canvas = context.canvas;
|
|
||||||
Paint paint = new Paint()..color = _decoration.backgroundColor;
|
|
||||||
Path path = new Path();
|
|
||||||
double outerRadius = (parentData.radius + deltaRadius);
|
|
||||||
Rect outerBounds = new Rect.fromLTRB(offset.dx-outerRadius, offset.dy-outerRadius, offset.dx+outerRadius, offset.dy+outerRadius);
|
|
||||||
path.arcTo(outerBounds, parentData.theta, deltaTheta, true);
|
|
||||||
double innerRadius = parentData.radius;
|
|
||||||
Rect innerBounds = new 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);
|
|
||||||
|
|
||||||
void hitTestChildren(HitTestResult result, { double radius, double theta }) {
|
|
||||||
RenderSector child = lastChild;
|
|
||||||
while (child != null) {
|
|
||||||
assert(child.parentData is SectorChildListParentData);
|
|
||||||
if (child.hitTest(result, radius: radius, theta: theta))
|
|
||||||
return;
|
|
||||||
child = child.parentData.previousSibling;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void visitChildren(RenderObjectVisitor visitor) {
|
|
||||||
RenderSector child = lastChild;
|
|
||||||
while (child != null) {
|
|
||||||
visitor(child);
|
|
||||||
child = child.parentData.previousSibling;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class RenderSectorRing extends RenderSectorWithChildren {
|
|
||||||
// lays out RenderSector children in a ring
|
|
||||||
|
|
||||||
RenderSectorRing({
|
|
||||||
BoxDecoration decoration,
|
|
||||||
double deltaRadius: double.INFINITY,
|
|
||||||
double padding: 0.0
|
|
||||||
}) : super(decoration), _padding = padding, _desiredDeltaRadius = deltaRadius;
|
|
||||||
|
|
||||||
double _desiredDeltaRadius;
|
|
||||||
double get desiredDeltaRadius => _desiredDeltaRadius;
|
|
||||||
void set desiredDeltaRadius(double value) {
|
|
||||||
assert(value != null);
|
|
||||||
if (_desiredDeltaRadius != value) {
|
|
||||||
_desiredDeltaRadius = value;
|
|
||||||
markNeedsLayout();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
double _padding;
|
|
||||||
double get padding => _padding;
|
|
||||||
void set padding(double value) {
|
|
||||||
// TODO(ianh): avoid code duplication
|
|
||||||
assert(value != null);
|
|
||||||
if (_padding != value) {
|
|
||||||
_padding = value;
|
|
||||||
markNeedsLayout();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void setupParentData(RenderObject child) {
|
|
||||||
// TODO(ianh): avoid code duplication
|
|
||||||
if (child.parentData is! SectorChildListParentData)
|
|
||||||
child.parentData = new SectorChildListParentData();
|
|
||||||
}
|
|
||||||
|
|
||||||
SectorDimensions getIntrinsicDimensions(SectorConstraints constraints, double radius) {
|
|
||||||
double outerDeltaRadius = constraints.constrainDeltaRadius(desiredDeltaRadius);
|
|
||||||
double innerDeltaRadius = outerDeltaRadius - padding * 2.0;
|
|
||||||
double childRadius = radius + padding;
|
|
||||||
double paddingTheta = math.atan(padding / (radius + outerDeltaRadius));
|
|
||||||
double innerTheta = paddingTheta; // increments with each child
|
|
||||||
double remainingDeltaTheta = constraints.maxDeltaTheta - (innerTheta + paddingTheta);
|
|
||||||
RenderSector child = firstChild;
|
|
||||||
while (child != null) {
|
|
||||||
SectorConstraints innerConstraints = new SectorConstraints(
|
|
||||||
maxDeltaRadius: innerDeltaRadius,
|
|
||||||
maxDeltaTheta: remainingDeltaTheta
|
|
||||||
);
|
|
||||||
SectorDimensions childDimensions = child.getIntrinsicDimensions(innerConstraints, childRadius);
|
|
||||||
innerTheta += childDimensions.deltaTheta;
|
|
||||||
remainingDeltaTheta -= childDimensions.deltaTheta;
|
|
||||||
assert(child.parentData is SectorChildListParentData);
|
|
||||||
child = child.parentData.nextSibling;
|
|
||||||
if (child != null) {
|
|
||||||
innerTheta += paddingTheta;
|
|
||||||
remainingDeltaTheta -= paddingTheta;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return new SectorDimensions.withConstraints(constraints,
|
|
||||||
deltaRadius: outerDeltaRadius,
|
|
||||||
deltaTheta: innerTheta);
|
|
||||||
}
|
|
||||||
|
|
||||||
void performLayout() {
|
|
||||||
assert(this.parentData is SectorParentData);
|
|
||||||
deltaRadius = constraints.constrainDeltaRadius(desiredDeltaRadius);
|
|
||||||
assert(deltaRadius < double.INFINITY);
|
|
||||||
double innerDeltaRadius = deltaRadius - padding * 2.0;
|
|
||||||
double childRadius = this.parentData.radius + padding;
|
|
||||||
double paddingTheta = math.atan(padding / (this.parentData.radius + deltaRadius));
|
|
||||||
double innerTheta = paddingTheta; // increments with each child
|
|
||||||
double remainingDeltaTheta = constraints.maxDeltaTheta - (innerTheta + paddingTheta);
|
|
||||||
RenderSector child = firstChild;
|
|
||||||
while (child != null) {
|
|
||||||
SectorConstraints innerConstraints = new 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;
|
|
||||||
assert(child.parentData is SectorChildListParentData);
|
|
||||||
child = child.parentData.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
|
|
||||||
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.toPoint());
|
|
||||||
child = child.parentData.nextSibling;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
class RenderSectorSlice extends RenderSectorWithChildren {
|
|
||||||
// lays out RenderSector children in a stack
|
|
||||||
|
|
||||||
RenderSectorSlice({
|
|
||||||
BoxDecoration decoration,
|
|
||||||
double deltaTheta: kTwoPi,
|
|
||||||
double padding: 0.0
|
|
||||||
}) : super(decoration), _padding = padding, _desiredDeltaTheta = deltaTheta;
|
|
||||||
|
|
||||||
double _desiredDeltaTheta;
|
|
||||||
double get desiredDeltaTheta => _desiredDeltaTheta;
|
|
||||||
void set desiredDeltaTheta(double value) {
|
|
||||||
assert(value != null);
|
|
||||||
if (_desiredDeltaTheta != value) {
|
|
||||||
_desiredDeltaTheta = value;
|
|
||||||
markNeedsLayout();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
double _padding;
|
|
||||||
double get padding => _padding;
|
|
||||||
void set padding(double value) {
|
|
||||||
// TODO(ianh): avoid code duplication
|
|
||||||
assert(value != null);
|
|
||||||
if (_padding != value) {
|
|
||||||
_padding = value;
|
|
||||||
markNeedsLayout();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void setupParentData(RenderObject child) {
|
|
||||||
// TODO(ianh): avoid code duplication
|
|
||||||
if (child.parentData is! SectorChildListParentData)
|
|
||||||
child.parentData = new SectorChildListParentData();
|
|
||||||
}
|
|
||||||
|
|
||||||
SectorDimensions getIntrinsicDimensions(SectorConstraints constraints, double radius) {
|
|
||||||
assert(this.parentData is SectorParentData);
|
|
||||||
double paddingTheta = math.atan(padding / this.parentData.radius);
|
|
||||||
double outerDeltaTheta = constraints.constrainDeltaTheta(desiredDeltaTheta);
|
|
||||||
double innerDeltaTheta = outerDeltaTheta - paddingTheta * 2.0;
|
|
||||||
double childRadius = this.parentData.radius + padding;
|
|
||||||
double remainingDeltaRadius = constraints.maxDeltaRadius - (padding * 2.0);
|
|
||||||
RenderSector child = firstChild;
|
|
||||||
while (child != null) {
|
|
||||||
SectorConstraints innerConstraints = new SectorConstraints(
|
|
||||||
maxDeltaRadius: remainingDeltaRadius,
|
|
||||||
maxDeltaTheta: innerDeltaTheta
|
|
||||||
);
|
|
||||||
SectorDimensions childDimensions = child.getIntrinsicDimensions(innerConstraints, childRadius);
|
|
||||||
childRadius += childDimensions.deltaRadius;
|
|
||||||
remainingDeltaRadius -= childDimensions.deltaRadius;
|
|
||||||
assert(child.parentData is SectorChildListParentData);
|
|
||||||
child = child.parentData.nextSibling;
|
|
||||||
childRadius += padding;
|
|
||||||
remainingDeltaRadius -= padding;
|
|
||||||
}
|
|
||||||
return new SectorDimensions.withConstraints(constraints,
|
|
||||||
deltaRadius: childRadius - this.parentData.radius,
|
|
||||||
deltaTheta: outerDeltaTheta);
|
|
||||||
}
|
|
||||||
|
|
||||||
void performLayout() {
|
|
||||||
assert(this.parentData is SectorParentData);
|
|
||||||
deltaTheta = constraints.constrainDeltaTheta(desiredDeltaTheta);
|
|
||||||
assert(deltaTheta <= kTwoPi);
|
|
||||||
double paddingTheta = math.atan(padding / this.parentData.radius);
|
|
||||||
double innerTheta = this.parentData.theta + paddingTheta;
|
|
||||||
double innerDeltaTheta = deltaTheta - paddingTheta * 2.0;
|
|
||||||
double childRadius = this.parentData.radius + padding;
|
|
||||||
double remainingDeltaRadius = constraints.maxDeltaRadius - (padding * 2.0);
|
|
||||||
RenderSector child = firstChild;
|
|
||||||
while (child != null) {
|
|
||||||
SectorConstraints innerConstraints = new SectorConstraints(
|
|
||||||
maxDeltaRadius: remainingDeltaRadius,
|
|
||||||
maxDeltaTheta: innerDeltaTheta
|
|
||||||
);
|
|
||||||
child.parentData.theta = innerTheta;
|
|
||||||
child.parentData.radius = childRadius;
|
|
||||||
child.layout(innerConstraints, parentUsesSize: true);
|
|
||||||
childRadius += child.deltaRadius;
|
|
||||||
remainingDeltaRadius -= child.deltaRadius;
|
|
||||||
assert(child.parentData is SectorChildListParentData);
|
|
||||||
child = child.parentData.nextSibling;
|
|
||||||
childRadius += padding;
|
|
||||||
remainingDeltaRadius -= padding;
|
|
||||||
}
|
|
||||||
deltaRadius = childRadius - this.parentData.radius;
|
|
||||||
}
|
|
||||||
|
|
||||||
// offset must point to the center of our circle
|
|
||||||
// each sector then knows how to paint itself at its location
|
|
||||||
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.toPoint());
|
|
||||||
child = child.parentData.nextSibling;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
class RenderBoxToRenderSectorAdapter extends RenderBox {
|
|
||||||
|
|
||||||
RenderBoxToRenderSectorAdapter({ double innerRadius: 0.0, RenderSector child }) :
|
|
||||||
_innerRadius = innerRadius {
|
|
||||||
_child = child;
|
|
||||||
adoptChild(_child);
|
|
||||||
}
|
|
||||||
|
|
||||||
double _innerRadius;
|
|
||||||
double get innerRadius => _innerRadius;
|
|
||||||
void set innerRadius(double value) {
|
|
||||||
_innerRadius = value;
|
|
||||||
markNeedsLayout();
|
|
||||||
}
|
|
||||||
|
|
||||||
RenderSector _child;
|
|
||||||
RenderSector get child => _child;
|
|
||||||
void set child(RenderSector value) {
|
|
||||||
if (_child != null)
|
|
||||||
dropChild(_child);
|
|
||||||
_child = value;
|
|
||||||
adoptChild(_child);
|
|
||||||
markNeedsLayout();
|
|
||||||
}
|
|
||||||
|
|
||||||
void setupParentData(RenderObject child) {
|
|
||||||
if (child.parentData is! SectorParentData)
|
|
||||||
child.parentData = new SectorParentData();
|
|
||||||
}
|
|
||||||
|
|
||||||
void visitChildren(RenderObjectVisitor visitor) {
|
|
||||||
visitor(_child);
|
|
||||||
}
|
|
||||||
|
|
||||||
double getMinIntrinsicWidth(BoxConstraints constraints) {
|
|
||||||
if (child == null)
|
|
||||||
return super.getMinIntrinsicWidth(constraints);
|
|
||||||
return getIntrinsicDimensions(constraints).width;
|
|
||||||
}
|
|
||||||
|
|
||||||
double getMaxIntrinsicWidth(BoxConstraints constraints) {
|
|
||||||
if (child == null)
|
|
||||||
return super.getMaxIntrinsicWidth(constraints);
|
|
||||||
return getIntrinsicDimensions(constraints).width;
|
|
||||||
}
|
|
||||||
|
|
||||||
double getMinIntrinsicHeight(BoxConstraints constraints) {
|
|
||||||
if (child == null)
|
|
||||||
return super.getMinIntrinsicHeight(constraints);
|
|
||||||
return getIntrinsicDimensions(constraints).height;
|
|
||||||
}
|
|
||||||
|
|
||||||
double getMaxIntrinsicHeight(BoxConstraints constraints) {
|
|
||||||
if (child == null)
|
|
||||||
return super.getMaxIntrinsicHeight(constraints);
|
|
||||||
return getIntrinsicDimensions(constraints).height;
|
|
||||||
}
|
|
||||||
|
|
||||||
Size getIntrinsicDimensions(BoxConstraints constraints) {
|
|
||||||
assert(child is RenderSector);
|
|
||||||
assert(child.parentData is SectorParentData);
|
|
||||||
assert(constraints.maxWidth < double.INFINITY || constraints.maxHeight < double.INFINITY);
|
|
||||||
double maxChildDeltaRadius = math.min(constraints.maxWidth, constraints.maxHeight) / 2.0 - innerRadius;
|
|
||||||
SectorDimensions childDimensions = child.getIntrinsicDimensions(new SectorConstraints(maxDeltaRadius: maxChildDeltaRadius), innerRadius);
|
|
||||||
double dimension = (innerRadius + childDimensions.deltaRadius) * 2.0;
|
|
||||||
return constraints.constrain(new Size(dimension, dimension));
|
|
||||||
}
|
|
||||||
|
|
||||||
void performLayout() {
|
|
||||||
if (child == null) {
|
|
||||||
size = constraints.constrain(Size.zero);
|
|
||||||
} else {
|
|
||||||
assert(child is RenderSector);
|
|
||||||
assert(constraints.maxWidth < double.INFINITY || constraints.maxHeight < double.INFINITY);
|
|
||||||
double maxChildDeltaRadius = math.min(constraints.maxWidth, constraints.maxHeight) / 2.0 - innerRadius;
|
|
||||||
assert(child.parentData is SectorParentData);
|
|
||||||
child.parentData.radius = innerRadius;
|
|
||||||
child.parentData.theta = 0.0;
|
|
||||||
child.layout(new SectorConstraints(maxDeltaRadius: maxChildDeltaRadius), parentUsesSize: true);
|
|
||||||
double dimension = (innerRadius + child.deltaRadius) * 2.0;
|
|
||||||
size = constraints.constrain(new Size(dimension, dimension));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void paint(PaintingContext context, Offset offset) {
|
|
||||||
super.paint(context, offset);
|
|
||||||
if (child != null) {
|
|
||||||
Rect bounds = offset & size;
|
|
||||||
// we move the offset to the center of the circle for the RenderSectors
|
|
||||||
context.paintChild(child, bounds.center);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool hitTest(HitTestResult result, { Point position }) {
|
|
||||||
double x = position.x;
|
|
||||||
double y = position.y;
|
|
||||||
if (child == null)
|
|
||||||
return false;
|
|
||||||
// translate to our origin
|
|
||||||
x -= size.width/2.0;
|
|
||||||
y -= size.height/2.0;
|
|
||||||
// convert to radius/theta
|
|
||||||
double radius = math.sqrt(x*x+y*y);
|
|
||||||
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(result, radius: radius, theta: theta);
|
|
||||||
result.add(new BoxHitTestEntry(this, position));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
class RenderSolidColor extends RenderDecoratedSector {
|
|
||||||
RenderSolidColor(Color backgroundColor, {
|
|
||||||
this.desiredDeltaRadius: double.INFINITY,
|
|
||||||
this.desiredDeltaTheta: kTwoPi
|
|
||||||
}) : this.backgroundColor = backgroundColor,
|
|
||||||
super(new BoxDecoration(backgroundColor: backgroundColor));
|
|
||||||
|
|
||||||
double desiredDeltaRadius;
|
|
||||||
double desiredDeltaTheta;
|
|
||||||
final Color backgroundColor;
|
|
||||||
|
|
||||||
SectorDimensions getIntrinsicDimensions(SectorConstraints constraints, double radius) {
|
|
||||||
return new SectorDimensions.withConstraints(constraints, deltaTheta: desiredDeltaTheta);
|
|
||||||
}
|
|
||||||
|
|
||||||
void performLayout() {
|
|
||||||
deltaRadius = constraints.constrainDeltaRadius(desiredDeltaRadius);
|
|
||||||
deltaTheta = constraints.constrainDeltaTheta(desiredDeltaTheta);
|
|
||||||
}
|
|
||||||
|
|
||||||
void handleEvent(ui.Event event, HitTestEntry entry) {
|
|
||||||
if (event.type == 'pointerdown')
|
|
||||||
decoration = new BoxDecoration(backgroundColor: const Color(0xFFFF0000));
|
|
||||||
else if (event.type == 'pointerup')
|
|
||||||
decoration = new BoxDecoration(backgroundColor: backgroundColor);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
RenderBox buildSectorExample() {
|
RenderBox buildSectorExample() {
|
||||||
RenderSectorRing rootCircle = new RenderSectorRing(padding: 20.0);
|
RenderSectorRing rootCircle = new RenderSectorRing(padding: 20.0);
|
||||||
|
@ -1,9 +1,12 @@
|
|||||||
name: widgets
|
name: sky_widgets_examples
|
||||||
dependencies:
|
dependencies:
|
||||||
flutter: ">=0.0.3 <0.1.0"
|
flutter: ">=0.0.3 <0.1.0"
|
||||||
sky_tools: any
|
sky_tools: any
|
||||||
|
flutter_rendering_examples: any
|
||||||
dependency_overrides:
|
dependency_overrides:
|
||||||
material_design_icons:
|
material_design_icons:
|
||||||
path: ../../sky/packages/material_design_icons
|
path: ../../sky/packages/material_design_icons
|
||||||
flutter:
|
flutter:
|
||||||
path: ../../sky/packages/sky
|
path: ../../sky/packages/sky
|
||||||
|
flutter_rendering_examples:
|
||||||
|
path: ../rendering
|
||||||
|
@ -7,7 +7,7 @@ import 'dart:math' as math;
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/rendering.dart';
|
import 'package:flutter/rendering.dart';
|
||||||
|
|
||||||
import '../rendering/sector_layout.dart';
|
import 'package:flutter_rendering_examples/sector_layout.dart';
|
||||||
|
|
||||||
RenderBox initCircle() {
|
RenderBox initCircle() {
|
||||||
return new RenderBoxToRenderSectorAdapter(
|
return new RenderBoxToRenderSectorAdapter(
|
||||||
@ -16,10 +16,14 @@ RenderBox initCircle() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
class SectorApp extends MaterialApp {
|
class SectorApp extends StatefulComponent {
|
||||||
|
SectorAppState createState() => new SectorAppState();
|
||||||
|
}
|
||||||
|
|
||||||
RenderBoxToRenderSectorAdapter sectors = initCircle();
|
class SectorAppState extends State<SectorApp> {
|
||||||
math.Random rand = new math.Random(1);
|
|
||||||
|
final RenderBoxToRenderSectorAdapter sectors = initCircle();
|
||||||
|
final math.Random rand = new math.Random(1);
|
||||||
|
|
||||||
void addSector() {
|
void addSector() {
|
||||||
double deltaTheta;
|
double deltaTheta;
|
||||||
@ -52,27 +56,27 @@ class SectorApp extends MaterialApp {
|
|||||||
RenderBoxToRenderSectorAdapter sectorAddIcon = initSector(const Color(0xFF00DD00));
|
RenderBoxToRenderSectorAdapter sectorAddIcon = initSector(const Color(0xFF00DD00));
|
||||||
RenderBoxToRenderSectorAdapter sectorRemoveIcon = initSector(const Color(0xFFDD0000));
|
RenderBoxToRenderSectorAdapter sectorRemoveIcon = initSector(const Color(0xFFDD0000));
|
||||||
|
|
||||||
bool enabledAdd = true;
|
bool _enabledAdd = true;
|
||||||
bool enabledRemove = false;
|
bool _enabledRemove = false;
|
||||||
void updateEnabledState() {
|
void updateEnabledState() {
|
||||||
setState(() {
|
setState(() {
|
||||||
var ring = (sectors.child as RenderSectorRing);
|
var ring = (sectors.child as RenderSectorRing);
|
||||||
SectorDimensions currentSize = ring.getIntrinsicDimensions(const SectorConstraints(), ring.deltaRadius);
|
SectorDimensions currentSize = ring.getIntrinsicDimensions(const SectorConstraints(), ring.deltaRadius);
|
||||||
enabledAdd = currentSize.deltaTheta < kTwoPi;
|
_enabledAdd = currentSize.deltaTheta < kTwoPi;
|
||||||
enabledRemove = ring.firstChild != null;
|
_enabledRemove = ring.firstChild != null;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget buildBody() {
|
Widget buildBody() {
|
||||||
return new Material(
|
return new Material(
|
||||||
child: new Column([
|
child: new Column(<Widget>[
|
||||||
new Container(
|
new Container(
|
||||||
padding: new EdgeDims.symmetric(horizontal: 8.0, vertical: 25.0),
|
padding: new EdgeDims.symmetric(horizontal: 8.0, vertical: 25.0),
|
||||||
child: new Row([
|
child: new Row(<Widget>[
|
||||||
new RaisedButton(
|
new RaisedButton(
|
||||||
enabled: enabledAdd,
|
enabled: _enabledAdd,
|
||||||
child: new IntrinsicWidth(
|
child: new IntrinsicWidth(
|
||||||
child: new Row([
|
child: new Row(<Widget>[
|
||||||
new Container(
|
new Container(
|
||||||
padding: new EdgeDims.all(4.0),
|
padding: new EdgeDims.all(4.0),
|
||||||
margin: new EdgeDims.only(right: 10.0),
|
margin: new EdgeDims.only(right: 10.0),
|
||||||
@ -84,9 +88,9 @@ class SectorApp extends MaterialApp {
|
|||||||
onPressed: addSector
|
onPressed: addSector
|
||||||
),
|
),
|
||||||
new RaisedButton(
|
new RaisedButton(
|
||||||
enabled: enabledRemove,
|
enabled: _enabledRemove,
|
||||||
child: new IntrinsicWidth(
|
child: new IntrinsicWidth(
|
||||||
child: new Row([
|
child: new Row(<Widget>[
|
||||||
new Container(
|
new Container(
|
||||||
padding: new EdgeDims.all(4.0),
|
padding: new EdgeDims.all(4.0),
|
||||||
margin: new EdgeDims.only(right: 10.0),
|
margin: new EdgeDims.only(right: 10.0),
|
||||||
@ -117,18 +121,20 @@ class SectorApp extends MaterialApp {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget build() {
|
Widget build(BuildContext context) {
|
||||||
return new Theme(
|
return new MaterialApp(
|
||||||
data: new ThemeData.light(),
|
theme: new ThemeData.light(),
|
||||||
child: new Title(
|
|
||||||
title: 'Sector Layout',
|
title: 'Sector Layout',
|
||||||
child: new Scaffold(
|
routes: <String, RouteBuilder>{
|
||||||
|
'/': (RouteArguments args) {
|
||||||
|
return new Scaffold(
|
||||||
toolBar: new ToolBar(
|
toolBar: new ToolBar(
|
||||||
center: new Text('Sector Layout in a Widget Tree')
|
center: new Text('Sector Layout in a Widget Tree')
|
||||||
),
|
),
|
||||||
body: buildBody()
|
body: buildBody()
|
||||||
)
|
);
|
||||||
)
|
}
|
||||||
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -633,7 +633,7 @@ abstract class RenderBox extends RenderObject {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String debugDescribeSettings(String prefix) => '${super.debugDescribeSettings(prefix)}${prefix}size: $size\n';
|
String debugDescribeSettings(String prefix) => '${super.debugDescribeSettings(prefix)}${prefix}size: ${ hasSize ? size : "MISSING" }\n';
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A mixin that provides useful default behaviors for boxes with children
|
/// A mixin that provides useful default behaviors for boxes with children
|
||||||
|
@ -961,6 +961,22 @@ class AssetImage extends StatelessComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class WidgetToRenderBoxAdapter extends LeafRenderObjectWidget {
|
||||||
|
WidgetToRenderBoxAdapter(RenderBox renderBox)
|
||||||
|
: renderBox = renderBox,
|
||||||
|
// WidgetToRenderBoxAdapter objects are keyed to their render box. This
|
||||||
|
// prevents the widget being used in the widget hierarchy in two different
|
||||||
|
// places, which would cause the RenderBox to get inserted in multiple
|
||||||
|
// places in the RenderObject tree.
|
||||||
|
super(key: new GlobalObjectKey(renderBox)) {
|
||||||
|
assert(renderBox != null);
|
||||||
|
}
|
||||||
|
|
||||||
|
final RenderBox renderBox;
|
||||||
|
|
||||||
|
RenderBox createRenderObject() => renderBox;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// EVENT HANDLING
|
// EVENT HANDLING
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user