|  | // 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. | 
|  |  | 
|  | // This example shows how to use process input events in the underlying render | 
|  | // tree. | 
|  |  | 
|  | import 'package:flutter/material.dart'; // Imported just for its color palette. | 
|  | import 'package:flutter/rendering.dart'; | 
|  |  | 
|  | // Material design colors. :p | 
|  | List<Color> _kColors = <Color>[ | 
|  | Colors.teal, | 
|  | Colors.amber, | 
|  | Colors.purple, | 
|  | Colors.lightBlue, | 
|  | Colors.deepPurple, | 
|  | Colors.lime, | 
|  | ]; | 
|  |  | 
|  | /// A simple model object for a dot that reacts to pointer pressure. | 
|  | class Dot { | 
|  | Dot({ Color color }) : _paint = Paint()..color = color; | 
|  |  | 
|  | final Paint _paint; | 
|  | Offset position = Offset.zero; | 
|  | double radius = 0.0; | 
|  |  | 
|  | void update(PointerEvent event) { | 
|  | position = event.position; | 
|  | radius = 5 + (95 * event.pressure); | 
|  | } | 
|  |  | 
|  | void paint(Canvas canvas, Offset offset) { | 
|  | canvas.drawCircle(position + offset, radius, _paint); | 
|  | } | 
|  | } | 
|  |  | 
|  | /// A render object that draws dots under each pointer. | 
|  | class RenderDots extends RenderBox { | 
|  | RenderDots(); | 
|  |  | 
|  | /// State to remember which dots to paint. | 
|  | final Map<int, Dot> _dots = <int, Dot>{}; | 
|  |  | 
|  | /// Indicates that the size of this render object depends only on the | 
|  | /// layout constraints provided by the parent. | 
|  | @override | 
|  | bool get sizedByParent => true; | 
|  |  | 
|  | /// By selecting the biggest value permitted by the incoming constraints | 
|  | /// during layout, this function makes this render object as large as | 
|  | /// possible (i.e., fills the entire screen). | 
|  | @override | 
|  | void performResize() { | 
|  | size = constraints.biggest; | 
|  | } | 
|  |  | 
|  | /// Makes this render object hittable so that it receives pointer events. | 
|  | @override | 
|  | bool hitTestSelf(Offset position) => true; | 
|  |  | 
|  | /// Processes pointer events by mutating state and invalidating its previous | 
|  | /// painting commands. | 
|  | @override | 
|  | void handleEvent(PointerEvent event, BoxHitTestEntry entry) { | 
|  | if (event is PointerDownEvent) { | 
|  | final Color color = _kColors[event.pointer.remainder(_kColors.length) as int]; | 
|  | _dots[event.pointer] = Dot(color: color)..update(event); | 
|  | // We call markNeedsPaint to indicate that our painting commands have | 
|  | // changed and that paint needs to be called before displaying a new frame | 
|  | // to the user. It's harmless to call markNeedsPaint multiple times | 
|  | // because the render tree will ignore redundant calls. | 
|  | markNeedsPaint(); | 
|  | } else if (event is PointerUpEvent || event is PointerCancelEvent) { | 
|  | _dots.remove(event.pointer); | 
|  | markNeedsPaint(); | 
|  | } else if (event is PointerMoveEvent) { | 
|  | _dots[event.pointer].update(event); | 
|  | markNeedsPaint(); | 
|  | } | 
|  | } | 
|  |  | 
|  | /// Issues new painting commands. | 
|  | @override | 
|  | void paint(PaintingContext context, Offset offset) { | 
|  | final Canvas canvas = context.canvas; | 
|  | // The "size" property indicates the size of that this render box was | 
|  | // allotted during layout. Here we paint our bounds white. Notice that we're | 
|  | // located at "offset" from the origin of the canvas' coordinate system. | 
|  | // Passing offset during the render tree's paint walk is an optimization to | 
|  | // avoid having to change the origin of the canvas's coordinate system too | 
|  | // often. | 
|  | canvas.drawRect(offset & size, Paint()..color = const Color(0xFFFFFFFF)); | 
|  |  | 
|  | // We iterate through our model and paint each dot. | 
|  | for (final Dot dot in _dots.values) | 
|  | dot.paint(canvas, offset); | 
|  | } | 
|  | } | 
|  |  | 
|  | void main() { | 
|  | // Create some styled text to tell the user to interact with the app. | 
|  | final RenderParagraph paragraph = RenderParagraph( | 
|  | const TextSpan( | 
|  | style: TextStyle(color: Colors.black87), | 
|  | text: 'Touch me!', | 
|  | ), | 
|  | textDirection: TextDirection.ltr, | 
|  | ); | 
|  | // A stack is a render object that layers its children on top of each other. | 
|  | // The bottom later is our RenderDots object, and on top of that we show the | 
|  | // text. | 
|  | final RenderStack stack = RenderStack( | 
|  | textDirection: TextDirection.ltr, | 
|  | children: <RenderBox>[ | 
|  | RenderDots(), | 
|  | paragraph, | 
|  | ], | 
|  | ); | 
|  | // The "parentData" field of a render object is controlled by the render | 
|  | // object's parent render object. Now that we've added the paragraph as a | 
|  | // child of the RenderStack, the paragraph's parentData field has been | 
|  | // populated with a StackParentData, which we can use to provide input to the | 
|  | // stack's layout algorithm. | 
|  | // | 
|  | // We use the StackParentData of the paragraph to position the text in the top | 
|  | // left corner of the screen. | 
|  | final StackParentData paragraphParentData = paragraph.parentData as StackParentData; | 
|  | paragraphParentData | 
|  | ..top = 40.0 | 
|  | ..left = 20.0; | 
|  |  | 
|  | // Finally, we attach the render tree we've built to the screen. | 
|  | RenderingFlutterBinding(root: stack); | 
|  | } |