fractional_offset.dart 7.04 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

5

6 7
import 'dart:ui' as ui show lerpDouble;

8 9
import 'package:flutter/foundation.dart';

10
import 'alignment.dart';
11 12
import 'basic_types.dart';

13
/// An offset that's expressed as a fraction of a [Size].
14
///
15 16 17 18 19 20
/// `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].
21
///
22
/// The [FractionalOffset] class specifies offsets in terms of a distance from
23
/// the top left, regardless of the [TextDirection].
24
///
25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
/// ## 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].
///
50 51
/// See also:
///
52 53
///  * [Alignment], which uses a coordinate system based on the center of the
///    rectangle instead of the top left corner of the rectangle.
54
@immutable
55
class FractionalOffset extends Alignment {
56 57 58
  /// Creates a fractional offset.
  ///
  /// The [dx] and [dy] arguments must not be null.
59
  const FractionalOffset(double dx, double dy)
60
    : assert(dx != null),
61 62
      assert(dy != null),
      super(dx * 2.0 - 1.0, dy * 2.0 - 1.0);
63

64 65 66 67
  /// 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].
68 69 70
  factory FractionalOffset.fromOffsetAndSize(Offset offset, Size size) {
    assert(size != null);
    assert(offset != null);
71
    return FractionalOffset(
72 73 74 75
      offset.dx / size.width,
      offset.dy / size.height,
    );
  }
76 77 78 79 80 81 82 83 84 85 86

  /// 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) {
87
    return FractionalOffset.fromOffsetAndSize(
88 89 90 91 92
      offset - rect.topLeft,
      rect.size,
    );
  }

93 94
  /// The distance fraction in the horizontal direction.
  ///
95
  /// A value of 0.0 corresponds to the leftmost edge. A value of 1.0
96 97 98 99
  /// 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.
100
  double get dx => (x + 1.0) / 2.0;
101

102 103
  /// The distance fraction in the vertical direction.
  ///
104 105
  /// 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
106
  /// values represent positions above the top, and values greater than 1.0
107
  /// represent positions below the bottom.
108
  double get dy => (y + 1.0) / 2.0;
109

110
  /// The top left corner.
111
  static const FractionalOffset topLeft = FractionalOffset(0.0, 0.0);
112 113

  /// The center point along the top edge.
114
  static const FractionalOffset topCenter = FractionalOffset(0.5, 0.0);
115 116

  /// The top right corner.
117
  static const FractionalOffset topRight = FractionalOffset(1.0, 0.0);
118

119
  /// The center point along the left edge.
120
  static const FractionalOffset centerLeft = FractionalOffset(0.0, 0.5);
121 122

  /// The center point, both horizontally and vertically.
123
  static const FractionalOffset center = FractionalOffset(0.5, 0.5);
124 125

  /// The center point along the right edge.
126
  static const FractionalOffset centerRight = FractionalOffset(1.0, 0.5);
127

128
  /// The bottom left corner.
129
  static const FractionalOffset bottomLeft = FractionalOffset(0.0, 1.0);
130 131

  /// The center point along the bottom edge.
132
  static const FractionalOffset bottomCenter = FractionalOffset(0.5, 1.0);
133 134

  /// The bottom right corner.
135
  static const FractionalOffset bottomRight = FractionalOffset(1.0, 1.0);
136

137 138 139 140
  @override
  Alignment operator -(Alignment other) {
    if (other is! FractionalOffset)
      return super - other;
141
    return FractionalOffset(dx - other.dx, dy - other.dy);
142 143 144 145 146 147
  }

  @override
  Alignment operator +(Alignment other) {
    if (other is! FractionalOffset)
      return super + other;
148
    return FractionalOffset(dx + other.dx, dy + other.dy);
149 150 151 152
  }

  @override
  FractionalOffset operator -() {
153
    return FractionalOffset(-dx, -dy);
154 155 156 157
  }

  @override
  FractionalOffset operator *(double other) {
158
    return FractionalOffset(dx * other, dy * other);
159 160 161 162
  }

  @override
  FractionalOffset operator /(double other) {
163
    return FractionalOffset(dx / other, dy / other);
164 165 166 167
  }

  @override
  FractionalOffset operator ~/(double other) {
168
    return FractionalOffset((dx ~/ other).toDouble(), (dy ~/ other).toDouble());
169 170 171 172
  }

  @override
  FractionalOffset operator %(double other) {
173
    return FractionalOffset(dx % other, dy % other);
174 175
  }

176 177 178
  /// Linearly interpolate between two [FractionalOffset]s.
  ///
  /// If either is null, this function interpolates from [FractionalOffset.center].
179
  ///
180
  /// {@macro dart.ui.shadow.lerp}
181
  static FractionalOffset? lerp(FractionalOffset? a, FractionalOffset? b, double t) {
182
    assert(t != null);
183 184 185
    if (a == null && b == null)
      return null;
    if (a == null)
186
      return FractionalOffset(ui.lerpDouble(0.5, b!.dx, t)!, ui.lerpDouble(0.5, b.dy, t)!);
187
    if (b == null)
188 189
      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)!);
190
  }
191 192 193 194 195 196

  @override
  String toString() {
    return 'FractionalOffset(${dx.toStringAsFixed(1)}, '
                            '${dy.toStringAsFixed(1)})';
  }
197
}