Skip to content

Commit

Permalink
feat: Added AlignComponent layout component (#2350)
Browse files Browse the repository at this point in the history
This PR adds first layout component: AlignComponent, an equivalent of Align widget in Flutter.

AlignComponent sizes itself to its parent, and then keeps its child aligned to the specified anchor within its own bounding box.

Also adding onParentResize() lifecycle method, which is similar to onGameResize, but fires whenever the parent of the current component changes its size for any reason. (FlameGame is assumed to have the size canvasSize, and will invoke onParentResize whenever the canvas size changes).

Additional layout components are planned to be added in future PRs.
  • Loading branch information
st-pasha authored Mar 2, 2023
1 parent 9d18248 commit 4f5e56f
Show file tree
Hide file tree
Showing 18 changed files with 403 additions and 6 deletions.
3 changes: 3 additions & 0 deletions doc/flame/components.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ Every `Component` has a few methods that you can optionally implement, which are
The `onGameResize` method is called whenever the screen is resized, and also when this component
gets added into the component tree, before the `onMount`.

The `onParentResize` method is similar: it is also called when the component is mounted into the
component tree, and also whenever the parent of the current component changes its size.

The `onRemove` method can be overridden to run code before the component is removed from the game,
it is only run once even if the component is removed both by using the parents remove method and
the `Component` remove method.
Expand Down
2 changes: 2 additions & 0 deletions doc/flame/flame.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
- [Camera Component](camera_component.md)
- [Inputs](inputs/inputs.md)
- [Rendering](rendering/rendering.md)
- [Layout](layout/layout.md)
- [Overlays](overlays.md)
- [Other](other/other.md)

Expand All @@ -30,5 +31,6 @@ Camera & Viewport <camera_and_viewport.md>
Camera Component <camera_component.md>
Inputs <inputs/inputs.md>
Rendering <rendering/rendering.md>
Layout <layout/layout.md>
Other <other/other.md>
```
10 changes: 10 additions & 0 deletions doc/flame/layout/align_component.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# AlignComponent

```{dartdoc}
:package: flame
:symbol: AlignComponent
:file: src/layout/align_component.dart
[Align]: https://api.flutter.dev/flutter/widgets/Align-class.html
[Alignment]: https://api.flutter.dev/flutter/painting/Alignment-class.html
```
7 changes: 7 additions & 0 deletions doc/flame/layout/layout.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Layout

```{toctree}
:hidden:
AlignComponent <align_component.md>
```
2 changes: 2 additions & 0 deletions examples/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import 'package:examples/stories/effects/effects.dart';
import 'package:examples/stories/experimental/experimental.dart';
import 'package:examples/stories/games/games.dart';
import 'package:examples/stories/input/input.dart';
import 'package:examples/stories/layout/layout.dart';
import 'package:examples/stories/parallax/parallax.dart';
import 'package:examples/stories/rendering/rendering.dart';
import 'package:examples/stories/sprites/sprites.dart';
Expand Down Expand Up @@ -39,6 +40,7 @@ void main() {
addEffectsStories(dashbook);
addExperimentalStories(dashbook);
addInputStories(dashbook);
addLayoutStories(dashbook);
addParallaxStories(dashbook);
addRenderingStories(dashbook);
addTiledStories(dashbook);
Expand Down
2 changes: 1 addition & 1 deletion examples/lib/stories/components/game_in_game_example.dart
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ class GameChangeTimer extends TimerComponent
void onTick() {
final child = gameRef.draggablesGame.square;
final newParent = child.parent == gameRef.draggablesGame
? gameRef.composedGame.parentSquare
? gameRef.composedGame.parentSquare as Component
: gameRef.draggablesGame;
child.changeParent(newParent);
}
Expand Down
100 changes: 100 additions & 0 deletions examples/lib/stories/layout/align_component.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import 'package:flame/components.dart';
import 'package:flame/effects.dart';
import 'package:flame/game.dart';
import 'package:flame/layout.dart';

class AlignComponentExample extends FlameGame {
static const String description = '''
In this example the AlignComponent is used to arrange the circles
so that there is one in the middle and 8 more surrounding it in
the shape of a diamond.
The arrangement will remain intact if you change the window size.
''';

@override
void onLoad() {
addAll([
AlignComponent(
child: CircleComponent(
radius: 40,
children: [
SizeEffect.by(
Vector2.all(25),
EffectController(
infinite: true,
duration: 0.75,
reverseDuration: 0.5,
),
),
AlignComponent(
alignment: Anchor.topCenter,
child: CircleComponent(
radius: 10,
anchor: Anchor.bottomCenter,
),
keepChildAnchor: true,
),
AlignComponent(
alignment: Anchor.bottomCenter,
child: CircleComponent(
radius: 10,
anchor: Anchor.topCenter,
),
keepChildAnchor: true,
),
AlignComponent(
alignment: Anchor.centerLeft,
child: CircleComponent(
radius: 10,
anchor: Anchor.centerRight,
),
keepChildAnchor: true,
),
AlignComponent(
alignment: Anchor.centerRight,
child: CircleComponent(
radius: 10,
anchor: Anchor.centerLeft,
),
keepChildAnchor: true,
),
],
),
alignment: Anchor.center,
),
AlignComponent(
child: CircleComponent(radius: 30),
alignment: Anchor.topCenter,
),
AlignComponent(
child: CircleComponent(radius: 30),
alignment: Anchor.bottomCenter,
),
AlignComponent(
child: CircleComponent(radius: 30),
alignment: Anchor.centerLeft,
),
AlignComponent(
child: CircleComponent(radius: 30),
alignment: Anchor.centerRight,
),
AlignComponent(
child: CircleComponent(radius: 10),
alignment: const Anchor(0.25, 0.25),
),
AlignComponent(
child: CircleComponent(radius: 10),
alignment: const Anchor(0.25, 0.75),
),
AlignComponent(
child: CircleComponent(radius: 10),
alignment: const Anchor(0.75, 0.25),
),
AlignComponent(
child: CircleComponent(radius: 10),
alignment: const Anchor(0.75, 0.75),
),
]);
}
}
13 changes: 13 additions & 0 deletions examples/lib/stories/layout/layout.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import 'package:dashbook/dashbook.dart';
import 'package:examples/commons/commons.dart';
import 'package:examples/stories/layout/align_component.dart';
import 'package:flame/game.dart';

void addLayoutStories(Dashbook dashbook) {
dashbook.storiesOf('Layout').add(
'AlignComponent',
(_) => GameWidget(game: AlignComponentExample()),
codeLink: baseLink('layout/align_component.dart'),
info: AlignComponentExample.description,
);
}
1 change: 1 addition & 0 deletions packages/flame/lib/layout.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export 'src/layout/align_component.dart' show AlignComponent;
3 changes: 3 additions & 0 deletions packages/flame/lib/src/camera/viewport.dart
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,9 @@ abstract class Viewport extends Component
camera.viewfinder.onViewportResize();
}
onViewportResize();
if (hasChildren) {
children.forEach((child) => child.onParentResize(_size));
}
}

/// Reference to the parent camera.
Expand Down
12 changes: 12 additions & 0 deletions packages/flame/lib/src/components/core/component.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import 'package:flame/src/components/core/component_tree_root.dart';
import 'package:flame/src/components/core/position_type.dart';
import 'package:flame/src/components/mixins/coordinate_transform.dart';
import 'package:flame/src/components/mixins/has_game_ref.dart';
import 'package:flame/src/effects/provider_interfaces.dart';
import 'package:flame/src/game/flame_game.dart';
import 'package:flame/src/game/game.dart';
import 'package:flame/src/gestures/events.dart';
Expand Down Expand Up @@ -474,6 +475,14 @@ class Component {
/// [onMount] call before.
void onRemove() {}

/// Called whenever the parent of this component changes size; and also once
/// before [onMount].
///
/// The component may change its own size or perform layout in response to
/// this call. If the component changes size, then it should call
/// [onParentResize] for all its children.
void onParentResize(Vector2 maxSize) {}

/// This method is called periodically by the game engine to request that your
/// component updates itself.
///
Expand Down Expand Up @@ -830,6 +839,9 @@ class Component {
assert(isLoaded && !isLoading);
_setMountingBit();
onGameResize(_parent!.findGame()!.canvasSize);
if (_parent is ReadonlySizeProvider) {
onParentResize((_parent! as ReadonlySizeProvider).size);
}
if (isRemoved) {
_clearRemovedBit();
} else if (isRemoving) {
Expand Down
11 changes: 10 additions & 1 deletion packages/flame/lib/src/components/position_component.dart
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ class PositionComponent extends Component
AngleProvider,
PositionProvider,
ScaleProvider,
SizeProvider,
CoordinateTransform {
PositionComponent({
Vector2? position,
Expand Down Expand Up @@ -184,8 +185,16 @@ class PositionComponent extends Component
/// This property can be reassigned at runtime, although this is not
/// recommended. Instead, in order to make the [PositionComponent] larger
/// or smaller, change its [scale].
@override
NotifyingVector2 get size => _size;
set size(Vector2 size) => _size.setFrom(size);

@override
set size(Vector2 size) {
_size.setFrom(size);
if (hasChildren) {
children.forEach((child) => child.onParentResize(_size));
}
}

/// The width of the component in local coordinates. Note that the object
/// may visually appear larger or smaller due to application of [scale].
Expand Down
9 changes: 7 additions & 2 deletions packages/flame/lib/src/effects/provider_interfaces.dart
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,14 @@ abstract class AnchorProvider {
set anchor(Anchor value);
}

/// Interface for a component that can be affected by size effects.
abstract class SizeProvider {
/// Interface for a class that has [size] property which can be read but not
/// modified.
abstract class ReadonlySizeProvider {
Vector2 get size;
}

/// Interface for a component that can be affected by size effects.
abstract class SizeProvider extends ReadonlySizeProvider {
set size(Vector2 value);
}

Expand Down
6 changes: 5 additions & 1 deletion packages/flame/lib/src/game/flame_game.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import 'dart:ui';

import 'package:flame/components.dart';
import 'package:flame/src/components/core/component_tree_root.dart';
import 'package:flame/src/effects/provider_interfaces.dart';
import 'package:flame/src/game/camera/camera.dart';
import 'package:flame/src/game/camera/camera_wrapper.dart';
import 'package:flame/src/game/game.dart';
Expand All @@ -15,7 +16,9 @@ import 'package:meta/meta.dart';
///
/// This is the recommended base class to use for most games made with Flame.
/// It is based on the Flame Component System (also known as FCS).
class FlameGame extends ComponentTreeRoot with Game {
class FlameGame extends ComponentTreeRoot
with Game
implements ReadonlySizeProvider {
FlameGame({
super.children,
Camera? camera,
Expand Down Expand Up @@ -111,6 +114,7 @@ class FlameGame extends ComponentTreeRoot with Game {
// there is no way to explicitly call the [Component]'s implementation,
// we propagate the event to [FlameGame]'s children manually.
handleResize(canvasSize);
children.forEach((child) => child.onParentResize(canvasSize));
}

/// Ensure that all pending tree operations finish.
Expand Down
Loading

0 comments on commit 4f5e56f

Please sign in to comment.