LCOV - code coverage report
Current view: top level - lib/src/components - position_component.dart (source / functions) Hit Total Coverage
Test: lcov.info Lines: 66 90 73.3 %
Date: 2021-08-10 15:50:53 Functions: 0 0 -

          Line data    Source code
       1             : import 'dart:ui' hide Offset;
       2             : 
       3             : import '../anchor.dart';
       4             : import '../extensions/offset.dart';
       5             : import '../extensions/rect.dart';
       6             : import '../extensions/vector2.dart';
       7             : import '../geometry/rectangle.dart';
       8             : import 'base_component.dart';
       9             : import 'cache/value_cache.dart';
      10             : import 'component.dart';
      11             : import 'mixins/hitbox.dart';
      12             : 
      13             : /// A [Component] implementation that represents a component that has a
      14             : /// specific, possibly dynamic position on the screen.
      15             : ///
      16             : /// It represents a rectangle of dimension [size], on the [position],
      17             : /// rotated around its [anchor] with angle [angle].
      18             : ///
      19             : /// It also uses the [anchor] property to properly position itself.
      20             : ///
      21             : /// A [PositionComponent] can have children. The children are all updated and
      22             : /// rendered automatically when this is updated and rendered.
      23             : /// They are translated by this component's (x,y). They do not need to fit
      24             : /// within this component's (width, height).
      25             : abstract class PositionComponent extends BaseComponent {
      26             :   /// The position of this component on the screen (relative to the anchor).
      27          30 :   Vector2 get position => _position;
      28          15 :   set position(Vector2 position) => _position.setFrom(position);
      29             :   final Vector2 _position;
      30             : 
      31             :   /// X position of this component on the screen (relative to the anchor).
      32          12 :   double get x => _position.x;
      33           3 :   set x(double x) => _position.x = x;
      34             : 
      35             :   /// Y position of this component on the screen (relative to the anchor).
      36          12 :   double get y => _position.y;
      37           3 :   set y(double y) => _position.y = y;
      38             : 
      39             :   /// The size that this component is rendered with before [scale] is applied.
      40             :   /// This is not necessarily the source size of the asset.
      41          38 :   Vector2 get size => _size;
      42           6 :   set size(Vector2 size) => _size.setFrom(size);
      43             :   final Vector2 _size;
      44             : 
      45             :   /// Width (size) that this component is rendered with.
      46           3 :   double get width => scaledSize.x;
      47           6 :   set width(double width) => size.x = width / scale.x;
      48             : 
      49             :   /// Height (size) that this component is rendered with.
      50           3 :   double get height => scaledSize.y;
      51           6 :   set height(double height) => size.y = height / scale.y;
      52             : 
      53             :   /// The scale factor of this component
      54          32 :   Vector2 get scale => _scale;
      55           6 :   set scale(Vector2 scale) => _scale.setFrom(scale);
      56             :   final Vector2 _scale;
      57             : 
      58             :   /// Cache to store the calculated scaled size
      59             :   final ValueCache<Vector2> _scaledSizeCache = ValueCache();
      60             : 
      61             :   /// The size that this component is rendered with after [scale] is applied.
      62           8 :   Vector2 get scaledSize {
      63          40 :     if (!_scaledSizeCache.isCacheValid([scale, size])) {
      64          16 :       _scaledSizeCache.updateCache(
      65          32 :         size.clone()..multiply(scale),
      66          40 :         [scale.clone(), size.clone()],
      67             :       );
      68             :     }
      69          16 :     return _scaledSizeCache.value!;
      70             :   }
      71             : 
      72             :   /// Get the absolute position, with the anchor taken into consideration
      73          24 :   Vector2 get absolutePosition => absoluteParentPosition + position;
      74             : 
      75             :   /// Get the relative top left position regardless of the anchor and angle
      76           5 :   Vector2 get topLeftPosition {
      77          10 :     return anchor.toOtherAnchorPosition(
      78           5 :       position,
      79             :       Anchor.topLeft,
      80           5 :       scaledSize,
      81             :     );
      82             :   }
      83             : 
      84             :   /// Set the top left position regardless of the anchor
      85           1 :   set topLeftPosition(Vector2 position) {
      86           6 :     this.position = position + (anchor.toVector2()..multiply(scaledSize));
      87             :   }
      88             : 
      89             :   /// Get the absolute top left position regardless of whether it is a child or not
      90           4 :   Vector2 get absoluteTopLeftPosition {
      91           4 :     final p = parent;
      92           4 :     if (p is PositionComponent) {
      93           6 :       return p.absoluteTopLeftPosition + topLeftPosition;
      94             :     } else {
      95           4 :       return topLeftPosition;
      96             :     }
      97             :   }
      98             : 
      99             :   /// Get the position that everything in this component is positioned in relation to
     100             :   /// If this component has no parent the absolute parent position is the origin,
     101             :   /// otherwise it's the parents absolute top left position
     102           6 :   Vector2 get absoluteParentPosition {
     103           6 :     final p = parent;
     104           6 :     if (p is PositionComponent) {
     105           2 :       return p.absoluteTopLeftPosition;
     106             :     } else {
     107           6 :       return Vector2.zero();
     108             :     }
     109             :   }
     110             : 
     111             :   /// Get the position of the center of the component's bounding rectangle
     112           7 :   Vector2 get center {
     113          14 :     if (anchor == Anchor.center) {
     114           2 :       return position;
     115             :     } else {
     116          24 :       return anchor.toOtherAnchorPosition(position, Anchor.center, scaledSize)
     117          18 :         ..rotate(angle, center: absolutePosition);
     118             :     }
     119             :   }
     120             : 
     121             :   /// Get the absolute center of the component
     122          24 :   Vector2 get absoluteCenter => absoluteParentPosition + center;
     123             : 
     124             :   /// Angle (with respect to the x-axis) this component should be rendered with.
     125             :   /// It is rotated around its anchor.
     126             :   double angle;
     127             : 
     128             :   /// Anchor point for this component. This is where flame "grabs it".
     129             :   /// The [position] is relative to this point inside the component.
     130             :   /// The [angle] is rotated around this point.
     131             :   Anchor anchor;
     132             : 
     133             :   /// Whether this component should be flipped on the X axis before being rendered.
     134             :   bool renderFlipX = false;
     135             : 
     136             :   /// Whether this component should be flipped ofn the Y axis before being rendered.
     137             :   bool renderFlipY = false;
     138             : 
     139          27 :   PositionComponent({
     140             :     Vector2? position,
     141             :     Vector2? size,
     142             :     Vector2? scale,
     143             :     this.angle = 0.0,
     144             :     this.anchor = Anchor.topLeft,
     145             :     this.renderFlipX = false,
     146             :     this.renderFlipY = false,
     147             :     int? priority,
     148          21 :   })  : _position = position ?? Vector2.zero(),
     149          16 :         _size = size ?? Vector2.zero(),
     150          26 :         _scale = scale ?? Vector2.all(1.0),
     151          27 :         super(priority: priority);
     152             : 
     153           4 :   @override
     154             :   bool containsPoint(Vector2 point) {
     155          12 :     final rectangle = Rectangle.fromRect(toAbsoluteRect(), angle: angle)
     156           8 :       ..position = absoluteCenter;
     157           4 :     return rectangle.containsPoint(point);
     158             :   }
     159             : 
     160           0 :   double angleTo(PositionComponent c) => position.angleTo(c.position);
     161             : 
     162           0 :   double distance(PositionComponent c) => position.distanceTo(c.position);
     163             : 
     164           0 :   @override
     165             :   void renderDebugMode(Canvas canvas) {
     166           0 :     if (this is Hitbox) {
     167           0 :       (this as Hitbox).renderHitboxes(canvas);
     168             :     }
     169           0 :     canvas.drawRect(scaledSize.toRect(), debugPaint);
     170           0 :     debugTextPaint.render(
     171             :       canvas,
     172           0 :       'x: ${x.toStringAsFixed(2)} y:${y.toStringAsFixed(2)}',
     173           0 :       Vector2(-50, -15),
     174             :     );
     175             : 
     176           0 :     final rect = toRect();
     177           0 :     final dx = rect.right;
     178           0 :     final dy = rect.bottom;
     179           0 :     debugTextPaint.render(
     180             :       canvas,
     181           0 :       'x:${dx.toStringAsFixed(2)} y:${dy.toStringAsFixed(2)}',
     182           0 :       Vector2(width - 50, height),
     183             :     );
     184             :   }
     185             : 
     186             :   final Matrix4 _preRenderMatrix = Matrix4.identity();
     187             : 
     188           3 :   @override
     189             :   void preRender(Canvas canvas) {
     190             :     // Move canvas to the components anchor position.
     191          12 :     _preRenderMatrix.translate(x, y);
     192             :     // Rotate canvas around anchor
     193           9 :     _preRenderMatrix.rotateZ(angle);
     194             :     // Scale canvas if it should be scaled
     195          18 :     if (scale.x != 1.0 || scale.y != 1.0) {
     196           0 :       _preRenderMatrix.scale(scale.x, scale.y);
     197             :     }
     198           9 :     canvas.transform(_preRenderMatrix.storage);
     199           6 :     _preRenderMatrix.setIdentity();
     200             : 
     201          12 :     final delta = anchor.toVector2()..multiply(size);
     202          15 :     canvas.translate(-delta.x, -delta.y);
     203             : 
     204             :     // Handle inverted rendering by moving center and flipping.
     205           6 :     if (renderFlipX || renderFlipY) {
     206           0 :       final size = scaledSize;
     207           0 :       _preRenderMatrix.translate(size.x / 2, size.y / 2);
     208           0 :       _preRenderMatrix.scale(
     209           0 :         renderFlipX ? -1.0 : 1.0,
     210           0 :         renderFlipY ? -1.0 : 1.0,
     211             :       );
     212           0 :       canvas.transform(_preRenderMatrix.storage);
     213           0 :       canvas.translate(-size.x / 2, -size.y / 2);
     214           0 :       _preRenderMatrix.setIdentity();
     215             :     }
     216             :   }
     217             : 
     218             :   /// Returns the relative position/size of this component.
     219             :   /// Relative because it might be translated by their parents (which is not considered here).
     220           4 :   Rect toRect() => topLeftPosition.toPositionedRect(scaledSize);
     221             : 
     222             :   /// Returns the absolute position/size of this component.
     223             :   /// Absolute because it takes any possible parent position into consideration.
     224          16 :   Rect toAbsoluteRect() => absoluteTopLeftPosition.toPositionedRect(scaledSize);
     225             : 
     226             :   /// Mutates position and size using the provided [rect] as basis.
     227             :   /// This is a relative rect, same definition that [toRect] use
     228             :   /// (therefore both methods are compatible, i.e. setByRect ∘ toRect = identity).
     229           1 :   void setByRect(Rect rect) {
     230           4 :     size.setValues(rect.width, rect.height);
     231           3 :     topLeftPosition = rect.topLeft.toVector2();
     232             :   }
     233             : }

Generated by: LCOV version 1.15