// 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:ui' as ui show lerpDouble; import 'package:flutter/foundation.dart'; import 'alignment.dart'; import 'basic_types.dart'; /// An offset that's expressed as a fraction of a [Size]. /// /// `FractionalOffset(1.0, 0.0)` represents the top right of the [Size]. /// /// `FractionalOffset(0.0, 1.0)` represents the bottom left of the [Size]. /// /// `FractionalOffset(0.5, 2.0)` represents a point half way across the [Size], /// below the bottom of the rectangle by the height of the [Size]. /// /// The [FractionalOffset] class specifies offsets in terms of a distance from /// the top left, regardless of the [TextDirection]. /// /// ## Design discussion /// /// [FractionalOffset] and [Alignment] are two different representations of the /// same information: the location within a rectangle relative to the size of /// the rectangle. The difference between the two classes is in the coordinate /// system they use to represent the location. /// /// [FractionalOffset] uses a coordinate system with an origin in the top-left /// corner of the rectangle whereas [Alignment] uses a coordinate system with an /// origin in the center of the rectangle. /// /// Historically, [FractionalOffset] predates [Alignment]. When we attempted to /// make a version of [FractionalOffset] that adapted to the [TextDirection], we /// ran into difficulty because placing the origin in the top-left corner /// introduced a left-to-right bias that was hard to remove. /// /// By placing the origin in the center, [Alignment] and [AlignmentDirectional] /// are able to use the same origin, which means we can use a linear function to /// resolve an [AlignmentDirectional] into an [Alignment] in both /// [TextDirection.rtl] and [TextDirection.ltr]. /// /// [Alignment] is better for most purposes than [FractionalOffset] and should /// be used instead of [FractionalOffset]. We continue to implement /// [FractionalOffset] to support code that predates [Alignment]. /// /// See also: /// /// * [Alignment], which uses a coordinate system based on the center of the /// rectangle instead of the top left corner of the rectangle. @immutable class FractionalOffset extends Alignment { /// Creates a fractional offset. /// /// The [dx] and [dy] arguments must not be null. const FractionalOffset(double dx, double dy) : assert(dx != null), assert(dy != null), super(dx * 2.0 - 1.0, dy * 2.0 - 1.0); /// Creates a fractional offset from a specific offset and size. /// /// The returned [FractionalOffset] describes the position of the /// [Offset] in the [Size], as a fraction of the [Size]. factory FractionalOffset.fromOffsetAndSize(Offset offset, Size size) { assert(size != null); assert(offset != null); return new FractionalOffset( offset.dx / size.width, offset.dy / size.height, ); } /// Creates a fractional offset from a specific offset and rectangle. /// /// The offset is assumed to be relative to the same origin as the rectangle. /// /// If the offset is relative to the top left of the rectangle, use [new /// FractionalOffset.fromOffsetAndSize] instead, passing `rect.size`. /// /// The returned [FractionalOffset] describes the position of the /// [Offset] in the [Rect], as a fraction of the [Rect]. factory FractionalOffset.fromOffsetAndRect(Offset offset, Rect rect) { return new FractionalOffset.fromOffsetAndSize( offset - rect.topLeft, rect.size, ); } /// The distance fraction in the horizontal direction. /// /// A value of 0.0 corresponds to the leftmost edge. A value of 1.0 /// corresponds to the rightmost edge. Values are not limited to that range; /// negative values represent positions to the left of the left edge, and /// values greater than 1.0 represent positions to the right of the right /// edge. double get dx => (x + 1.0) / 2.0; /// The distance fraction in the vertical direction. /// /// A value of 0.0 corresponds to the topmost edge. A value of 1.0 corresponds /// to the bottommost edge. Values are not limited to that range; negative /// values represent positions above the top, and values greater than 1.0 /// represent positions below the bottom. double get dy => (y + 1.0) / 2.0; /// The top left corner. static const FractionalOffset topLeft = const FractionalOffset(0.0, 0.0); /// The center point along the top edge. static const FractionalOffset topCenter = const FractionalOffset(0.5, 0.0); /// The top right corner. static const FractionalOffset topRight = const FractionalOffset(1.0, 0.0); /// The center point along the left edge. static const FractionalOffset centerLeft = const FractionalOffset(0.0, 0.5); /// The center point, both horizontally and vertically. static const FractionalOffset center = const FractionalOffset(0.5, 0.5); /// The center point along the right edge. static const FractionalOffset centerRight = const FractionalOffset(1.0, 0.5); /// The bottom left corner. static const FractionalOffset bottomLeft = const FractionalOffset(0.0, 1.0); /// The center point along the bottom edge. static const FractionalOffset bottomCenter = const FractionalOffset(0.5, 1.0); /// The bottom right corner. static const FractionalOffset bottomRight = const FractionalOffset(1.0, 1.0); @override Alignment operator -(Alignment other) { if (other is! FractionalOffset) return super - other; final FractionalOffset typedOther = other; return new FractionalOffset(dx - typedOther.dx, dy - typedOther.dy); } @override Alignment operator +(Alignment other) { if (other is! FractionalOffset) return super + other; final FractionalOffset typedOther = other; return new FractionalOffset(dx + typedOther.dx, dy + typedOther.dy); } @override FractionalOffset operator -() { return new FractionalOffset(-dx, -dy); } @override FractionalOffset operator *(double other) { return new FractionalOffset(dx * other, dy * other); } @override FractionalOffset operator /(double other) { return new FractionalOffset(dx / other, dy / other); } @override FractionalOffset operator ~/(double other) { return new FractionalOffset((dx ~/ other).toDouble(), (dy ~/ other).toDouble()); } @override FractionalOffset operator %(double other) { return new FractionalOffset(dx % other, dy % other); } /// Linearly interpolate between two [FractionalOffset]s. /// /// If either is null, this function interpolates from [FractionalOffset.center]. /// /// The `t` argument represents position on the timeline, with 0.0 meaning /// that the interpolation has not started, returning `a` (or something /// equivalent to `a`), 1.0 meaning that the interpolation has finished, /// returning `b` (or something equivalent to `b`), and values in between /// meaning that the interpolation is at the relevant point on the timeline /// between `a` and `b`. The interpolation can be extrapolated beyond 0.0 and /// 1.0, so negative values and values greater than 1.0 are valid (and can /// easily be generated by curves such as [Curves.elasticInOut]). /// /// Values for `t` are usually obtained from an [Animation<double>], such as /// an [AnimationController]. static FractionalOffset lerp(FractionalOffset a, FractionalOffset b, double t) { assert(t != null); if (a == null && b == null) return null; if (a == null) return new FractionalOffset(ui.lerpDouble(0.5, b.dx, t), ui.lerpDouble(0.5, b.dy, t)); if (b == null) return new FractionalOffset(ui.lerpDouble(a.dx, 0.5, t), ui.lerpDouble(a.dy, 0.5, t)); return new FractionalOffset(ui.lerpDouble(a.dx, b.dx, t), ui.lerpDouble(a.dy, b.dy, t)); } @override String toString() { return 'FractionalOffset(${dx.toStringAsFixed(1)}, ' '${dy.toStringAsFixed(1)})'; } }