alignment.dart 22.8 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4 5 6 7 8 9 10 11 12 13
// 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.
///
14 15
/// A property or argument of this type accepts classes created either with [
/// Alignment] and its variants, or [AlignmentDirectional.new].
16
///
17
/// To convert an [AlignmentGeometry] object of indeterminate type into an
18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
/// [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) {
43
    return _MixedAlignment(
44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86
      _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].
87
  ///
88
  /// {@macro dart.ui.shadow.lerp}
89
  static AlignmentGeometry? lerp(AlignmentGeometry? a, AlignmentGeometry? b, double t) {
90
    assert(t != null);
91
    if (a == null && b == null) {
92
      return null;
93 94
    }
    if (a == null) {
95
      return b! * t;
96 97
    }
    if (b == null) {
98
      return a * (1.0 - t);
99 100
    }
    if (a is Alignment && b is Alignment) {
101
      return Alignment.lerp(a, b, t);
102 103
    }
    if (a is AlignmentDirectional && b is AlignmentDirectional) {
104
      return AlignmentDirectional.lerp(a, b, t);
105
    }
106
    return _MixedAlignment(
107 108 109
      ui.lerpDouble(a._x, b._x, t)!,
      ui.lerpDouble(a._start, b._start, t)!,
      ui.lerpDouble(a._y, b._y, t)!,
110 111 112
    );
  }

113
  /// Convert this instance into an [Alignment], which uses literal
114 115 116 117 118 119 120 121
  /// 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.
122
  Alignment resolve(TextDirection? direction);
123 124 125

  @override
  String toString() {
126
    if (_start == 0.0) {
127
      return Alignment._stringify(_x, _y);
128 129
    }
    if (_x == 0.0) {
130
      return AlignmentDirectional._stringify(_start, _y);
131
    }
132
    return '${Alignment._stringify(_x, _y)} + ${AlignmentDirectional._stringify(_start, 0.0)}';
133 134 135
  }

  @override
136
  bool operator ==(Object other) {
137 138 139 140
    return other is AlignmentGeometry
        && other._x == _x
        && other._start == _start
        && other._y == _y;
141 142 143
  }

  @override
144
  int get hashCode => Object.hash(_x, _start, _y);
145 146 147 148 149 150 151 152 153 154 155 156 157 158
}

/// 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
159
/// respect to the rectangle and vertically below the bottom of the rectangle by
160 161
/// the height of the rectangle.
///
162 163 164 165 166 167 168 169 170
/// `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
171 172 173 174 175 176 177 178
/// 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:
///
179
///  * [Align] positions a child according to an [Alignment].
180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221
///
/// 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.
222
  static const Alignment topLeft = Alignment(-1.0, -1.0);
223 224

  /// The center point along the top edge.
225
  static const Alignment topCenter = Alignment(0.0, -1.0);
226 227

  /// The top right corner.
228
  static const Alignment topRight = Alignment(1.0, -1.0);
229 230

  /// The center point along the left edge.
231
  static const Alignment centerLeft = Alignment(-1.0, 0.0);
232 233

  /// The center point, both horizontally and vertically.
234
  static const Alignment center = Alignment(0.0, 0.0);
235 236

  /// The center point along the right edge.
237
  static const Alignment centerRight = Alignment(1.0, 0.0);
238 239

  /// The bottom left corner.
240
  static const Alignment bottomLeft = Alignment(-1.0, 1.0);
241 242

  /// The center point along the bottom edge.
243
  static const Alignment bottomCenter = Alignment(0.0, 1.0);
244 245

  /// The bottom right corner.
246
  static const Alignment bottomRight = Alignment(1.0, 1.0);
247 248 249

  @override
  AlignmentGeometry add(AlignmentGeometry other) {
250
    if (other is Alignment) {
251
      return this + other;
252
    }
253 254 255 256 257
    return super.add(other);
  }

  /// Returns the difference between two [Alignment]s.
  Alignment operator -(Alignment other) {
258
    return Alignment(x - other.x, y - other.y);
259 260 261 262
  }

  /// Returns the sum of two [Alignment]s.
  Alignment operator +(Alignment other) {
263
    return Alignment(x + other.x, y + other.y);
264 265 266 267 268
  }

  /// Returns the negation of the given [Alignment].
  @override
  Alignment operator -() {
269
    return Alignment(-x, -y);
270 271 272 273 274
  }

  /// Scales the [Alignment] in each dimension by the given factor.
  @override
  Alignment operator *(double other) {
275
    return Alignment(x * other, y * other);
276 277 278 279 280
  }

  /// Divides the [Alignment] in each dimension by the given factor.
  @override
  Alignment operator /(double other) {
281
    return Alignment(x / other, y / other);
282 283 284 285 286
  }

  /// Integer divides the [Alignment] in each dimension by the given factor.
  @override
  Alignment operator ~/(double other) {
287
    return Alignment((x ~/ other).toDouble(), (y ~/ other).toDouble());
288 289 290 291 292
  }

  /// Computes the remainder in each dimension by the given factor.
  @override
  Alignment operator %(double other) {
293
    return Alignment(x % other, y % other);
294 295 296 297 298 299
  }

  /// 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;
300
    return Offset(centerX + x * centerX, centerY + y * centerY);
301 302 303 304 305 306
  }

  /// 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;
307
    return Offset(centerX + x * centerX, centerY + y * centerY);
308 309 310 311 312 313
  }

  /// 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;
314
    return Offset(
315 316 317 318 319 320 321 322 323 324 325 326 327 328
      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;
329
    return Rect.fromLTWH(
330 331 332 333 334 335 336 337 338 339
      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].
340
  ///
341
  /// {@macro dart.ui.shadow.lerp}
342
  static Alignment? lerp(Alignment? a, Alignment? b, double t) {
343
    assert(t != null);
344
    if (a == null && b == null) {
345
      return null;
346 347
    }
    if (a == null) {
348
      return Alignment(ui.lerpDouble(0.0, b!.x, t)!, ui.lerpDouble(0.0, b.y, t)!);
349 350
    }
    if (b == null) {
351
      return Alignment(ui.lerpDouble(a.x, 0.0, t)!, ui.lerpDouble(a.y, 0.0, t)!);
352
    }
353
    return Alignment(ui.lerpDouble(a.x, b.x, t)!, ui.lerpDouble(a.y, b.y, t)!);
354 355 356
  }

  @override
357
  Alignment resolve(TextDirection? direction) => this;
358 359

  static String _stringify(double x, double y) {
360
    if (x == -1.0 && y == -1.0) {
361
      return 'Alignment.topLeft';
362 363
    }
    if (x == 0.0 && y == -1.0) {
364
      return 'Alignment.topCenter';
365 366
    }
    if (x == 1.0 && y == -1.0) {
367
      return 'Alignment.topRight';
368 369
    }
    if (x == -1.0 && y == 0.0) {
370
      return 'Alignment.centerLeft';
371 372
    }
    if (x == 0.0 && y == 0.0) {
373
      return 'Alignment.center';
374 375
    }
    if (x == 1.0 && y == 0.0) {
376
      return 'Alignment.centerRight';
377 378
    }
    if (x == -1.0 && y == 1.0) {
379
      return 'Alignment.bottomLeft';
380 381
    }
    if (x == 0.0 && y == 1.0) {
382
      return 'Alignment.bottomCenter';
383 384
    }
    if (x == 1.0 && y == 1.0) {
385
      return 'Alignment.bottomRight';
386
    }
387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422
    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.
  ///
423
  /// This value is normalized into an [Alignment.x] value by the [resolve]
424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447
  /// 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.
448
  static const AlignmentDirectional topStart = AlignmentDirectional(-1.0, -1.0);
449 450 451 452 453

  /// The center point along the top edge.
  ///
  /// Consider using [Alignment.topCenter] instead, as it does not need
  /// to be [resolve]d to be used.
454
  static const AlignmentDirectional topCenter = AlignmentDirectional(0.0, -1.0);
455 456

  /// The top corner on the "end" side.
457
  static const AlignmentDirectional topEnd = AlignmentDirectional(1.0, -1.0);
458 459

  /// The center point along the "start" edge.
460
  static const AlignmentDirectional centerStart = AlignmentDirectional(-1.0, 0.0);
461 462 463 464 465

  /// The center point, both horizontally and vertically.
  ///
  /// Consider using [Alignment.center] instead, as it does not need to
  /// be [resolve]d to be used.
466
  static const AlignmentDirectional center = AlignmentDirectional(0.0, 0.0);
467 468

  /// The center point along the "end" edge.
469
  static const AlignmentDirectional centerEnd = AlignmentDirectional(1.0, 0.0);
470 471

  /// The bottom corner on the "start" side.
472
  static const AlignmentDirectional bottomStart = AlignmentDirectional(-1.0, 1.0);
473 474 475 476 477

  /// The center point along the bottom edge.
  ///
  /// Consider using [Alignment.bottomCenter] instead, as it does not
  /// need to be [resolve]d to be used.
478
  static const AlignmentDirectional bottomCenter = AlignmentDirectional(0.0, 1.0);
479 480

  /// The bottom corner on the "end" side.
481
  static const AlignmentDirectional bottomEnd = AlignmentDirectional(1.0, 1.0);
482 483 484

  @override
  AlignmentGeometry add(AlignmentGeometry other) {
485
    if (other is AlignmentDirectional) {
486
      return this + other;
487
    }
488 489 490 491 492
    return super.add(other);
  }

  /// Returns the difference between two [AlignmentDirectional]s.
  AlignmentDirectional operator -(AlignmentDirectional other) {
493
    return AlignmentDirectional(start - other.start, y - other.y);
494 495 496 497
  }

  /// Returns the sum of two [AlignmentDirectional]s.
  AlignmentDirectional operator +(AlignmentDirectional other) {
498
    return AlignmentDirectional(start + other.start, y + other.y);
499 500 501 502 503
  }

  /// Returns the negation of the given [AlignmentDirectional].
  @override
  AlignmentDirectional operator -() {
504
    return AlignmentDirectional(-start, -y);
505 506 507 508 509
  }

  /// Scales the [AlignmentDirectional] in each dimension by the given factor.
  @override
  AlignmentDirectional operator *(double other) {
510
    return AlignmentDirectional(start * other, y * other);
511 512 513 514 515
  }

  /// Divides the [AlignmentDirectional] in each dimension by the given factor.
  @override
  AlignmentDirectional operator /(double other) {
516
    return AlignmentDirectional(start / other, y / other);
517 518 519 520 521
  }

  /// Integer divides the [AlignmentDirectional] in each dimension by the given factor.
  @override
  AlignmentDirectional operator ~/(double other) {
522
    return AlignmentDirectional((start ~/ other).toDouble(), (y ~/ other).toDouble());
523 524 525 526 527
  }

  /// Computes the remainder in each dimension by the given factor.
  @override
  AlignmentDirectional operator %(double other) {
528
    return AlignmentDirectional(start % other, y % other);
529 530 531 532 533
  }

  /// Linearly interpolate between two [AlignmentDirectional]s.
  ///
  /// If either is null, this function interpolates from [AlignmentDirectional.center].
534
  ///
535
  /// {@macro dart.ui.shadow.lerp}
536
  static AlignmentDirectional? lerp(AlignmentDirectional? a, AlignmentDirectional? b, double t) {
537
    assert(t != null);
538
    if (a == null && b == null) {
539
      return null;
540 541
    }
    if (a == null) {
542
      return AlignmentDirectional(ui.lerpDouble(0.0, b!.start, t)!, ui.lerpDouble(0.0, b.y, t)!);
543 544
    }
    if (b == null) {
545
      return AlignmentDirectional(ui.lerpDouble(a.start, 0.0, t)!, ui.lerpDouble(a.y, 0.0, t)!);
546
    }
547
    return AlignmentDirectional(ui.lerpDouble(a.start, b.start, t)!, ui.lerpDouble(a.y, b.y, t)!);
548 549 550
  }

  @override
551
  Alignment resolve(TextDirection? direction) {
552
    assert(direction != null, 'Cannot resolve $runtimeType without a TextDirection.');
553
    switch (direction!) {
554
      case TextDirection.rtl:
555
        return Alignment(-start, y);
556
      case TextDirection.ltr:
557
        return Alignment(start, y);
558 559 560 561
    }
  }

  static String _stringify(double start, double y) {
562
    if (start == -1.0 && y == -1.0) {
563
      return 'AlignmentDirectional.topStart';
564 565
    }
    if (start == 0.0 && y == -1.0) {
566
      return 'AlignmentDirectional.topCenter';
567 568
    }
    if (start == 1.0 && y == -1.0) {
569
      return 'AlignmentDirectional.topEnd';
570 571
    }
    if (start == -1.0 && y == 0.0) {
572
      return 'AlignmentDirectional.centerStart';
573 574
    }
    if (start == 0.0 && y == 0.0) {
575
      return 'AlignmentDirectional.center';
576 577
    }
    if (start == 1.0 && y == 0.0) {
578
      return 'AlignmentDirectional.centerEnd';
579 580
    }
    if (start == -1.0 && y == 1.0) {
581
      return 'AlignmentDirectional.bottomStart';
582 583
    }
    if (start == 0.0 && y == 1.0) {
584
      return 'AlignmentDirectional.bottomCenter';
585 586
    }
    if (start == 1.0 && y == 1.0) {
587
      return 'AlignmentDirectional.bottomEnd';
588
    }
589
    return 'AlignmentDirectional(${start.toStringAsFixed(1)}, '
590
                                '${y.toStringAsFixed(1)})';
591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610
  }

  @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 -() {
611
    return _MixedAlignment(
612 613 614 615 616 617 618 619
      -_x,
      -_start,
      -_y,
    );
  }

  @override
  _MixedAlignment operator *(double other) {
620
    return _MixedAlignment(
621 622 623 624 625 626 627 628
      _x * other,
      _start * other,
      _y * other,
    );
  }

  @override
  _MixedAlignment operator /(double other) {
629
    return _MixedAlignment(
630 631 632 633 634 635 636 637
      _x / other,
      _start / other,
      _y / other,
    );
  }

  @override
  _MixedAlignment operator ~/(double other) {
638
    return _MixedAlignment(
639 640 641 642 643 644 645 646
      (_x ~/ other).toDouble(),
      (_start ~/ other).toDouble(),
      (_y ~/ other).toDouble(),
    );
  }

  @override
  _MixedAlignment operator %(double other) {
647
    return _MixedAlignment(
648 649 650 651 652 653 654
      _x % other,
      _start % other,
      _y % other,
    );
  }

  @override
655
  Alignment resolve(TextDirection? direction) {
656
    assert(direction != null, 'Cannot resolve $runtimeType without a TextDirection.');
657
    switch (direction!) {
658
      case TextDirection.rtl:
659
        return Alignment(_x - _start, _y);
660
      case TextDirection.ltr:
661
        return Alignment(_x + _start, _y);
662 663 664
    }
  }
}
665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683

/// 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({
684
    required this.y,
685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702
  }) : 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() {
703
    return '${objectRuntimeType(this, 'TextAlignVertical')}(y: $y)';
704 705
  }
}