Skip to content

Commit

Permalink
Added a ButtonComponent (#1146)
Browse files Browse the repository at this point in the history
* Added a ButtonComponent

* Don't import VoidCallback

* Change epsilon since test is flaky

* Added SpriteButtonComponent

* Add changelog entry

* Add docs
  • Loading branch information
spydon committed Nov 29, 2021
1 parent 66f27d7 commit ca8f8d3
Show file tree
Hide file tree
Showing 8 changed files with 226 additions and 3 deletions.
13 changes: 13 additions & 0 deletions doc/other-inputs.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,19 @@ If you want to act upon the button being pressed (which would be the common thin
a callback function as the `onPressed` argument, or you extend the component and override
`onTapDown`, `onTapUp` and/or `onTapCancel` and implement your logic there.

## SpriteButtonComponent

A `SpriteButtonComponent` is a button that is defined by two `Sprite`s, one that represents
when the button is pressed and one that represents when the button is released.

## ButtonComponent

A `ButtonComponent` is a button that is defined by two `PositionComponent`s, one that represents
when the button is pressed and one that represents when the button is released. If you only want
to use sprites for the button, use the [](#SpriteButtonComponent) instead, but this component can be
good to use if you for example want to have a `SpriteAnimationComponent` as a button, or anything
else which isn't a pure sprite.

## Gamepad

Flame has a separate plugin to support external game controllers (gamepads), check
Expand Down
47 changes: 47 additions & 0 deletions examples/lib/stories/input/joystick_advanced_example.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import 'package:flame/input.dart';
import 'package:flame/palette.dart';
import 'package:flame/sprite.dart';
import 'package:flutter/animation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/painting.dart';

import 'joystick_player.dart';
Expand Down Expand Up @@ -112,6 +113,50 @@ class JoystickAdvancedExample extends FlameGame
),
);

// A button, created from a shape, that adds a scale effect to the player
// when it is pressed.
final buttonComponent = ButtonComponent(
button: RectangleComponent(
size: Vector2(185, 50),
paint: Paint()
..color = Colors.orange
..style = PaintingStyle.stroke,
),
buttonDown: RectangleComponent(
size: Vector2(185, 50),
paint: BasicPalette.magenta.paint(),
),
position: Vector2(20, size.y - 280),
onPressed: () => player.add(
ScaleEffect(
scale: Vector2.all(1.5),
duration: 1.0,
isAlternating: true,
),
),
);

final buttonSprites = await images.load('buttons.png');
final buttonSheet = SpriteSheet.fromColumnsAndRows(
image: buttonSprites,
columns: 1,
rows: 2,
);

// A sprite button, created from a shape, that adds a opacity effect to the
// player when it is pressed.
final spriteButtonComponent = SpriteButtonComponent(
button: buttonSheet.getSpriteById(0),
buttonDown: buttonSheet.getSpriteById(1),
position: Vector2(20, size.y - 360),
size: Vector2(185, 50),
onPressed: () => player.add(
OpacityEffect.fadeOut(
isAlternating: true,
),
),
);

final _regular = TextPaint(
style: TextStyle(color: BasicPalette.white.color),
);
Expand Down Expand Up @@ -142,6 +187,8 @@ class JoystickAdvancedExample extends FlameGame
add(joystick);
add(flipButton);
add(flopButton);
add(buttonComponent);
add(spriteButtonComponent);
add(shapeButton);
add(speedWithMargin);
add(directionWithMargin);
Expand Down
4 changes: 4 additions & 0 deletions packages/flame/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# CHANGELOG

## [Next]
- Add `ButtonComponent` backed by two `PositionComponent`s
- Add `SpriteButtonComponent` backed by two `Sprite`s

## [1.0.0-releasecandidate.18]
- Forcing portrait and landscape mode is now supported on web
- Fixed margin calculation in `HudMarginComponent` when using a viewport
Expand Down
2 changes: 2 additions & 0 deletions packages/flame/lib/input.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
export 'src/components/input/button_component.dart';
export 'src/components/input/hud_button_component.dart';
export 'src/components/input/hud_margin_component.dart';
export 'src/components/input/joystick_component.dart';
export 'src/components/input/sprite_button_component.dart';
export 'src/extensions/vector2.dart';
export 'src/game/mixins/keyboard.dart';
export 'src/gestures/detectors.dart';
Expand Down
85 changes: 85 additions & 0 deletions packages/flame/lib/src/components/input/button_component.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import 'package:meta/meta.dart';

import '../../../components.dart';
import '../../../extensions.dart';
import '../../../input.dart';
import '../../gestures/events.dart';

/// The [ButtonComponent] bundles two [PositionComponent]s, one that shows while
/// the button is being pressed, and one that shows otherwise.
///
/// Note: You have to set the [button] in [onLoad] if you are not passing it in
/// through the constructor.
class ButtonComponent extends PositionComponent with Tappable {
late final PositionComponent? button;
late final PositionComponent? buttonDown;

/// Callback for what should happen when the button is pressed.
/// If you want to interact with [onTapUp] or [onTapCancel] it is recommended
/// to extend [ButtonComponent].
void Function()? onPressed;

ButtonComponent({
this.button,
this.buttonDown,
this.onPressed,
Vector2? position,
Vector2? size,
Vector2? scale,
double? angle,
Anchor? anchor,
int? priority,
}) : super(
position: position,
size: size ?? button?.size,
scale: scale,
angle: angle,
anchor: anchor,
priority: priority,
);

@override
@mustCallSuper
void onMount() {
assert(
button != null,
'The button has to either be passed in as an argument or set in onLoad',
);
final idleButton = button;
if (idleButton != null && !contains(idleButton)) {
add(idleButton);
}
}

@override
@mustCallSuper
bool onTapDown(TapDownInfo info) {
if (buttonDown != null) {
if (button != null) {
remove(button!);
}
add(buttonDown!);
}
onPressed?.call();
return false;
}

@override
@mustCallSuper
bool onTapUp(TapUpInfo info) {
onTapCancel();
return true;
}

@override
@mustCallSuper
bool onTapCancel() {
if (buttonDown != null) {
remove(buttonDown!);
if (button != null) {
add(button!);
}
}
return false;
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import 'package:flutter/rendering.dart' show EdgeInsets, VoidCallback;
import 'package:flutter/rendering.dart' show EdgeInsets;
import 'package:meta/meta.dart';

import '../../../components.dart';
Expand All @@ -18,7 +18,7 @@ class HudButtonComponent extends HudMarginComponent with Tappable {
/// Callback for what should happen when the button is pressed.
/// If you want to interact with [onTapUp] or [onTapCancel] it is recommended
/// to extend [HudButtonComponent].
VoidCallback? onPressed;
void Function()? onPressed;

HudButtonComponent({
this.button,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import '../../../components.dart';
import '../../../extensions.dart';
import '../../../input.dart';

enum _ButtonState {
up,
down,
}

/// The [SpriteButtonComponent] bundles two [Sprite]s, one that shows while
/// the button is being pressed, and one that shows otherwise.
///
/// Note: You have to set the [button] in [onLoad] if you are not passing it in
/// through the constructor.
class SpriteButtonComponent extends SpriteGroupComponent<_ButtonState>
with Tappable {
/// Callback for what should happen when the button is pressed.
void Function()? onPressed;

Sprite? button;
Sprite? buttonDown;

SpriteButtonComponent({
this.button,
this.buttonDown,
this.onPressed,
Vector2? position,
Vector2? size,
Vector2? scale,
double? angle,
Anchor? anchor,
int? priority,
}) : super(
current: _ButtonState.up,
position: position,
size: size ?? button?.originalSize,
scale: scale,
angle: angle,
anchor: anchor,
priority: priority,
);

@override
void onMount() {
assert(
button != null,
'The button sprite has to be set either in onLoad or in the constructor',
);
sprites = {_ButtonState.up: button!};
sprites![_ButtonState.down] = buttonDown ?? button!;
super.onMount();
}

@override
bool onTapDown(_) {
current = _ButtonState.down;
return false;
}

@override
bool onTapUp(_) {
onTapCancel();
return false;
}

@override
bool onTapCancel() {
current = _ButtonState.up;
onPressed?.call();
return false;
}
}
2 changes: 1 addition & 1 deletion packages/flame/test/effects2/opacity_effect_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ void main() {
// It should change from 0-255 in 1s so it will change alpha with an
// average of 255/100=2.5 per tick, which should not result in a need of
// an epsilon value this high.
expectDouble(component.getOpacity(), 1.0, epsilon: 6 * _epsilon);
expectDouble(component.getOpacity(), 1.0, epsilon: 10 * _epsilon);
});
});
}

0 comments on commit ca8f8d3

Please sign in to comment.