mirror of
				https://github.com/flame-engine/flame.git
				synced 2025-11-04 04:47:13 +08:00 
			
		
		
		
	feat: Add advanced button component (#2742)
New button with support for multiple states: <img width="278" alt="image" src="https://github.com/flame-engine/flame/assets/18004353/041c1105-8991-4976-b1a2-0553c149ec4e">
This commit is contained in:
		@ -142,3 +142,33 @@ else which isn't a pure sprite.
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
Flame has a separate plugin to support external game controllers (gamepads), check
 | 
					Flame has a separate plugin to support external game controllers (gamepads), check
 | 
				
			||||||
[here](https://github.com/flame-engine/flame_gamepad) for more information.
 | 
					[here](https://github.com/flame-engine/flame_gamepad) for more information.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## AdvancedButtonComponent
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The `AdvancedButtonComponent` have separate states for each of the different pointer phases.
 | 
				
			||||||
 | 
					The skin can be customized for each state and each skin is represented by a `PositionComponent`.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					These are the fields that can be used to customize the looks of the `AdvancedButtonComponent`:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- `defaultSkin`: Component that will be displayed by default on the button.
 | 
				
			||||||
 | 
					- `downSkin`: Component displayed when the button is clicked or tapped.
 | 
				
			||||||
 | 
					- `hoverSkin`: Component displayed when the button is hovered. (desktop and web).
 | 
				
			||||||
 | 
					- `defaultLabel`: Component shown on top of skins. Automatically aligned to center.
 | 
				
			||||||
 | 
					- `disabledSkin`: Component displayed when button is disabled.
 | 
				
			||||||
 | 
					- `disabledLabel`: Component shown on top of skins when button is disabled.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## ToggleButtonComponent
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The [ToggleButtonComponent] is an [AdvancedButtonComponent] that can switch between selected
 | 
				
			||||||
 | 
					and not selected.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					In addition to the already existing skins, the [ToggleButtonComponent] contains the following skins:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- `defaultSelectedSkin`: The component to display when the button is selected.
 | 
				
			||||||
 | 
					- `downAndSelectedSkin`: The component that is displayed when the selectable button is selected and
 | 
				
			||||||
 | 
					  pressed.
 | 
				
			||||||
 | 
					- `hoverAndSelectedSkin`: Hover on selectable and selected button (desktop and web).
 | 
				
			||||||
 | 
					- `disabledAndSelectedSkin`: For when the button is selected and in the disabled state.
 | 
				
			||||||
 | 
					- `defaultSelectedLabel`: Component shown on top of the skins when button is selected.
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										125
									
								
								examples/lib/stories/input/advanced_button_example.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										125
									
								
								examples/lib/stories/input/advanced_button_example.dart
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,125 @@
 | 
				
			|||||||
 | 
					import 'package:flame/components.dart';
 | 
				
			||||||
 | 
					import 'package:flame/game.dart';
 | 
				
			||||||
 | 
					import 'package:flame/palette.dart';
 | 
				
			||||||
 | 
					import 'package:flutter/painting.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class AdvancedButtonExample extends FlameGame {
 | 
				
			||||||
 | 
					  static const String description =
 | 
				
			||||||
 | 
					      '''This example shows how you can use a button with different states''';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  Future<void> onLoad() async {
 | 
				
			||||||
 | 
					    final defaultButton = DefaultButton();
 | 
				
			||||||
 | 
					    defaultButton.position = Vector2(50, 50);
 | 
				
			||||||
 | 
					    defaultButton.size = Vector2(250, 50);
 | 
				
			||||||
 | 
					    add(defaultButton);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    final disableButton = DisableButton();
 | 
				
			||||||
 | 
					    disableButton.isDisabled = true;
 | 
				
			||||||
 | 
					    disableButton.position = Vector2(400, 50);
 | 
				
			||||||
 | 
					    disableButton.size = defaultButton.size;
 | 
				
			||||||
 | 
					    add(disableButton);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    final toggleButton = ToggleButton();
 | 
				
			||||||
 | 
					    toggleButton.position = Vector2(50, 150);
 | 
				
			||||||
 | 
					    toggleButton.size = defaultButton.size;
 | 
				
			||||||
 | 
					    add(toggleButton);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ToggleButton extends ToggleButtonComponent {
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  Future<void> onLoad() async {
 | 
				
			||||||
 | 
					    super.onLoad();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    defaultLabel = TextComponent(
 | 
				
			||||||
 | 
					      text: 'Toggle button',
 | 
				
			||||||
 | 
					      textRenderer: TextPaint(
 | 
				
			||||||
 | 
					        style: TextStyle(
 | 
				
			||||||
 | 
					          fontSize: 24,
 | 
				
			||||||
 | 
					          color: BasicPalette.white.color,
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					      ),
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    defaultSelectedLabel = TextComponent(
 | 
				
			||||||
 | 
					      text: 'Toggle button',
 | 
				
			||||||
 | 
					      textRenderer: TextPaint(
 | 
				
			||||||
 | 
					        style: TextStyle(
 | 
				
			||||||
 | 
					          fontSize: 24,
 | 
				
			||||||
 | 
					          color: BasicPalette.red.color,
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					      ),
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    defaultSkin = RoundedRectComponent()
 | 
				
			||||||
 | 
					      ..setColor(const Color.fromRGBO(0, 200, 0, 1));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    hoverSkin = RoundedRectComponent()
 | 
				
			||||||
 | 
					      ..setColor(const Color.fromRGBO(0, 180, 0, 1));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    downSkin = RoundedRectComponent()
 | 
				
			||||||
 | 
					      ..setColor(const Color.fromRGBO(0, 100, 0, 1));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    defaultSelectedSkin = RoundedRectComponent()
 | 
				
			||||||
 | 
					      ..setColor(const Color.fromRGBO(0, 0, 200, 1));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    hoverAndSelectedSkin = RoundedRectComponent()
 | 
				
			||||||
 | 
					      ..setColor(const Color.fromRGBO(0, 0, 180, 1));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    downAndSelectedSkin = RoundedRectComponent()
 | 
				
			||||||
 | 
					      ..setColor(const Color.fromRGBO(0, 0, 100, 1));
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class DefaultButton extends AdvancedButtonComponent {
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  Future<void> onLoad() async {
 | 
				
			||||||
 | 
					    super.onLoad();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    defaultLabel = TextComponent(text: 'Default button');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    defaultSkin = RoundedRectComponent()
 | 
				
			||||||
 | 
					      ..setColor(const Color.fromRGBO(0, 200, 0, 1));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    hoverSkin = RoundedRectComponent()
 | 
				
			||||||
 | 
					      ..setColor(const Color.fromRGBO(0, 180, 0, 1));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    downSkin = RoundedRectComponent()
 | 
				
			||||||
 | 
					      ..setColor(const Color.fromRGBO(0, 100, 0, 1));
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class DisableButton extends AdvancedButtonComponent {
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  Future<void> onLoad() async {
 | 
				
			||||||
 | 
					    super.onLoad();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    disabledLabel = TextComponent(text: 'Disabled button');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    defaultSkin = RoundedRectComponent()
 | 
				
			||||||
 | 
					      ..setColor(const Color.fromRGBO(0, 255, 0, 1));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    disabledSkin = RoundedRectComponent()
 | 
				
			||||||
 | 
					      ..setColor(const Color.fromRGBO(100, 100, 100, 1));
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class RoundedRectComponent extends PositionComponent with HasPaint {
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  void render(Canvas canvas) {
 | 
				
			||||||
 | 
					    canvas.drawRRect(
 | 
				
			||||||
 | 
					      RRect.fromLTRBAndCorners(
 | 
				
			||||||
 | 
					        0,
 | 
				
			||||||
 | 
					        0,
 | 
				
			||||||
 | 
					        width,
 | 
				
			||||||
 | 
					        height,
 | 
				
			||||||
 | 
					        topLeft: Radius.circular(height),
 | 
				
			||||||
 | 
					        topRight: Radius.circular(height),
 | 
				
			||||||
 | 
					        bottomRight: Radius.circular(height),
 | 
				
			||||||
 | 
					        bottomLeft: Radius.circular(height),
 | 
				
			||||||
 | 
					      ),
 | 
				
			||||||
 | 
					      paint,
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -1,5 +1,6 @@
 | 
				
			|||||||
import 'package:dashbook/dashbook.dart';
 | 
					import 'package:dashbook/dashbook.dart';
 | 
				
			||||||
import 'package:examples/commons/commons.dart';
 | 
					import 'package:examples/commons/commons.dart';
 | 
				
			||||||
 | 
					import 'package:examples/stories/input/advanced_button_example.dart';
 | 
				
			||||||
import 'package:examples/stories/input/double_tap_callbacks_example.dart';
 | 
					import 'package:examples/stories/input/double_tap_callbacks_example.dart';
 | 
				
			||||||
import 'package:examples/stories/input/draggables_example.dart';
 | 
					import 'package:examples/stories/input/draggables_example.dart';
 | 
				
			||||||
import 'package:examples/stories/input/gesture_hitboxes_example.dart';
 | 
					import 'package:examples/stories/input/gesture_hitboxes_example.dart';
 | 
				
			||||||
@ -129,5 +130,11 @@ void addInputStories(Dashbook dashbook) {
 | 
				
			|||||||
      (_) => GameWidget(game: JoystickAdvancedExample()),
 | 
					      (_) => GameWidget(game: JoystickAdvancedExample()),
 | 
				
			||||||
      codeLink: baseLink('input/joystick_advanced_example.dart'),
 | 
					      codeLink: baseLink('input/joystick_advanced_example.dart'),
 | 
				
			||||||
      info: JoystickAdvancedExample.description,
 | 
					      info: JoystickAdvancedExample.description,
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    ..add(
 | 
				
			||||||
 | 
					      'Advanced Button',
 | 
				
			||||||
 | 
					      (_) => GameWidget(game: AdvancedButtonExample()),
 | 
				
			||||||
 | 
					      codeLink: baseLink('input/advanced_button_example.dart'),
 | 
				
			||||||
 | 
					      info: AdvancedButtonExample.description,
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -13,8 +13,10 @@ export 'src/components/core/position_type.dart';
 | 
				
			|||||||
export 'src/components/custom_painter_component.dart';
 | 
					export 'src/components/custom_painter_component.dart';
 | 
				
			||||||
export 'src/components/fps_component.dart';
 | 
					export 'src/components/fps_component.dart';
 | 
				
			||||||
export 'src/components/fps_text_component.dart';
 | 
					export 'src/components/fps_text_component.dart';
 | 
				
			||||||
 | 
					export 'src/components/input/advanced_button_component.dart';
 | 
				
			||||||
export 'src/components/input/joystick_component.dart';
 | 
					export 'src/components/input/joystick_component.dart';
 | 
				
			||||||
export 'src/components/input/keyboard_listener_component.dart';
 | 
					export 'src/components/input/keyboard_listener_component.dart';
 | 
				
			||||||
 | 
					export 'src/components/input/toggle_button_component.dart';
 | 
				
			||||||
export 'src/components/isometric_tile_map_component.dart';
 | 
					export 'src/components/isometric_tile_map_component.dart';
 | 
				
			||||||
export 'src/components/mixins/component_viewport_margin.dart';
 | 
					export 'src/components/mixins/component_viewport_margin.dart';
 | 
				
			||||||
export 'src/components/mixins/coordinate_transform.dart';
 | 
					export 'src/components/mixins/coordinate_transform.dart';
 | 
				
			||||||
 | 
				
			|||||||
@ -0,0 +1,292 @@
 | 
				
			|||||||
 | 
					import 'package:flame/components.dart';
 | 
				
			||||||
 | 
					import 'package:flame/events.dart';
 | 
				
			||||||
 | 
					import 'package:flame/layout.dart';
 | 
				
			||||||
 | 
					import 'package:flutter/foundation.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// The [AdvancedButtonComponent] has different skins for
 | 
				
			||||||
 | 
					/// different button states.
 | 
				
			||||||
 | 
					/// The [defaultSkin] must be added to the constructor or
 | 
				
			||||||
 | 
					/// if you are inheriting - defined in the onLod method.
 | 
				
			||||||
 | 
					///
 | 
				
			||||||
 | 
					/// The label is a [PositionComponent] and is added
 | 
				
			||||||
 | 
					/// to the foreground of the button. The label is automatically aligned to
 | 
				
			||||||
 | 
					/// the center of the button.
 | 
				
			||||||
 | 
					///
 | 
				
			||||||
 | 
					/// Note: You have to set the skins that you want to use ([defaultSkin],
 | 
				
			||||||
 | 
					/// [downSkin], [hoverSkin], [disabledSkin], [defaultLabel]) in [onLoad]
 | 
				
			||||||
 | 
					/// if you are not passing them in through the constructor.
 | 
				
			||||||
 | 
					class AdvancedButtonComponent extends PositionComponent
 | 
				
			||||||
 | 
					    with HoverCallbacks, TapCallbacks {
 | 
				
			||||||
 | 
					  AdvancedButtonComponent({
 | 
				
			||||||
 | 
					    this.onPressed,
 | 
				
			||||||
 | 
					    this.onChangeState,
 | 
				
			||||||
 | 
					    PositionComponent? defaultSkin,
 | 
				
			||||||
 | 
					    PositionComponent? downSkin,
 | 
				
			||||||
 | 
					    PositionComponent? hoverSkin,
 | 
				
			||||||
 | 
					    PositionComponent? disabledSkin,
 | 
				
			||||||
 | 
					    PositionComponent? defaultLabel,
 | 
				
			||||||
 | 
					    PositionComponent? disabledLabel,
 | 
				
			||||||
 | 
					    super.size,
 | 
				
			||||||
 | 
					    super.position,
 | 
				
			||||||
 | 
					    super.scale,
 | 
				
			||||||
 | 
					    super.angle,
 | 
				
			||||||
 | 
					    super.anchor,
 | 
				
			||||||
 | 
					    super.children,
 | 
				
			||||||
 | 
					    super.priority,
 | 
				
			||||||
 | 
					  }) {
 | 
				
			||||||
 | 
					    this.defaultSkin = defaultSkin;
 | 
				
			||||||
 | 
					    this.downSkin = downSkin;
 | 
				
			||||||
 | 
					    this.hoverSkin = hoverSkin;
 | 
				
			||||||
 | 
					    this.disabledSkin = disabledSkin;
 | 
				
			||||||
 | 
					    this.defaultLabel = defaultLabel;
 | 
				
			||||||
 | 
					    this.disabledLabel = disabledLabel;
 | 
				
			||||||
 | 
					    size.addListener(_updateSizes);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /// Callback for what should happen when the button is pressed.
 | 
				
			||||||
 | 
					  void Function()? onPressed;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /// Callback when button state changes
 | 
				
			||||||
 | 
					  void Function(ButtonState state)? onChangeState;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @mustCallSuper
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  Future<void> onLoad() async {
 | 
				
			||||||
 | 
					    super.onLoad();
 | 
				
			||||||
 | 
					    add(skinContainer);
 | 
				
			||||||
 | 
					    add(labelAlignContainer);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @protected
 | 
				
			||||||
 | 
					  final skinContainer = Component();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @protected
 | 
				
			||||||
 | 
					  AlignComponent labelAlignContainer = AlignComponent(alignment: Anchor.center);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  @mustCallSuper
 | 
				
			||||||
 | 
					  void onMount() {
 | 
				
			||||||
 | 
					    super.onMount();
 | 
				
			||||||
 | 
					    assert(
 | 
				
			||||||
 | 
					      defaultSkin != null,
 | 
				
			||||||
 | 
					      'The defaultSkin has to either be passed '
 | 
				
			||||||
 | 
					      'in as an argument or set in onLoad',
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    if (_state.isDefault && !contains(defaultSkin!)) {
 | 
				
			||||||
 | 
					      defaultSkin!.parent = skinContainer;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @protected
 | 
				
			||||||
 | 
					  bool isPressed = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  @mustCallSuper
 | 
				
			||||||
 | 
					  void onTapDown(TapDownEvent event) {
 | 
				
			||||||
 | 
					    if (_isDisabled) {
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    onPressed?.call();
 | 
				
			||||||
 | 
					    isPressed = true;
 | 
				
			||||||
 | 
					    updateState();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  void onTapUp(TapUpEvent event) {
 | 
				
			||||||
 | 
					    isPressed = false;
 | 
				
			||||||
 | 
					    updateState();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  void onHoverEnter() {
 | 
				
			||||||
 | 
					    updateState();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  void onHoverExit() {
 | 
				
			||||||
 | 
					    isPressed = false;
 | 
				
			||||||
 | 
					    updateState();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Map<ButtonState, PositionComponent?> skinsMap = {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  PositionComponent? get defaultSkin => skinsMap[ButtonState.up];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  set defaultSkin(PositionComponent? value) {
 | 
				
			||||||
 | 
					    skinsMap[ButtonState.up] = value;
 | 
				
			||||||
 | 
					    if (size.isZero()) {
 | 
				
			||||||
 | 
					      size = skinsMap[ButtonState.up]?.size ?? Vector2.zero();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    invalidateSkins();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  set downSkin(PositionComponent? value) {
 | 
				
			||||||
 | 
					    skinsMap[ButtonState.down] = value;
 | 
				
			||||||
 | 
					    invalidateSkins();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  set hoverSkin(PositionComponent? value) {
 | 
				
			||||||
 | 
					    skinsMap[ButtonState.hover] = value;
 | 
				
			||||||
 | 
					    invalidateSkins();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  set disabledSkin(PositionComponent? value) {
 | 
				
			||||||
 | 
					    skinsMap[ButtonState.disabled] = value;
 | 
				
			||||||
 | 
					    invalidateSkins();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Map<ButtonState, PositionComponent?> labelsMap = {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  PositionComponent? get defaultLabel => labelsMap[ButtonState.up];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  set defaultLabel(PositionComponent? value) {
 | 
				
			||||||
 | 
					    labelsMap[ButtonState.up] = value;
 | 
				
			||||||
 | 
					    updateLabel();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  set disabledLabel(PositionComponent? value) {
 | 
				
			||||||
 | 
					    labelsMap[ButtonState.disabled] = value;
 | 
				
			||||||
 | 
					    updateLabel();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @protected
 | 
				
			||||||
 | 
					  void invalidateSkins() {
 | 
				
			||||||
 | 
					    _updateSizes();
 | 
				
			||||||
 | 
					    _updateSkin();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  bool _isDisabled = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  bool get isDisabled => _isDisabled;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  set isDisabled(bool value) {
 | 
				
			||||||
 | 
					    if (_isDisabled == value) {
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    _isDisabled = value;
 | 
				
			||||||
 | 
					    updateState();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void _updateSizes() {
 | 
				
			||||||
 | 
					    for (final skin in skinsMap.values) {
 | 
				
			||||||
 | 
					      skin?.size = size;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @protected
 | 
				
			||||||
 | 
					  void updateState() {
 | 
				
			||||||
 | 
					    if (isDisabled) {
 | 
				
			||||||
 | 
					      setState(ButtonState.disabled);
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (isPressed) {
 | 
				
			||||||
 | 
					      setState(ButtonState.down);
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (isHovered) {
 | 
				
			||||||
 | 
					      setState(ButtonState.hover);
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    setState(ButtonState.up);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  ButtonState _state = ButtonState.up;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @protected
 | 
				
			||||||
 | 
					  void setState(ButtonState value) {
 | 
				
			||||||
 | 
					    if (_state == value) {
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    _state = value;
 | 
				
			||||||
 | 
					    _updateSkin();
 | 
				
			||||||
 | 
					    updateLabel();
 | 
				
			||||||
 | 
					    onChangeState?.call(_state);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void _updateSkin() {
 | 
				
			||||||
 | 
					    _removeSkins();
 | 
				
			||||||
 | 
					    setSkin(_state);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @protected
 | 
				
			||||||
 | 
					  void setSkin(ButtonState state) {
 | 
				
			||||||
 | 
					    (skinsMap[state] ?? defaultSkin)?.parent = skinContainer;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void _removeSkins() {
 | 
				
			||||||
 | 
					    for (final skins in skinsMap.values) {
 | 
				
			||||||
 | 
					      skins?.parent = null;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @protected
 | 
				
			||||||
 | 
					  void updateLabel() {
 | 
				
			||||||
 | 
					    _removeLabels();
 | 
				
			||||||
 | 
					    addLabel(_state);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @protected
 | 
				
			||||||
 | 
					  void addLabel(ButtonState state) {
 | 
				
			||||||
 | 
					    labelAlignContainer.child = labelsMap[state] ?? defaultLabel;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void _removeLabels() {
 | 
				
			||||||
 | 
					    for (final label in labelsMap.values) {
 | 
				
			||||||
 | 
					      label?.parent = null;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @protected
 | 
				
			||||||
 | 
					  bool hasSkinForState(ButtonState state) {
 | 
				
			||||||
 | 
					    return skinsMap[state] != null;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					enum ButtonState {
 | 
				
			||||||
 | 
					  up,
 | 
				
			||||||
 | 
					  upAndSelected,
 | 
				
			||||||
 | 
					  down,
 | 
				
			||||||
 | 
					  downAndSelected,
 | 
				
			||||||
 | 
					  hover,
 | 
				
			||||||
 | 
					  hoverAndSelected,
 | 
				
			||||||
 | 
					  disabled,
 | 
				
			||||||
 | 
					  disabledAndSelected;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const ButtonState();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  bool get isDefault {
 | 
				
			||||||
 | 
					    return this == ButtonState.up;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  bool get isDefaultSelected {
 | 
				
			||||||
 | 
					    return this == ButtonState.upAndSelected;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  bool get isNotDefault {
 | 
				
			||||||
 | 
					    return !isDefault;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  bool get isDown {
 | 
				
			||||||
 | 
					    return this == ButtonState.down;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  bool get isDownAndSelected {
 | 
				
			||||||
 | 
					    return this == ButtonState.downAndSelected;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  bool get isHover {
 | 
				
			||||||
 | 
					    return this == ButtonState.hover;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  bool get isHoverAndSelected {
 | 
				
			||||||
 | 
					    return this == ButtonState.hoverAndSelected;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  bool get isDisabled {
 | 
				
			||||||
 | 
					    return this == ButtonState.disabled;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  bool get isDisabledAndSelected {
 | 
				
			||||||
 | 
					    return this == ButtonState.disabledAndSelected;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -0,0 +1,167 @@
 | 
				
			|||||||
 | 
					import 'package:flame/components.dart';
 | 
				
			||||||
 | 
					import 'package:flame/events.dart';
 | 
				
			||||||
 | 
					import 'package:flutter/foundation.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// The [ToggleButtonComponent] is an [AdvancedButtonComponent] that can switch
 | 
				
			||||||
 | 
					/// between the selected and not selected state, imagine for example a switch
 | 
				
			||||||
 | 
					/// widget or a tab that can be selected.
 | 
				
			||||||
 | 
					///
 | 
				
			||||||
 | 
					/// Note: You have to set the [defaultSkin], [defaultSelectedSkin]
 | 
				
			||||||
 | 
					/// and other skins that you want to use in [onLoad] if you are not passed in
 | 
				
			||||||
 | 
					/// through the constructor.
 | 
				
			||||||
 | 
					class ToggleButtonComponent extends AdvancedButtonComponent {
 | 
				
			||||||
 | 
					  ToggleButtonComponent({
 | 
				
			||||||
 | 
					    super.onPressed,
 | 
				
			||||||
 | 
					    this.onSelectedChanged,
 | 
				
			||||||
 | 
					    super.onChangeState,
 | 
				
			||||||
 | 
					    super.defaultSkin,
 | 
				
			||||||
 | 
					    super.downSkin,
 | 
				
			||||||
 | 
					    super.hoverSkin,
 | 
				
			||||||
 | 
					    super.disabledSkin,
 | 
				
			||||||
 | 
					    PositionComponent? defaultSelectedSkin,
 | 
				
			||||||
 | 
					    PositionComponent? downAndSelectedSkin,
 | 
				
			||||||
 | 
					    PositionComponent? hoverAndSelectedSkin,
 | 
				
			||||||
 | 
					    PositionComponent? disabledAndSelectedSkin,
 | 
				
			||||||
 | 
					    super.defaultLabel,
 | 
				
			||||||
 | 
					    super.disabledLabel,
 | 
				
			||||||
 | 
					    PositionComponent? defaultSelectedLabel,
 | 
				
			||||||
 | 
					    PositionComponent? disabledAndSelectedLabel,
 | 
				
			||||||
 | 
					    super.size,
 | 
				
			||||||
 | 
					    super.position,
 | 
				
			||||||
 | 
					    super.scale,
 | 
				
			||||||
 | 
					    super.angle,
 | 
				
			||||||
 | 
					    super.anchor,
 | 
				
			||||||
 | 
					    super.children,
 | 
				
			||||||
 | 
					    super.priority,
 | 
				
			||||||
 | 
					  }) {
 | 
				
			||||||
 | 
					    this.defaultSelectedSkin = defaultSelectedSkin;
 | 
				
			||||||
 | 
					    this.downAndSelectedSkin = downAndSelectedSkin;
 | 
				
			||||||
 | 
					    this.hoverAndSelectedSkin = hoverAndSelectedSkin;
 | 
				
			||||||
 | 
					    this.disabledAndSelectedSkin = disabledAndSelectedSkin;
 | 
				
			||||||
 | 
					    this.defaultSelectedLabel = defaultSelectedLabel;
 | 
				
			||||||
 | 
					    this.disabledAndSelectedLabel = disabledAndSelectedLabel;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /// Callback when button selected changed
 | 
				
			||||||
 | 
					  ValueChanged<bool>? onSelectedChanged;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  @mustCallSuper
 | 
				
			||||||
 | 
					  void onMount() {
 | 
				
			||||||
 | 
					    assert(
 | 
				
			||||||
 | 
					      defaultSelectedSkin != null,
 | 
				
			||||||
 | 
					      'The defaultSelectedSkin has to either be passed '
 | 
				
			||||||
 | 
					      'in as an argument or set in onLoad',
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    super.onMount();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  PositionComponent? get defaultSelectedSkin =>
 | 
				
			||||||
 | 
					      skinsMap[ButtonState.upAndSelected];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  set defaultSelectedSkin(PositionComponent? value) {
 | 
				
			||||||
 | 
					    skinsMap[ButtonState.upAndSelected] = value;
 | 
				
			||||||
 | 
					    invalidateSkins();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  set downAndSelectedSkin(PositionComponent? value) {
 | 
				
			||||||
 | 
					    skinsMap[ButtonState.downAndSelected] = value;
 | 
				
			||||||
 | 
					    invalidateSkins();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  set hoverAndSelectedSkin(PositionComponent? value) {
 | 
				
			||||||
 | 
					    skinsMap[ButtonState.hoverAndSelected] = value;
 | 
				
			||||||
 | 
					    invalidateSkins();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  set disabledAndSelectedSkin(PositionComponent? value) {
 | 
				
			||||||
 | 
					    skinsMap[ButtonState.disabledAndSelected] = value;
 | 
				
			||||||
 | 
					    invalidateSkins();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  PositionComponent? get defaultSelectedLabel =>
 | 
				
			||||||
 | 
					      labelsMap[ButtonState.upAndSelected];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  set defaultSelectedLabel(PositionComponent? value) {
 | 
				
			||||||
 | 
					    labelsMap[ButtonState.upAndSelected] = value;
 | 
				
			||||||
 | 
					    updateLabel();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  set disabledAndSelectedLabel(PositionComponent? value) {
 | 
				
			||||||
 | 
					    labelsMap[ButtonState.disabledAndSelected] = value;
 | 
				
			||||||
 | 
					    updateLabel();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  void onTapUp(TapUpEvent event) {
 | 
				
			||||||
 | 
					    isSelected = !_isSelected;
 | 
				
			||||||
 | 
					    super.onTapUp(event);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  bool _isSelected = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  bool get isSelected => _isSelected;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  set isSelected(bool value) {
 | 
				
			||||||
 | 
					    if (_isSelected == value) {
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    _isSelected = value;
 | 
				
			||||||
 | 
					    updateState();
 | 
				
			||||||
 | 
					    onSelectedChanged?.call(_isSelected);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  @protected
 | 
				
			||||||
 | 
					  void setSkin(ButtonState state) {
 | 
				
			||||||
 | 
					    var skin = skinsMap[state];
 | 
				
			||||||
 | 
					    if (state.isDisabledAndSelected && !hasSkinForState(state)) {
 | 
				
			||||||
 | 
					      skin = skinsMap[ButtonState.disabled];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (state.isDownAndSelected && !hasSkinForState(state)) {
 | 
				
			||||||
 | 
					      skin = skinsMap[ButtonState.down];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (state.isHoverAndSelected && !hasSkinForState(state)) {
 | 
				
			||||||
 | 
					      skin = skinsMap[ButtonState.hover];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (state.isDownAndSelected && !hasSkinForState(state)) {
 | 
				
			||||||
 | 
					      skin = skinsMap[ButtonState.down];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    skin = skin ?? (isSelected ? defaultSelectedSkin : defaultSkin);
 | 
				
			||||||
 | 
					    skin?.parent = skinContainer;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  @protected
 | 
				
			||||||
 | 
					  void addLabel(ButtonState state) {
 | 
				
			||||||
 | 
					    labelAlignContainer.child =
 | 
				
			||||||
 | 
					        labelsMap[state] ?? (isSelected ? defaultSelectedLabel : defaultLabel);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @mustCallSuper
 | 
				
			||||||
 | 
					  @protected
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  void updateState() {
 | 
				
			||||||
 | 
					    if (isDisabled) {
 | 
				
			||||||
 | 
					      setState(
 | 
				
			||||||
 | 
					        _isSelected ? ButtonState.disabledAndSelected : ButtonState.disabled,
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (isPressed) {
 | 
				
			||||||
 | 
					      setState(
 | 
				
			||||||
 | 
					        _isSelected ? ButtonState.downAndSelected : ButtonState.down,
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (isHovered) {
 | 
				
			||||||
 | 
					      setState(
 | 
				
			||||||
 | 
					        _isSelected ? ButtonState.hoverAndSelected : ButtonState.hover,
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    setState(
 | 
				
			||||||
 | 
					      _isSelected ? ButtonState.upAndSelected : ButtonState.up,
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										
											BIN
										
									
								
								packages/flame/test/_goldens/advanced_button_component.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								packages/flame/test/_goldens/advanced_button_component.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 199 B  | 
@ -0,0 +1,221 @@
 | 
				
			|||||||
 | 
					import 'package:flame/components.dart';
 | 
				
			||||||
 | 
					import 'package:flame/game.dart';
 | 
				
			||||||
 | 
					import 'package:flame/src/events/flame_game_mixins/multi_tap_dispatcher.dart';
 | 
				
			||||||
 | 
					import 'package:flame_test/flame_test.dart';
 | 
				
			||||||
 | 
					import 'package:flutter/material.dart';
 | 
				
			||||||
 | 
					import 'package:flutter_test/flutter_test.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void main() {
 | 
				
			||||||
 | 
					  group('AdvancedButtonComponent', () {
 | 
				
			||||||
 | 
					    testGolden(
 | 
				
			||||||
 | 
					      'label renders correctly',
 | 
				
			||||||
 | 
					      (game) async {
 | 
				
			||||||
 | 
					        await game.add(
 | 
				
			||||||
 | 
					          AdvancedButtonComponent(
 | 
				
			||||||
 | 
					            defaultSkin: RectangleComponent(size: Vector2(40, 20)),
 | 
				
			||||||
 | 
					            defaultLabel: RectangleComponent(
 | 
				
			||||||
 | 
					              size: Vector2(10, 5),
 | 
				
			||||||
 | 
					              paint: Paint()..color = const Color(0xFFFF0000),
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					          ),
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      size: Vector2(50, 30),
 | 
				
			||||||
 | 
					      goldenFile: '../_goldens/advanced_button_component.png',
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    testWithFlameGame('correctly registers taps', (game) async {
 | 
				
			||||||
 | 
					      var pressedTimes = 0;
 | 
				
			||||||
 | 
					      final initialGameSize = Vector2.all(200);
 | 
				
			||||||
 | 
					      final componentSize = Vector2.all(10);
 | 
				
			||||||
 | 
					      final buttonPosition = Vector2.all(100);
 | 
				
			||||||
 | 
					      late final AdvancedButtonComponent button;
 | 
				
			||||||
 | 
					      game.onGameResize(initialGameSize);
 | 
				
			||||||
 | 
					      await game.ensureAdd(
 | 
				
			||||||
 | 
					        button = AdvancedButtonComponent(
 | 
				
			||||||
 | 
					          defaultSkin: RectangleComponent(size: componentSize),
 | 
				
			||||||
 | 
					          onPressed: () => pressedTimes++,
 | 
				
			||||||
 | 
					          position: buttonPosition,
 | 
				
			||||||
 | 
					          size: componentSize,
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      expect(pressedTimes, 0);
 | 
				
			||||||
 | 
					      final tapDispatcher = game.firstChild<MultiTapDispatcher>()!;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      tapDispatcher.handleTapDown(1, TapDownDetails());
 | 
				
			||||||
 | 
					      expect(pressedTimes, 0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      tapDispatcher.handleTapUp(
 | 
				
			||||||
 | 
					        1,
 | 
				
			||||||
 | 
					        createTapUpDetails(
 | 
				
			||||||
 | 
					          globalPosition: button.positionOfAnchor(Anchor.center).toOffset(),
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					      expect(pressedTimes, 0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      tapDispatcher.handleTapDown(
 | 
				
			||||||
 | 
					        1,
 | 
				
			||||||
 | 
					        TapDownDetails(globalPosition: buttonPosition.toOffset()),
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					      expect(pressedTimes, 1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      tapDispatcher.handleTapUp(
 | 
				
			||||||
 | 
					        1,
 | 
				
			||||||
 | 
					        createTapUpDetails(globalPosition: buttonPosition.toOffset()),
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					      expect(pressedTimes, 1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      tapDispatcher.handleTapDown(
 | 
				
			||||||
 | 
					        1,
 | 
				
			||||||
 | 
					        TapDownDetails(globalPosition: buttonPosition.toOffset()),
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					      tapDispatcher.handleTapCancel(1);
 | 
				
			||||||
 | 
					      expect(pressedTimes, 2);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    testWithFlameGame('correctly registers taps onGameResize', (game) async {
 | 
				
			||||||
 | 
					      var pressedTimes = 0;
 | 
				
			||||||
 | 
					      final initialGameSize = Vector2.all(100);
 | 
				
			||||||
 | 
					      final componentSize = Vector2.all(10);
 | 
				
			||||||
 | 
					      final buttonPosition = Vector2.all(100);
 | 
				
			||||||
 | 
					      late final AdvancedButtonComponent button;
 | 
				
			||||||
 | 
					      game.onGameResize(initialGameSize);
 | 
				
			||||||
 | 
					      await game.ensureAdd(
 | 
				
			||||||
 | 
					        button = AdvancedButtonComponent(
 | 
				
			||||||
 | 
					          defaultSkin: RectangleComponent(size: componentSize),
 | 
				
			||||||
 | 
					          onPressed: () => pressedTimes++,
 | 
				
			||||||
 | 
					          position: buttonPosition,
 | 
				
			||||||
 | 
					          size: componentSize,
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					      final previousPosition =
 | 
				
			||||||
 | 
					          button.positionOfAnchor(Anchor.center).toOffset();
 | 
				
			||||||
 | 
					      game.onGameResize(initialGameSize * 2);
 | 
				
			||||||
 | 
					      final tapDispatcher = game.firstChild<MultiTapDispatcher>()!;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      tapDispatcher.handleTapDown(
 | 
				
			||||||
 | 
					        1,
 | 
				
			||||||
 | 
					        TapDownDetails(globalPosition: previousPosition),
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					      expect(pressedTimes, 1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      tapDispatcher.handleTapUp(
 | 
				
			||||||
 | 
					        1,
 | 
				
			||||||
 | 
					        createTapUpDetails(globalPosition: previousPosition),
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					      expect(pressedTimes, 1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      tapDispatcher.handleTapDown(
 | 
				
			||||||
 | 
					        1,
 | 
				
			||||||
 | 
					        TapDownDetails(globalPosition: previousPosition),
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					      tapDispatcher.handleTapCancel(1);
 | 
				
			||||||
 | 
					      expect(pressedTimes, 2);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    testWithFlameGame('correctly work isDisabled', (game) async {
 | 
				
			||||||
 | 
					      var pressedTimes = 0;
 | 
				
			||||||
 | 
					      final initialGameSize = Vector2.all(100);
 | 
				
			||||||
 | 
					      final componentSize = Vector2.all(10);
 | 
				
			||||||
 | 
					      final buttonPosition = Vector2.all(100);
 | 
				
			||||||
 | 
					      late final AdvancedButtonComponent button;
 | 
				
			||||||
 | 
					      game.onGameResize(initialGameSize);
 | 
				
			||||||
 | 
					      await game.ensureAdd(
 | 
				
			||||||
 | 
					        button = AdvancedButtonComponent(
 | 
				
			||||||
 | 
					          defaultSkin: RectangleComponent(size: componentSize),
 | 
				
			||||||
 | 
					          onPressed: () => pressedTimes++,
 | 
				
			||||||
 | 
					          position: buttonPosition,
 | 
				
			||||||
 | 
					          size: componentSize,
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					      button.isDisabled = true;
 | 
				
			||||||
 | 
					      final previousPosition =
 | 
				
			||||||
 | 
					          button.positionOfAnchor(Anchor.center).toOffset();
 | 
				
			||||||
 | 
					      game.onGameResize(initialGameSize * 2);
 | 
				
			||||||
 | 
					      final tapDispatcher = game.firstChild<MultiTapDispatcher>()!;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      tapDispatcher.handleTapDown(
 | 
				
			||||||
 | 
					        1,
 | 
				
			||||||
 | 
					        TapDownDetails(globalPosition: previousPosition),
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					      expect(pressedTimes, 0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      tapDispatcher.handleTapUp(
 | 
				
			||||||
 | 
					        1,
 | 
				
			||||||
 | 
					        createTapUpDetails(globalPosition: previousPosition),
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					      expect(pressedTimes, 0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      tapDispatcher.handleTapDown(
 | 
				
			||||||
 | 
					        1,
 | 
				
			||||||
 | 
					        TapDownDetails(globalPosition: previousPosition),
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					      tapDispatcher.handleTapCancel(1);
 | 
				
			||||||
 | 
					      expect(pressedTimes, 0);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    testWidgets(
 | 
				
			||||||
 | 
					      '[#1723] can be pressed while the engine is paused',
 | 
				
			||||||
 | 
					      (tester) async {
 | 
				
			||||||
 | 
					        final game = FlameGame();
 | 
				
			||||||
 | 
					        game.add(
 | 
				
			||||||
 | 
					          AdvancedButtonComponent(
 | 
				
			||||||
 | 
					            defaultSkin: CircleComponent(radius: 40),
 | 
				
			||||||
 | 
					            downSkin: CircleComponent(radius: 40),
 | 
				
			||||||
 | 
					            position: Vector2(400, 300),
 | 
				
			||||||
 | 
					            anchor: Anchor.center,
 | 
				
			||||||
 | 
					            onPressed: () {
 | 
				
			||||||
 | 
					              game.pauseEngine();
 | 
				
			||||||
 | 
					              game.overlays.add('pause-menu');
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					          ),
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					        await tester.pumpWidget(
 | 
				
			||||||
 | 
					          GameWidget(
 | 
				
			||||||
 | 
					            game: game,
 | 
				
			||||||
 | 
					            overlayBuilderMap: {
 | 
				
			||||||
 | 
					              'pause-menu': (context, _) {
 | 
				
			||||||
 | 
					                return SimpleStatelessWidget(
 | 
				
			||||||
 | 
					                  build: (context) {
 | 
				
			||||||
 | 
					                    return Center(
 | 
				
			||||||
 | 
					                      child: OutlinedButton(
 | 
				
			||||||
 | 
					                        onPressed: () {
 | 
				
			||||||
 | 
					                          game.overlays.remove('pause-menu');
 | 
				
			||||||
 | 
					                          game.resumeEngine();
 | 
				
			||||||
 | 
					                        },
 | 
				
			||||||
 | 
					                        child: const Text('Resume'),
 | 
				
			||||||
 | 
					                      ),
 | 
				
			||||||
 | 
					                    );
 | 
				
			||||||
 | 
					                  },
 | 
				
			||||||
 | 
					                );
 | 
				
			||||||
 | 
					              },
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					          ),
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					        await tester.pump();
 | 
				
			||||||
 | 
					        await tester.pump();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        await tester.tapAt(const Offset(400, 300));
 | 
				
			||||||
 | 
					        await tester.pump(const Duration(seconds: 1));
 | 
				
			||||||
 | 
					        expect(game.paused, true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        await tester.tapAt(const Offset(400, 300));
 | 
				
			||||||
 | 
					        await tester.pump(const Duration(seconds: 1));
 | 
				
			||||||
 | 
					        expect(game.paused, false);
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class SimpleStatelessWidget extends StatelessWidget {
 | 
				
			||||||
 | 
					  const SimpleStatelessWidget({
 | 
				
			||||||
 | 
					    required Widget Function(BuildContext) build,
 | 
				
			||||||
 | 
					    super.key,
 | 
				
			||||||
 | 
					  }) : _build = build;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  final Widget Function(BuildContext) _build;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  Widget build(BuildContext context) => _build(context);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										254
									
								
								packages/flame/test/components/toogle_button_component_test.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										254
									
								
								packages/flame/test/components/toogle_button_component_test.dart
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,254 @@
 | 
				
			|||||||
 | 
					import 'package:flame/components.dart';
 | 
				
			||||||
 | 
					import 'package:flame/game.dart';
 | 
				
			||||||
 | 
					import 'package:flame/src/events/flame_game_mixins/multi_tap_dispatcher.dart';
 | 
				
			||||||
 | 
					import 'package:flame_test/flame_test.dart';
 | 
				
			||||||
 | 
					import 'package:flutter/material.dart';
 | 
				
			||||||
 | 
					import 'package:flutter_test/flutter_test.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void main() {
 | 
				
			||||||
 | 
					  group('ToggleButtonComponent', () {
 | 
				
			||||||
 | 
					    testWithFlameGame('correctly registers taps', (game) async {
 | 
				
			||||||
 | 
					      var pressedTimes = 0;
 | 
				
			||||||
 | 
					      final initialGameSize = Vector2.all(200);
 | 
				
			||||||
 | 
					      final componentSize = Vector2.all(10);
 | 
				
			||||||
 | 
					      final buttonPosition = Vector2.all(100);
 | 
				
			||||||
 | 
					      late final ToggleButtonComponent button;
 | 
				
			||||||
 | 
					      game.onGameResize(initialGameSize);
 | 
				
			||||||
 | 
					      await game.ensureAdd(
 | 
				
			||||||
 | 
					        button = ToggleButtonComponent(
 | 
				
			||||||
 | 
					          defaultSkin: RectangleComponent(size: componentSize),
 | 
				
			||||||
 | 
					          defaultSelectedSkin: RectangleComponent(size: componentSize),
 | 
				
			||||||
 | 
					          onPressed: () => pressedTimes++,
 | 
				
			||||||
 | 
					          position: buttonPosition,
 | 
				
			||||||
 | 
					          size: componentSize,
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      expect(pressedTimes, 0);
 | 
				
			||||||
 | 
					      final tapDispatcher = game.firstChild<MultiTapDispatcher>()!;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      tapDispatcher.handleTapDown(1, TapDownDetails());
 | 
				
			||||||
 | 
					      expect(pressedTimes, 0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      tapDispatcher.handleTapUp(
 | 
				
			||||||
 | 
					        1,
 | 
				
			||||||
 | 
					        createTapUpDetails(
 | 
				
			||||||
 | 
					          globalPosition: button.positionOfAnchor(Anchor.center).toOffset(),
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					      expect(pressedTimes, 0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      tapDispatcher.handleTapDown(
 | 
				
			||||||
 | 
					        1,
 | 
				
			||||||
 | 
					        TapDownDetails(globalPosition: buttonPosition.toOffset()),
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					      expect(pressedTimes, 1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      tapDispatcher.handleTapUp(
 | 
				
			||||||
 | 
					        1,
 | 
				
			||||||
 | 
					        createTapUpDetails(globalPosition: buttonPosition.toOffset()),
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					      expect(pressedTimes, 1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      tapDispatcher.handleTapDown(
 | 
				
			||||||
 | 
					        1,
 | 
				
			||||||
 | 
					        TapDownDetails(globalPosition: buttonPosition.toOffset()),
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					      tapDispatcher.handleTapCancel(1);
 | 
				
			||||||
 | 
					      expect(pressedTimes, 2);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    testWithFlameGame('correctly registers taps onGameResize', (game) async {
 | 
				
			||||||
 | 
					      var pressedTimes = 0;
 | 
				
			||||||
 | 
					      final initialGameSize = Vector2.all(100);
 | 
				
			||||||
 | 
					      final componentSize = Vector2.all(10);
 | 
				
			||||||
 | 
					      final buttonPosition = Vector2.all(100);
 | 
				
			||||||
 | 
					      late final ToggleButtonComponent button;
 | 
				
			||||||
 | 
					      game.onGameResize(initialGameSize);
 | 
				
			||||||
 | 
					      await game.ensureAdd(
 | 
				
			||||||
 | 
					        button = ToggleButtonComponent(
 | 
				
			||||||
 | 
					          defaultSkin: RectangleComponent(size: componentSize),
 | 
				
			||||||
 | 
					          defaultSelectedSkin: RectangleComponent(size: componentSize),
 | 
				
			||||||
 | 
					          onPressed: () => pressedTimes++,
 | 
				
			||||||
 | 
					          position: buttonPosition,
 | 
				
			||||||
 | 
					          size: componentSize,
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					      final previousPosition =
 | 
				
			||||||
 | 
					          button.positionOfAnchor(Anchor.center).toOffset();
 | 
				
			||||||
 | 
					      game.onGameResize(initialGameSize * 2);
 | 
				
			||||||
 | 
					      final tapDispatcher = game.firstChild<MultiTapDispatcher>()!;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      tapDispatcher.handleTapDown(
 | 
				
			||||||
 | 
					        1,
 | 
				
			||||||
 | 
					        TapDownDetails(globalPosition: previousPosition),
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					      expect(pressedTimes, 1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      tapDispatcher.handleTapUp(
 | 
				
			||||||
 | 
					        1,
 | 
				
			||||||
 | 
					        createTapUpDetails(globalPosition: previousPosition),
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					      expect(pressedTimes, 1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      tapDispatcher.handleTapDown(
 | 
				
			||||||
 | 
					        1,
 | 
				
			||||||
 | 
					        TapDownDetails(globalPosition: previousPosition),
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					      tapDispatcher.handleTapCancel(1);
 | 
				
			||||||
 | 
					      expect(pressedTimes, 2);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    testWithFlameGame('correctly work isDisabled', (game) async {
 | 
				
			||||||
 | 
					      var pressedTimes = 0;
 | 
				
			||||||
 | 
					      final initialGameSize = Vector2.all(100);
 | 
				
			||||||
 | 
					      final componentSize = Vector2.all(10);
 | 
				
			||||||
 | 
					      final buttonPosition = Vector2.all(100);
 | 
				
			||||||
 | 
					      late final ToggleButtonComponent button;
 | 
				
			||||||
 | 
					      game.onGameResize(initialGameSize);
 | 
				
			||||||
 | 
					      await game.ensureAdd(
 | 
				
			||||||
 | 
					        button = ToggleButtonComponent(
 | 
				
			||||||
 | 
					          defaultSkin: RectangleComponent(size: componentSize),
 | 
				
			||||||
 | 
					          defaultSelectedSkin: RectangleComponent(size: componentSize),
 | 
				
			||||||
 | 
					          onPressed: () => pressedTimes++,
 | 
				
			||||||
 | 
					          position: buttonPosition,
 | 
				
			||||||
 | 
					          size: componentSize,
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					      button.isDisabled = true;
 | 
				
			||||||
 | 
					      final previousPosition =
 | 
				
			||||||
 | 
					          button.positionOfAnchor(Anchor.center).toOffset();
 | 
				
			||||||
 | 
					      game.onGameResize(initialGameSize * 2);
 | 
				
			||||||
 | 
					      final tapDispatcher = game.firstChild<MultiTapDispatcher>()!;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      tapDispatcher.handleTapDown(
 | 
				
			||||||
 | 
					        1,
 | 
				
			||||||
 | 
					        TapDownDetails(globalPosition: previousPosition),
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					      expect(pressedTimes, 0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      tapDispatcher.handleTapUp(
 | 
				
			||||||
 | 
					        1,
 | 
				
			||||||
 | 
					        createTapUpDetails(globalPosition: previousPosition),
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					      expect(pressedTimes, 0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      tapDispatcher.handleTapDown(
 | 
				
			||||||
 | 
					        1,
 | 
				
			||||||
 | 
					        TapDownDetails(globalPosition: previousPosition),
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					      tapDispatcher.handleTapCancel(1);
 | 
				
			||||||
 | 
					      expect(pressedTimes, 0);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    testWithFlameGame('toggle works correctly', (game) async {
 | 
				
			||||||
 | 
					      var pressedTimes = 0;
 | 
				
			||||||
 | 
					      final initialGameSize = Vector2.all(100);
 | 
				
			||||||
 | 
					      final componentSize = Vector2.all(10);
 | 
				
			||||||
 | 
					      final buttonPosition = Vector2.all(100);
 | 
				
			||||||
 | 
					      late final ToggleButtonComponent button;
 | 
				
			||||||
 | 
					      game.onGameResize(initialGameSize);
 | 
				
			||||||
 | 
					      await game.ensureAdd(
 | 
				
			||||||
 | 
					        button = ToggleButtonComponent(
 | 
				
			||||||
 | 
					          defaultSkin: RectangleComponent(size: componentSize),
 | 
				
			||||||
 | 
					          defaultSelectedSkin: RectangleComponent(size: componentSize),
 | 
				
			||||||
 | 
					          onPressed: () => pressedTimes++,
 | 
				
			||||||
 | 
					          position: buttonPosition,
 | 
				
			||||||
 | 
					          size: componentSize,
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					      final previousPosition =
 | 
				
			||||||
 | 
					          button.positionOfAnchor(Anchor.center).toOffset();
 | 
				
			||||||
 | 
					      game.onGameResize(initialGameSize * 2);
 | 
				
			||||||
 | 
					      final tapDispatcher = game.firstChild<MultiTapDispatcher>()!;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      tapDispatcher.handleTapDown(
 | 
				
			||||||
 | 
					        1,
 | 
				
			||||||
 | 
					        TapDownDetails(globalPosition: previousPosition),
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					      expect(button.isSelected, false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      tapDispatcher.handleTapUp(
 | 
				
			||||||
 | 
					        1,
 | 
				
			||||||
 | 
					        createTapUpDetails(globalPosition: previousPosition),
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					      expect(button.isSelected, true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      tapDispatcher.handleTapDown(
 | 
				
			||||||
 | 
					        1,
 | 
				
			||||||
 | 
					        TapDownDetails(globalPosition: previousPosition),
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					      expect(button.isSelected, true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      tapDispatcher.handleTapUp(
 | 
				
			||||||
 | 
					        1,
 | 
				
			||||||
 | 
					        createTapUpDetails(globalPosition: previousPosition),
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					      expect(button.isSelected, false);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    testWidgets(
 | 
				
			||||||
 | 
					      '[#1723] can be pressed while the engine is paused',
 | 
				
			||||||
 | 
					      (tester) async {
 | 
				
			||||||
 | 
					        final game = FlameGame();
 | 
				
			||||||
 | 
					        game.add(
 | 
				
			||||||
 | 
					          ToggleButtonComponent(
 | 
				
			||||||
 | 
					            defaultSkin: CircleComponent(radius: 40),
 | 
				
			||||||
 | 
					            downSkin: CircleComponent(radius: 40),
 | 
				
			||||||
 | 
					            defaultSelectedSkin: CircleComponent(radius: 40),
 | 
				
			||||||
 | 
					            position: Vector2(400, 300),
 | 
				
			||||||
 | 
					            anchor: Anchor.center,
 | 
				
			||||||
 | 
					            onPressed: () {
 | 
				
			||||||
 | 
					              game.pauseEngine();
 | 
				
			||||||
 | 
					              game.overlays.add('pause-menu');
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					          ),
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					        await tester.pumpWidget(
 | 
				
			||||||
 | 
					          GameWidget(
 | 
				
			||||||
 | 
					            game: game,
 | 
				
			||||||
 | 
					            overlayBuilderMap: {
 | 
				
			||||||
 | 
					              'pause-menu': (context, _) {
 | 
				
			||||||
 | 
					                return SimpleStatelessWidget(
 | 
				
			||||||
 | 
					                  build: (context) {
 | 
				
			||||||
 | 
					                    return Center(
 | 
				
			||||||
 | 
					                      child: OutlinedButton(
 | 
				
			||||||
 | 
					                        onPressed: () {
 | 
				
			||||||
 | 
					                          game.overlays.remove('pause-menu');
 | 
				
			||||||
 | 
					                          game.resumeEngine();
 | 
				
			||||||
 | 
					                        },
 | 
				
			||||||
 | 
					                        child: const Text('Resume'),
 | 
				
			||||||
 | 
					                      ),
 | 
				
			||||||
 | 
					                    );
 | 
				
			||||||
 | 
					                  },
 | 
				
			||||||
 | 
					                );
 | 
				
			||||||
 | 
					              },
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					          ),
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					        await tester.pump();
 | 
				
			||||||
 | 
					        await tester.pump();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        await tester.tapAt(const Offset(400, 300));
 | 
				
			||||||
 | 
					        await tester.pump(const Duration(seconds: 1));
 | 
				
			||||||
 | 
					        expect(game.paused, true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        await tester.tapAt(const Offset(400, 300));
 | 
				
			||||||
 | 
					        await tester.pump(const Duration(seconds: 1));
 | 
				
			||||||
 | 
					        expect(game.paused, false);
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class SimpleStatelessWidget extends StatelessWidget {
 | 
				
			||||||
 | 
					  const SimpleStatelessWidget({
 | 
				
			||||||
 | 
					    required Widget Function(BuildContext) build,
 | 
				
			||||||
 | 
					    super.key,
 | 
				
			||||||
 | 
					  }) : _build = build;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  final Widget Function(BuildContext) _build;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  Widget build(BuildContext context) => _build(context);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Reference in New Issue
	
	Block a user