// Copyright 2014 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'dart: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 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 [ /// 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 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 = FractionalOffset(0.0, 0.0); /// The center point along the top edge. static const FractionalOffset topCenter = FractionalOffset(0.5, 0.0); /// The top right corner. static const FractionalOffset topRight = FractionalOffset(1.0, 0.0); /// The center point along the left edge. static const FractionalOffset centerLeft = FractionalOffset(0.0, 0.5); /// The center point, both horizontally and vertically. static const FractionalOffset center = FractionalOffset(0.5, 0.5); /// The center point along the right edge. static const FractionalOffset centerRight = FractionalOffset(1.0, 0.5); /// The bottom left corner. static const FractionalOffset bottomLeft = FractionalOffset(0.0, 1.0); /// The center point along the bottom edge. static const FractionalOffset bottomCenter = FractionalOffset(0.5, 1.0); /// The bottom right corner. static const FractionalOffset bottomRight = FractionalOffset(1.0, 1.0); @override Alignment operator -(Alignment other) { if (other is! FractionalOffset) { return super - other; } return FractionalOffset(dx - other.dx, dy - other.dy); } @override Alignment operator +(Alignment other) { if (other is! FractionalOffset) { return super + other; } return FractionalOffset(dx + other.dx, dy + other.dy); } @override FractionalOffset operator -() { return FractionalOffset(-dx, -dy); } @override FractionalOffset operator *(double other) { return FractionalOffset(dx * other, dy * other); } @override FractionalOffset operator /(double other) { return FractionalOffset(dx / other, dy / other); } @override FractionalOffset operator ~/(double other) { return FractionalOffset((dx ~/ other).toDouble(), (dy ~/ other).toDouble()); } @override FractionalOffset operator %(double other) { return FractionalOffset(dx % other, dy % other); } /// Linearly interpolate between two [FractionalOffset]s. /// /// If either is null, this function interpolates from [FractionalOffset.center]. /// /// {@macro dart.ui.shadow.lerp} static FractionalOffset? lerp(FractionalOffset? a, FractionalOffset? b, double t) { assert(t != null); if (a == null && b == null) { return null; } if (a == null) { return FractionalOffset(ui.lerpDouble(0.5, b!.dx, t)!, ui.lerpDouble(0.5, b.dy, t)!); } if (b == null) { return FractionalOffset(ui.lerpDouble(a.dx, 0.5, t)!, ui.lerpDouble(a.dy, 0.5, t)!); } return 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)})'; } }