// 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 'basic_types.dart'; /// Base class for [Alignment] that allows for text-direction aware /// resolution. /// /// A property or argument of this type accepts classes created either with [ /// Alignment] and its variants, or [AlignmentDirectional.new]. /// /// To convert an [AlignmentGeometry] object of indeterminate type into an /// [Alignment] object, call the [resolve] method. @immutable abstract class AlignmentGeometry { /// Abstract const constructor. This constructor enables subclasses to provide /// const constructors so that they can be used in const expressions. const AlignmentGeometry(); double get _x; double get _start; double get _y; /// Returns the sum of two [AlignmentGeometry] objects. /// /// If you know you are adding two [Alignment] or two [AlignmentDirectional] /// objects, consider using the `+` operator instead, which always returns an /// object of the same type as the operands, and is typed accordingly. /// /// If [add] is applied to two objects of the same type ([Alignment] or /// [AlignmentDirectional]), an object of that type will be returned (though /// this is not reflected in the type system). Otherwise, an object /// representing a combination of both is returned. That object can be turned /// into a concrete [Alignment] using [resolve]. AlignmentGeometry add(AlignmentGeometry other) { return _MixedAlignment( _x + other._x, _start + other._start, _y + other._y, ); } /// Returns the negation of the given [AlignmentGeometry] object. /// /// This is the same as multiplying the object by -1.0. /// /// This operator returns an object of the same type as the operand. AlignmentGeometry operator -(); /// Scales the [AlignmentGeometry] object in each dimension by the given factor. /// /// This operator returns an object of the same type as the operand. AlignmentGeometry operator *(double other); /// Divides the [AlignmentGeometry] object in each dimension by the given factor. /// /// This operator returns an object of the same type as the operand. AlignmentGeometry operator /(double other); /// Integer divides the [AlignmentGeometry] object in each dimension by the given factor. /// /// This operator returns an object of the same type as the operand. AlignmentGeometry operator ~/(double other); /// Computes the remainder in each dimension by the given factor. /// /// This operator returns an object of the same type as the operand. AlignmentGeometry operator %(double other); /// Linearly interpolate between two [AlignmentGeometry] objects. /// /// If either is null, this function interpolates from [Alignment.center], and /// the result is an object of the same type as the non-null argument. /// /// If [lerp] is applied to two objects of the same type ([Alignment] or /// [AlignmentDirectional]), an object of that type will be returned (though /// this is not reflected in the type system). Otherwise, an object /// representing a combination of both is returned. That object can be turned /// into a concrete [Alignment] using [resolve]. /// /// {@macro dart.ui.shadow.lerp} static AlignmentGeometry? lerp(AlignmentGeometry? a, AlignmentGeometry? b, double t) { assert(t != null); if (a == null && b == null) { return null; } if (a == null) { return b! * t; } if (b == null) { return a * (1.0 - t); } if (a is Alignment && b is Alignment) { return Alignment.lerp(a, b, t); } if (a is AlignmentDirectional && b is AlignmentDirectional) { return AlignmentDirectional.lerp(a, b, t); } return _MixedAlignment( ui.lerpDouble(a._x, b._x, t)!, ui.lerpDouble(a._start, b._start, t)!, ui.lerpDouble(a._y, b._y, t)!, ); } /// Convert this instance into an [Alignment], which uses literal /// coordinates (the `x` coordinate being explicitly a distance from the /// left). /// /// See also: /// /// * [Alignment], for which this is a no-op (returns itself). /// * [AlignmentDirectional], which flips the horizontal direction /// based on the `direction` argument. Alignment resolve(TextDirection? direction); @override String toString() { if (_start == 0.0) { return Alignment._stringify(_x, _y); } if (_x == 0.0) { return AlignmentDirectional._stringify(_start, _y); } return '${Alignment._stringify(_x, _y)} + ${AlignmentDirectional._stringify(_start, 0.0)}'; } @override bool operator ==(Object other) { return other is AlignmentGeometry && other._x == _x && other._start == _start && other._y == _y; } @override int get hashCode => Object.hash(_x, _start, _y); } /// A point within a rectangle. /// /// `Alignment(0.0, 0.0)` represents the center of the rectangle. The distance /// from -1.0 to +1.0 is the distance from one side of the rectangle to the /// other side of the rectangle. Therefore, 2.0 units horizontally (or /// vertically) is equivalent to the width (or height) of the rectangle. /// /// `Alignment(-1.0, -1.0)` represents the top left of the rectangle. /// /// `Alignment(1.0, 1.0)` represents the bottom right of the rectangle. /// /// `Alignment(0.0, 3.0)` represents a point that is horizontally centered with /// respect to the rectangle and vertically below the bottom of the rectangle by /// the height of the rectangle. /// /// `Alignment(0.0, -0.5)` represents a point that is horizontally centered with /// respect to the rectangle and vertically half way between the top edge and /// the center. /// /// `Alignment(x, y)` in a rectangle with height h and width w describes /// the point (x * w/2 + w/2, y * h/2 + h/2) in the coordinate system of the /// rectangle. /// /// [Alignment] uses visual coordinates, which means increasing [x] moves the /// point from left to right. To support layouts with a right-to-left /// [TextDirection], consider using [AlignmentDirectional], in which the /// direction the point moves when increasing the horizontal value depends on /// the [TextDirection]. /// /// A variety of widgets use [Alignment] in their configuration, most /// notably: /// /// * [Align] positions a child according to an [Alignment]. /// /// See also: /// /// * [AlignmentDirectional], which has a horizontal coordinate orientation /// that depends on the [TextDirection]. /// * [AlignmentGeometry], which is an abstract type that is agnostic as to /// whether the horizontal direction depends on the [TextDirection]. class Alignment extends AlignmentGeometry { /// Creates an alignment. /// /// The [x] and [y] arguments must not be null. const Alignment(this.x, this.y) : assert(x != null), assert(y != null); /// The distance fraction in the horizontal direction. /// /// A value of -1.0 corresponds to the leftmost edge. A value of 1.0 /// corresponds to the rightmost edge. Values are not limited to that range; /// values less than -1.0 represent positions to the left of the left edge, /// and values greater than 1.0 represent positions to the right of the right /// edge. final double x; /// The distance fraction in the vertical direction. /// /// A value of -1.0 corresponds to the topmost edge. A value of 1.0 /// corresponds to the bottommost edge. Values are not limited to that range; /// values less than -1.0 represent positions above the top, and values /// greater than 1.0 represent positions below the bottom. final double y; @override double get _x => x; @override double get _start => 0.0; @override double get _y => y; /// The top left corner. static const Alignment topLeft = Alignment(-1.0, -1.0); /// The center point along the top edge. static const Alignment topCenter = Alignment(0.0, -1.0); /// The top right corner. static const Alignment topRight = Alignment(1.0, -1.0); /// The center point along the left edge. static const Alignment centerLeft = Alignment(-1.0, 0.0); /// The center point, both horizontally and vertically. static const Alignment center = Alignment(0.0, 0.0); /// The center point along the right edge. static const Alignment centerRight = Alignment(1.0, 0.0); /// The bottom left corner. static const Alignment bottomLeft = Alignment(-1.0, 1.0); /// The center point along the bottom edge. static const Alignment bottomCenter = Alignment(0.0, 1.0); /// The bottom right corner. static const Alignment bottomRight = Alignment(1.0, 1.0); @override AlignmentGeometry add(AlignmentGeometry other) { if (other is Alignment) { return this + other; } return super.add(other); } /// Returns the difference between two [Alignment]s. Alignment operator -(Alignment other) { return Alignment(x - other.x, y - other.y); } /// Returns the sum of two [Alignment]s. Alignment operator +(Alignment other) { return Alignment(x + other.x, y + other.y); } /// Returns the negation of the given [Alignment]. @override Alignment operator -() { return Alignment(-x, -y); } /// Scales the [Alignment] in each dimension by the given factor. @override Alignment operator *(double other) { return Alignment(x * other, y * other); } /// Divides the [Alignment] in each dimension by the given factor. @override Alignment operator /(double other) { return Alignment(x / other, y / other); } /// Integer divides the [Alignment] in each dimension by the given factor. @override Alignment operator ~/(double other) { return Alignment((x ~/ other).toDouble(), (y ~/ other).toDouble()); } /// Computes the remainder in each dimension by the given factor. @override Alignment operator %(double other) { return Alignment(x % other, y % other); } /// Returns the offset that is this fraction in the direction of the given offset. Offset alongOffset(Offset other) { final double centerX = other.dx / 2.0; final double centerY = other.dy / 2.0; return Offset(centerX + x * centerX, centerY + y * centerY); } /// Returns the offset that is this fraction within the given size. Offset alongSize(Size other) { final double centerX = other.width / 2.0; final double centerY = other.height / 2.0; return Offset(centerX + x * centerX, centerY + y * centerY); } /// Returns the point that is this fraction within the given rect. Offset withinRect(Rect rect) { final double halfWidth = rect.width / 2.0; final double halfHeight = rect.height / 2.0; return Offset( rect.left + halfWidth + x * halfWidth, rect.top + halfHeight + y * halfHeight, ); } /// Returns a rect of the given size, aligned within given rect as specified /// by this alignment. /// /// For example, a 100×100 size inscribed on a 200×200 rect using /// [Alignment.topLeft] would be the 100×100 rect at the top left of /// the 200×200 rect. Rect inscribe(Size size, Rect rect) { final double halfWidthDelta = (rect.width - size.width) / 2.0; final double halfHeightDelta = (rect.height - size.height) / 2.0; return Rect.fromLTWH( rect.left + halfWidthDelta + x * halfWidthDelta, rect.top + halfHeightDelta + y * halfHeightDelta, size.width, size.height, ); } /// Linearly interpolate between two [Alignment]s. /// /// If either is null, this function interpolates from [Alignment.center]. /// /// {@macro dart.ui.shadow.lerp} static Alignment? lerp(Alignment? a, Alignment? b, double t) { assert(t != null); if (a == null && b == null) { return null; } if (a == null) { return Alignment(ui.lerpDouble(0.0, b!.x, t)!, ui.lerpDouble(0.0, b.y, t)!); } if (b == null) { return Alignment(ui.lerpDouble(a.x, 0.0, t)!, ui.lerpDouble(a.y, 0.0, t)!); } return Alignment(ui.lerpDouble(a.x, b.x, t)!, ui.lerpDouble(a.y, b.y, t)!); } @override Alignment resolve(TextDirection? direction) => this; static String _stringify(double x, double y) { if (x == -1.0 && y == -1.0) { return 'Alignment.topLeft'; } if (x == 0.0 && y == -1.0) { return 'Alignment.topCenter'; } if (x == 1.0 && y == -1.0) { return 'Alignment.topRight'; } if (x == -1.0 && y == 0.0) { return 'Alignment.centerLeft'; } if (x == 0.0 && y == 0.0) { return 'Alignment.center'; } if (x == 1.0 && y == 0.0) { return 'Alignment.centerRight'; } if (x == -1.0 && y == 1.0) { return 'Alignment.bottomLeft'; } if (x == 0.0 && y == 1.0) { return 'Alignment.bottomCenter'; } if (x == 1.0 && y == 1.0) { return 'Alignment.bottomRight'; } return 'Alignment(${x.toStringAsFixed(1)}, ' '${y.toStringAsFixed(1)})'; } @override String toString() => _stringify(x, y); } /// An offset that's expressed as a fraction of a [Size], but whose horizontal /// component is dependent on the writing direction. /// /// This can be used to indicate an offset from the left in [TextDirection.ltr] /// text and an offset from the right in [TextDirection.rtl] text without having /// to be aware of the current text direction. /// /// See also: /// /// * [Alignment], a variant that is defined in physical terms (i.e. /// whose horizontal component does not depend on the text direction). class AlignmentDirectional extends AlignmentGeometry { /// Creates a directional alignment. /// /// The [start] and [y] arguments must not be null. const AlignmentDirectional(this.start, this.y) : assert(start != null), assert(y != null); /// The distance fraction in the horizontal direction. /// /// A value of -1.0 corresponds to the edge on the "start" side, which is the /// left side in [TextDirection.ltr] contexts and the right side in /// [TextDirection.rtl] contexts. A value of 1.0 corresponds to the opposite /// edge, the "end" side. Values are not limited to that range; values less /// than -1.0 represent positions beyond the start edge, and values greater than /// 1.0 represent positions beyond the end edge. /// /// This value is normalized into an [Alignment.x] value by the [resolve] /// method. final double start; /// The distance fraction in the vertical direction. /// /// A value of -1.0 corresponds to the topmost edge. A value of 1.0 /// corresponds to the bottommost edge. Values are not limited to that range; /// values less than -1.0 represent positions above the top, and values /// greater than 1.0 represent positions below the bottom. /// /// This value is passed through to [Alignment.y] unmodified by the /// [resolve] method. final double y; @override double get _x => 0.0; @override double get _start => start; @override double get _y => y; /// The top corner on the "start" side. static const AlignmentDirectional topStart = AlignmentDirectional(-1.0, -1.0); /// The center point along the top edge. /// /// Consider using [Alignment.topCenter] instead, as it does not need /// to be [resolve]d to be used. static const AlignmentDirectional topCenter = AlignmentDirectional(0.0, -1.0); /// The top corner on the "end" side. static const AlignmentDirectional topEnd = AlignmentDirectional(1.0, -1.0); /// The center point along the "start" edge. static const AlignmentDirectional centerStart = AlignmentDirectional(-1.0, 0.0); /// The center point, both horizontally and vertically. /// /// Consider using [Alignment.center] instead, as it does not need to /// be [resolve]d to be used. static const AlignmentDirectional center = AlignmentDirectional(0.0, 0.0); /// The center point along the "end" edge. static const AlignmentDirectional centerEnd = AlignmentDirectional(1.0, 0.0); /// The bottom corner on the "start" side. static const AlignmentDirectional bottomStart = AlignmentDirectional(-1.0, 1.0); /// The center point along the bottom edge. /// /// Consider using [Alignment.bottomCenter] instead, as it does not /// need to be [resolve]d to be used. static const AlignmentDirectional bottomCenter = AlignmentDirectional(0.0, 1.0); /// The bottom corner on the "end" side. static const AlignmentDirectional bottomEnd = AlignmentDirectional(1.0, 1.0); @override AlignmentGeometry add(AlignmentGeometry other) { if (other is AlignmentDirectional) { return this + other; } return super.add(other); } /// Returns the difference between two [AlignmentDirectional]s. AlignmentDirectional operator -(AlignmentDirectional other) { return AlignmentDirectional(start - other.start, y - other.y); } /// Returns the sum of two [AlignmentDirectional]s. AlignmentDirectional operator +(AlignmentDirectional other) { return AlignmentDirectional(start + other.start, y + other.y); } /// Returns the negation of the given [AlignmentDirectional]. @override AlignmentDirectional operator -() { return AlignmentDirectional(-start, -y); } /// Scales the [AlignmentDirectional] in each dimension by the given factor. @override AlignmentDirectional operator *(double other) { return AlignmentDirectional(start * other, y * other); } /// Divides the [AlignmentDirectional] in each dimension by the given factor. @override AlignmentDirectional operator /(double other) { return AlignmentDirectional(start / other, y / other); } /// Integer divides the [AlignmentDirectional] in each dimension by the given factor. @override AlignmentDirectional operator ~/(double other) { return AlignmentDirectional((start ~/ other).toDouble(), (y ~/ other).toDouble()); } /// Computes the remainder in each dimension by the given factor. @override AlignmentDirectional operator %(double other) { return AlignmentDirectional(start % other, y % other); } /// Linearly interpolate between two [AlignmentDirectional]s. /// /// If either is null, this function interpolates from [AlignmentDirectional.center]. /// /// {@macro dart.ui.shadow.lerp} static AlignmentDirectional? lerp(AlignmentDirectional? a, AlignmentDirectional? b, double t) { assert(t != null); if (a == null && b == null) { return null; } if (a == null) { return AlignmentDirectional(ui.lerpDouble(0.0, b!.start, t)!, ui.lerpDouble(0.0, b.y, t)!); } if (b == null) { return AlignmentDirectional(ui.lerpDouble(a.start, 0.0, t)!, ui.lerpDouble(a.y, 0.0, t)!); } return AlignmentDirectional(ui.lerpDouble(a.start, b.start, t)!, ui.lerpDouble(a.y, b.y, t)!); } @override Alignment resolve(TextDirection? direction) { assert(direction != null, 'Cannot resolve $runtimeType without a TextDirection.'); switch (direction!) { case TextDirection.rtl: return Alignment(-start, y); case TextDirection.ltr: return Alignment(start, y); } } static String _stringify(double start, double y) { if (start == -1.0 && y == -1.0) { return 'AlignmentDirectional.topStart'; } if (start == 0.0 && y == -1.0) { return 'AlignmentDirectional.topCenter'; } if (start == 1.0 && y == -1.0) { return 'AlignmentDirectional.topEnd'; } if (start == -1.0 && y == 0.0) { return 'AlignmentDirectional.centerStart'; } if (start == 0.0 && y == 0.0) { return 'AlignmentDirectional.center'; } if (start == 1.0 && y == 0.0) { return 'AlignmentDirectional.centerEnd'; } if (start == -1.0 && y == 1.0) { return 'AlignmentDirectional.bottomStart'; } if (start == 0.0 && y == 1.0) { return 'AlignmentDirectional.bottomCenter'; } if (start == 1.0 && y == 1.0) { return 'AlignmentDirectional.bottomEnd'; } return 'AlignmentDirectional(${start.toStringAsFixed(1)}, ' '${y.toStringAsFixed(1)})'; } @override String toString() => _stringify(start, y); } class _MixedAlignment extends AlignmentGeometry { const _MixedAlignment(this._x, this._start, this._y); @override final double _x; @override final double _start; @override final double _y; @override _MixedAlignment operator -() { return _MixedAlignment( -_x, -_start, -_y, ); } @override _MixedAlignment operator *(double other) { return _MixedAlignment( _x * other, _start * other, _y * other, ); } @override _MixedAlignment operator /(double other) { return _MixedAlignment( _x / other, _start / other, _y / other, ); } @override _MixedAlignment operator ~/(double other) { return _MixedAlignment( (_x ~/ other).toDouble(), (_start ~/ other).toDouble(), (_y ~/ other).toDouble(), ); } @override _MixedAlignment operator %(double other) { return _MixedAlignment( _x % other, _start % other, _y % other, ); } @override Alignment resolve(TextDirection? direction) { assert(direction != null, 'Cannot resolve $runtimeType without a TextDirection.'); switch (direction!) { case TextDirection.rtl: return Alignment(_x - _start, _y); case TextDirection.ltr: return Alignment(_x + _start, _y); } } } /// The vertical alignment of text within an input box. /// /// A single [y] value that can range from -1.0 to 1.0. -1.0 aligns to the top /// of an input box so that the top of the first line of text fits within the /// box and its padding. 0.0 aligns to the center of the box. 1.0 aligns so that /// the bottom of the last line of text aligns with the bottom interior edge of /// the input box. /// /// See also: /// /// * [TextField.textAlignVertical], which is passed on to the [InputDecorator]. /// * [CupertinoTextField.textAlignVertical], which behaves in the same way as /// the parameter in TextField. /// * [InputDecorator.textAlignVertical], which defines the alignment of /// prefix, input, and suffix within an [InputDecorator]. class TextAlignVertical { /// Creates a TextAlignVertical from any y value between -1.0 and 1.0. const TextAlignVertical({ required this.y, }) : assert(y != null), assert(y >= -1.0 && y <= 1.0); /// A value ranging from -1.0 to 1.0 that defines the topmost and bottommost /// locations of the top and bottom of the input box. final double y; /// Aligns a TextField's input Text with the topmost location within a /// TextField's input box. static const TextAlignVertical top = TextAlignVertical(y: -1.0); /// Aligns a TextField's input Text to the center of the TextField. static const TextAlignVertical center = TextAlignVertical(y: 0.0); /// Aligns a TextField's input Text with the bottommost location within a /// TextField. static const TextAlignVertical bottom = TextAlignVertical(y: 1.0); @override String toString() { return '${objectRuntimeType(this, 'TextAlignVertical')}(y: $y)'; } }